大家好,我叫谢伟,是一名程序员。
上节从编程语言特性的角度讲述了编写可读代码的几个要点。
本节接着从编程语言的语言特性:流程控制和循环等角度,再次谈谈编写可读代码的要点。
还记得吗,编写可读代码的核心的要点是什么?
写易于理解的代码
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;
- 如果是,搜索数据库,看数据是否是有此字段,存在则获取shopUUID
- 否则 产生 shopUUID
- 如果根本不是 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 为佳。再一个评判方法是,查看函数的内容无需滚动鼠标进行翻页。
- 少些代码:每写一行都需要维护;不需要的功能,砍掉,不需要的代码,删掉
全文完,我是谢伟,再会。