51

『No25: 编写可读代码的艺术(2)』

 5 years ago
source link: https://studygolang.com/articles/15649?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
zam2eyF.png!web

GOPHER_VIKING.png

大家好,我叫谢伟,是一名程序员。

上节从编程语言特性的角度讲述了编写可读代码的几个要点。

编写可读的代码的艺术

本节接着从编程语言的语言特性:流程控制和循环等角度,再次谈谈编写可读代码的要点。

还记得吗,编写可读代码的核心的要点是什么?

写易于理解的代码

1. 流程控制

1.1 条件参数的顺序

编程语言关于流程控制的语句有哪些?

  • if ... else
  • while
  • switch

涉及流程控制的话,一般涉及条件判断,你有认真思考条件判断语句中的参数的顺序吗?

比如:

if (number < 10) {} // A

if (10 > number) {} // B

if (receivedNumber < expectedNumber) // C

if (expectNumber > receivedNumber) // D

上述实例,你会条件语句的参数顺序你会选择哪个?

A, C

那么应该准从什么样的尊则?

左边倾向于变量,右边倾向于常量;

其实这不是什么新的东西,在我们学习数学中的未知数的时候就是这么做的。

x < 2

1.2 if...else 语句块的顺序

可以参照下面的下面准则:

  • 先判断正向逻辑的,再判断负向逻辑
  • 先处理简单
  • 先处理有趣的或者可疑的
if createParam.Data.ShopType != RegionEntrances {
    newShop.ShopUUID = tools.GenerateUUID(16, createParam.Data.Name, company.Name)
} else {
    var tmpShop models.Shop
    if notFound := database.POSTGRES.Where("company_id = ? AND shop_type = ?", company.ID, createParam.Data.ShopType).First(&tmpShop).RecordNotFound(); notFound {
        newShop.ShopUUID = tools.GenerateUUID(16, createParam.Data.Name, company.Name)
    } else {
        newShop.ShopUUID = tmpShop.ShopUUID
    }

}

上文根据输入的 ShopType 字段是否是 RegionEntrances;

  1. 如果是,搜索数据库,看数据是否是有此字段,存在则获取shopUUID
  2. 否则 产生 shopUUID
  3. 如果根本不是 RegionEntrances 字段,则产生 shopUUID

上文没有一定的规范,搞的整个流程不容易理解。

根据:先处理正向逻辑,处理简单的,处理可疑或者有趣的准则,改善如下(仅仅只是调换顺序)

if createParam.Data.ShopType == RegionEntrances {
    var tmpShop models.Shop
    if notFound := database.POSTGRES.Where("company_id = ? AND shop_type = ?", company.ID, createParam.Data.ShopType).First(&tmpShop).RecordNotFound(); !notFound {
        newShop.ShopUUID = tmpShop.ShopUUID
    } else {
        newShop.ShopUUID = tools.GenerateUUID(16, createParam.Data.Name, company.Name)
    }
} else {
    newShop.ShopUUID = tools.GenerateUUID(16, createParam.Data.Name, company.Name)

}

1.3 避免使用三目运算符

三目运算符一定程度上能够精简代码,减少代码的行数,但是却存在另外一个缺点,即:不容易理解(虽然大学教材总会考这类题目,判断执行的顺序和结果)

只在简单场景下使用三目运算符。

1.4 函数什么时候返回

经常我们编写函数的时候,喜欢声明一个变量用来存储结果,到所有的逻辑结束后返回这个变量作为函数的返回值。

几个建议:

  • 可以提前进行函数返回值,多几个 return, 没关系
  • 最好函数都要有返回值,Golang 里建议至少返回一个 错误信息

1.5 减少多层级的嵌套

层级的增多,增加了认知的负担。而且容易出现不容易发现的 bug。

如何减少嵌套:

  • 提前函数返回
  • 在循坏内使用 continue

2. 表达式

建议使用短表达式

如何做到短表达式:

  • 已有的项目:拆分
  • 新的代码:有意识的使用短表达式

如何拆分:

使用中间变量

中间变量的用途可以划分为:

  • 解释型变量
  • 总结性变量

比如:

if createParam.Data.ShopType == RegionEntrances {}

感觉表达式长了,怎么做:

var shopTypeEqual = createParam.Data.ShopType == RegionEntrances

if shopTypeEqual {}

3. 变量

编程语言支持显式申明,也支持自动识别变量类型,你觉得哪种好?

var number int
number = 10

numberMax := 100

显式的命名更好,强类型编程语言遇到的问题可能还不多,弱类型的编程语言,可能存在隐藏的 bug.

变量的申明区分:全局和局部

问:全局变量多点好?还是少点好?

问:局部变量是统一在函数下侧统一命名,还是靠近需要使用变量的语句处?

全局变量少用,随着项目越来越复杂,可能在某个角落,全局变量就进行了更改。这样引起的 异常处理很难进行追踪和分析。

局部变量在靠近使用该变量的地方声明并使用,这样,逻辑、思维不容易断。

比如:

var fetchMaxNumber = func( ) int {

    var maxNumber int
    var minNumber int
    ...
    ...
    ...
    do...
    
    return maxNumber
}
var fetchMaxNumber = func()int{
    ...
    ...
    var (
        maxNumber int
        minNUmber int
    )
    
    do...
    return maxNumber
}

第二种变量的组织方式优于第一种,而且更利于思维。像第一种,读到真正的处理逻辑,还需要回过头去看下变量的声明,给思维造成了额外的认知负担,尤其你还喜欢写大段代码的函数。

一个准则:全局变量的个数需要尽可能少,如果有可能,使用常量替代。局部变量最好在需要使用变量的地方进行申明。

好,那么我们的目的便是尽可能的减少变量。

如何减少?

  • 减少没有价值的变量,甚至是没有价值的代码
  • 减少控制流变量(经常会使用一个诸如 Flag 的变量等来进行控制流的判断,其实完成可以省略,仅靠调整语句遍可实现)
  • 缩小变量的作用域:全局变量多处使用,赋值之类的可能变更变量,在函数内的变量作用域有限,不影响外部变量

4. 重新组织代码,持续迭代

软件架构有一种很流行的设计方法,叫:领域驱动设计,对持续迭代的微服务有很大的帮助。该领域驱动方法将项目划分为4个层级。

  • 领域层:即领域内操作的集合
  • 基础设施层:即辅助服务操作的集合
  • 用户界面层:即用户层
  • 应用层

其中谈到领域,和我们之前变量的命名建议使用专业的词、领域内的词不谋而合。

同时,基础设施层是将一些辅助性的任务集合。比如文件处理、比如网络请求处理、比如字符串处理等

组织代码节也提倡这么做。

实现核心的业务需求时,尽量将这些工具类的功能规整在独立的基础设施里,专注于实现核心的业务。

代码的组织,一个是项目的组织,一个良好的项目组织方式,一定程度上能体现代码的逻辑性。

另外一个比较重要的是函数的组织。

有下面几条准则:

  • 不相干的任务,提取出来
  • 一次只专注干一件事
  • 梳理逻辑时,如果你能使用自然语言表述出来,对你写出逻辑清晰的代码很有帮助
  • 单函数行数不宜过长 30 ~ 50 为佳。再一个评判方法是,查看函数的内容无需滚动鼠标进行翻页。
  • 少些代码:每写一行都需要维护;不需要的功能,砍掉,不需要的代码,删掉

全文完,我是谢伟,再会。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK