本文是《提升你的 Python 项目代码健壮性和性能》系列的第六篇文章。

接下来的三篇,围绕另一个主题

如何写出整洁的代码

『整洁』三篇是基于**『代码整洁之道』和『架构整洁之道』**的一些切身的理解和体会。

感谢这两本书的作者 Bob 大叔。

PPS: 某东读书 VIP 会员有不少 IT 资源类的书籍可以免费看,比如『代码整洁之道』

# 0x00 前言

# 软件系统的腐败之路

随着项目代码行数的增加,不可避免的遇到软件架构腐败的问题。

具体表现为:随着每一次产品版本的发布,对现有流程进行优化和修改就格外的费事和吃力。工程师的生产力就开始直线下降。

所谓

眼看他起朱楼,眼看他宴宾客,眼看他楼塌了。 清 孔尚任《桃花扇》

# 0x01 讨论

# 为什么会出现腐败的系统

原因可能是多方面的,比如常见的场景:

步骤 1. 领域建模的人对业务里概念的理解不到位,流程不深入了解。
步骤 2. 工程师在实现的时候,按照自己的理解,没有梳理整个流程。就开始动手实现。并且全程人肉测试。
步骤 3. 需求变动,流程更改。

第一步容易埋下坑点:

  1. 对『该领域』理解的不到位,导致『流程』就不清晰,也导致原型设计等同于 Axure 画的『表单』。
  2. 『关键概念』没有解释,『关键字段』没有解释,也没有『流程图』,也没有关于业务主体『状态图』,前后端面向表单开发。(Form Oriented Programming)

接着,领域理解不到位就会带来另一个问题。

  1. 对于需要『建模的实施者』一般是后端工程师,将花费比较多的时间来梳理流程。
  2. 前后端代码结构不清晰。比如,前端页面的路由命名不清晰,Page 组件命名不清晰,请求 API 接口不清晰。比如,后端路由命名不清晰,view func 不清晰,serializer 不清晰,table 命名不清晰。

经验老道的程序员会通过一些手段,比如让这些命名不清晰的东西统一一下,然后等概念清晰了。再改回来。

第二步容易埋下坑点:

  1. 全程口头对需求,『没有文档』落下来,产品之间和开发之间**『缺乏共通的文档理解指南』**。

当更改已有流程或者是出问题的时候,除了一脸懵逼就还剩下甩锅了。

第三步容易继续在坑里埋坑:

资本市场里,树欲静而风不止,想重构而时不我待。

  1. 领域建模的产品经理会继续叠加新的功能,至于是否已经牵扯到了已有功能,最多和工程师口头说明一下,是不会考虑落实到具体文档里面的。
  2. 工程师需要不断的去满足需求,而疯狂叠加代码。不但要搬砖,而且要快速的把方螺丝强行拧到圆螺母里面。

最后,

屠龙少年变成了恶龙,而产品和工程师们经过不懈努力,终于堆出了『代码屎山』-- 腐败系统

# 衡量系统的两个指标

腐败系统有一句成语可以概括,叫做金玉其外,败絮其中。

什么是外,什么是中?

  • 所谓外,就是软件的行为价值 – 软件系统的行为是否正常运行,是否满足需求
  • 所谓内,就是软件的架构价值 – 软件系统的架构是否清晰,是否灵活,是否可维护

看起来有点抽象,打个比方就清晰多了。

所谓软件系统,可以比作是人。

  • 所谓外,就是人的能力 – 即革命的本事。
  • 所谓内,就是人的健康 – 即革命的本钱。

身体是革命的本钱,所以,要经常锻炼身体。同理可知系统也是如此。

业务方和管理层仅在意软件系统的外在。

优秀的软件工程师,做的就是平衡这两者。必要的时候,需要和业务方以及管理层进行沟通。

# 代码是写出来的吗?

不一定。

假如你是做业务逻辑的。

首先,好代码可能是聊出来的。

比如需求确认这一块,多问多画流程图少动手。就可以减少后期很多麻烦事情。
如果在没有理解透需求的情况下动了手,就会做得越多,错的越多。我相信很多工程师都有
这种感觉。

当然,这是基本上不怎么可控的外部条件。所以,不在本文的讨论范围之内。

其次,好代码可能是边读边写出来的。

回顾一下一天的工作,你会发现,不管是,你写文章,或者是做一些其他的东西。

  1. 读代码,大部分都是跳转代码,文件内跳转,文件外跳转,分屏浏览。
  2. 在这个过程中不断整理和梳理原有的概念。最后落实到代码上。
  3. 代码的直接修改。占到你很少的时间。

最后,好代码是改出来的。

# 好代码的标准

我们只讨论如何保持代码的清晰整洁

