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

字数 1797阅读 142
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 为佳。再一个评判方法是,查看函数的内容无需滚动鼠标进行翻页。
  • 少些代码:每写一行都需要维护;不需要的功能,砍掉,不需要的代码,删掉

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

推荐阅读更多精彩内容