什么是好的代码?

引用 C++ 创始人的说法,我喜欢优雅而高效的代码,

  1. 代码逻辑应该直截了当,叫缺陷难以隐藏;
  2. 尽量减少依赖关系,使之便于维护;
  3. 依据某种分层战略完善错误处理代码;
  4. 性能调至最优,省的引诱别人做没规矩的优化。
  5. 整洁的代码只做好一件事情。

其实还有一些保证代码整洁的手段:

比如本系列的文章讲解

# 0x02 变量命名之道

名不正,则言不顺;言不顺,则事不成。事不成,则礼乐不兴;

写代码就是这样。

# 使用业务领域命名

对于具体业务而言,**起不好名字有一半的锅都是产品经理的锅,因为这是概念认知不清晰的表现。**不要小看这一点,产品经理对项目的理解不到位会在编写代码的过程中被放大。

遇到这种情况,务必要把产品经理拉过来认认真真的扯扯犊子。

# 代码命名

对于实际代码而言,

  • 涉及到具体业务名称,起不好名字就是程序员的锅,因为这是跟着产品经理犯傻,继续坚持概念认知不清晰的表现。
  • 涉及到非业务的名称,务必要统一,比如 Button 就是 Button, 如果团队统一叫做 Btn 那就统一叫做 Btn.

务必不要用单字母命名,取名字要准确,如无必要,也尽量规避掉缩写。

# 差
d = Column # 消逝的时间

# 好
elapsedTimeInDays
daysSinceCreation
daysSinceModification
fileAgeInDays
  • 一个单词一个意思,假设你需要往 group 里面添加 member, 那么出现 add/insert/append 三个词的意义就应该完全不一致。

不要做无意义的区分

fetchAccountList 就不如 fetchAccounts
fetchAccountRecord / fetchAccountInfo / fetchAccountData 就不如 fetchAccount

变量是名词,路由是名词,方法是动宾结构。

即便是名字起的不好,也应该很统一。

# 0x03 函数整洁之道

# 短小精悍

每个函数都要做到短小精悍

  1. 短小
  2. 只做一件事(无副作用)
  3. 一个函数一个抽象层级

# 调用层次清晰

不同抽象层级

def 日常 ():XX 点外卖 ()
	去厨房拿筷子 ()
	等小哥送来外卖 ()

	睡觉 ()
	打豆豆 ()

为什么是不同抽象层级呢? 点外卖不一定是日常。可能自己偶尔也会做饭。

并且,从业务角度来说,吃饭,睡觉,打豆豆比较符合业务人员的认知。

同一抽象层级

def 点外卖吃饭 ():XX 点外卖 ()
	去厨房拿筷子 ()
	等小哥送来外卖 ()

def 吃饭 ()
	if 今天比较懒:
		点外卖吃饭 ()

def 日常 ():
	吃饭 ()
	睡觉 ()
	打豆豆 ()

这样写出来的函数,比较符合自顶向下的阅读方式。

# 控制参数

参数尽量不要超过三个。

因为超过三个之后,就很难用几个单词把这个方法的意义给概括出来。

如果超过三个,这就需要谨慎的考虑将其中的一部分参数是封装为结构体或者是类。

# 如何写函数

  1. 不要为了炫技而炫技。
  2. 先写对函数。
  3. 然后打磨函数,即修改名称,消除重复,适当重构

# 0x04 注释整洁之道

  1. 注释不能美化糟糕的代码。
  2. 适当解释意图。有的时候 hardcode 却是最好的解决方案。
  3. 能用变量名 / 方法名 / 函数名表达清楚的,就不要讲废话。

# 0x05 格式整洁之道

# 排版整齐

代码一定要排版整齐

  • 缩进
  • 每行最大字符串数?
  • 字符串选择单引号还是双引号?
  • 团队协作 diff 同一段代码的时候还是 diff 出来不同的效果。

反正是我记不住 pep8 里面要求的那些标准的。当然,flake8 为你提供了比较好的 lint 标准。

在这里推荐两个工具:

  1. flake8
  2. black https://github.com/ambv/black

至于 black, 是类似于 javascript 圈里面的 prettier 的存在。

在每次 commit 的时候,执行 flake8 和 black, 让你的代码整整齐齐。

# 上下文相关

  1. 如果我使用了一个方法,如果不是从其他文件里使用,一般会在我编写的代码块的上方不远处。
  2. 如果我使用了一个变量,应该只在起到作用的作用域里。
  3. 如果我针对一个概念编写方法,那么在这个文件里,相关概念的代码应该是放在一起的。

# 0xEE 参考连接

  • 《代码整洁之道》
  • 《架构整洁之道》