本文是《提升你的 Python 项目代码健壮性和性能》系列的第 6 篇文章。
本系列总计 8 篇文章目录
- 用 Type Annotation 提升你的 Python 代码健壮性
- 如何通过测试提升 Python 代码的健壮性
- 如何保证 Django 项目的数据一致性
- 这几招,让你快速提升 Python 项目的性能
- 为你的项目快速搭建 ELKFA 日志系统
- 如何写出整洁的 Python 代码 上
- 如何写出整洁的 Python 代码 中
- 如何写出整洁的 Python 代码 下
接下来的三篇,围绕另一个主题
如何写出整洁的代码
『整洁』三篇是基于**『代码整洁之道』和『架构整洁之道』**的一些切身的理解和体会。
感谢这两本书的作者 Bob 大叔。
PPS: 某东读书 VIP 会员有不少 IT 资源类的书籍可以免费看,比如『代码整洁之道』
0x00 前言
软件系统的腐败之路
随着项目代码行数的增加,不可避免的遇到软件架构腐败的问题。
具体表现为:随着每一次产品版本的发布,对现有流程进行优化和修改就格外的费事和吃力。工程师的生产力就开始直线下降。
所谓
眼看他起朱楼,眼看他宴宾客,眼看他楼塌了。清 孔尚任《桃花扇》
0x01 讨论
为什么会出现腐败的系统
原因可能是多方面的,比如常见的场景:
步骤 1. 领域建模的人对业务里概念的理解不到位,流程不深入了解。
步骤 2. 工程师在实现的时候,按照自己的理解,没有梳理整个流程。就开始动手实现。并且全程人肉测试。
步骤 3. 需求变动,流程更改。
第一步容易埋下坑点:
- 对『该领域』理解的不到位,导致『流程』就不清晰,也导致原型设计等同于 Axure 画的『表单』。
- 『关键概念』没有解释,『关键字段』没有解释,也没有『流程图』,也没有关于业务主体『状态图』,前后端面向表单开发。(Form Oriented Programming)
接着,领域理解不到位就会带来另一个问题。
- 对于需要『建模的实施者』一般是后端工程师,将花费比较多的时间来梳理流程。
- 前后端代码结构不清晰。比如,前端页面的路由命名不清晰,Page 组件命名不清晰,请求 API 接口不清晰。比如,后端路由命名不清晰,view func 不清晰,serializer 不清晰,table 命名不清晰。
经验老道的程序员会通过一些手段,比如让这些命名不清晰的东西统一一下,然后等概念清晰了。再改回来。
第二步容易埋下坑点:
- 全程口头对需求,『没有文档』落下来,产品之间和开发之间**『缺乏共通的文档理解指南』**。
当更改已有流程或者是出问题的时候,除了一脸懵逼就还剩下甩锅了。
第三步容易继续在坑里埋坑:
资本市场里,树欲静而风不止,想重构而时不我待。
- 领域建模的产品经理会继续叠加新的功能,至于是否已经牵扯到了已有功能,最多和工程师口头说明一下,是不会考虑落实到具体文档里面的。
- 工程师需要不断的去满足需求,而疯狂叠加代码。不但要搬砖,而且要快速的把方螺丝强行拧到圆螺母里面。
最后,
屠龙少年变成了恶龙,而产品和工程师们经过不懈努力,终于堆出了『代码屎山』-- 腐败系统
衡量系统的两个指标
腐败系统有一句成语可以概括,叫做金玉其外,败絮其中。
什么是外,什么是中?
- 所谓外,就是软件的行为价值 -- 软件系统的行为是否正常运行,是否满足需求
- 所谓内,就是软件的架构价值 -- 软件系统的架构是否清晰,是否灵活,是否可维护
看起来有点抽象,打个比方就清晰多了。
所谓软件系统,可以比作是人。
- 所谓外,就是人的能力 -- 即革命的本事。
- 所谓内,就是人的健康 -- 即革命的本钱。
身体是革命的本钱,所以,要经常锻炼身体。同理可知系统也是如此。
业务方和管理层仅在意软件系统的外在。
优秀的软件工程师,做的就是平衡这两者。必要的时候,需要和业务方以及管理层进行沟通。
代码是写出来的吗?
不一定。
假如你是做业务逻辑的。
首先,好代码可能是聊出来的。
比如需求确认这一块,多问多画流程图少动手。就可以减少后期很多麻烦事情。 如果在没有理解透需求的情况下动了手,就会做得越多,错的越多。我相信很多工程师都有 这种感觉。
当然,这是基本上不怎么可控的外部条件。所以,不在本文的讨论范围之内。
其次,好代码可能是边读边写出来的。
回顾一下一天的工作,你会发现,不管是,你写文章,或者是做一些其他的东西。
- 读代码,大部分都是跳转代码,文件内跳转,文件外跳转,分屏浏览。
- 在这个过程中不断整理和梳理原有的概念。最后落实到代码上。
- 代码的直接修改。占到你很少的时间。
最后,好代码是改出来的。
好代码的标准
我们只讨论如何保持代码的清晰整洁
什么是好的代码?
引用 C++ 创始人的说法,我喜欢优雅而高效的代码,
- 代码逻辑应该直截了当,叫缺陷难以隐藏;
- 尽量减少依赖关系,使之便于维护;
- 依据某种分层战略完善错误处理代码;
- 性能调至最优,省的引诱别人做没规矩的优化。
- 整洁的代码只做好一件事情。
其实还有一些保证代码整洁的手段:
比如本系列的文章讲解
0x02 变量命名之道
名不正,则言不顺;言不顺,则事不成。事不成,则礼乐不兴;
写代码就是这样。
使用业务领域命名
对于具体业务而言,**起不好名字有一半的锅都是产品经理的锅,因为这是概念认知不清晰的表现。**不要小看这一点,产品经理对项目的理解不到位会在编写代码的过程中被放大。
遇到这种情况,务必要把产品经理拉过来认认真真的扯扯犊子。
代码命名
对于实际代码而言,
- 涉及到具体业务名称,起不好名字就是程序员的锅,因为这是跟着产品经理犯傻,继续坚持概念认知不清晰的表现。
- 涉及到非业务的名称,务必要统一,比如 Button 就是 Button, 如果团队统一叫做 Btn 那就统一叫做 Btn.
务必不要用单字母命名,取名字要准确,如无必要,也尽量规避掉缩写。
# 差
d = Column # 消逝的时间
# 好
elapsedTimeInDays
daysSinceCreation
daysSinceModification
fileAgeInDays
- 一个单词一个意思,假设你需要往 group 里面添加 member, 那么出现 add/insert/append 三个词的意义就应该完全不一致。
不要做无意义的区分
fetchAccountList 就不如 fetchAccounts
fetchAccountRecord / fetchAccountInfo / fetchAccountData 就不如 fetchAccount
变量是名词,路由是名词,方法是动宾结构。
即便是名字起的不好,也应该很统一。
0x03 函数整洁之道
短小精悍
每个函数都要做到短小精悍
- 短小
- 只做一件事(无副作用)
- 一个函数一个抽象层级
调用层次清晰
不同抽象层级
def 日常 ():
用 XX 点外卖 ()
去厨房拿筷子 ()
等小哥送来外卖 ()
睡觉 ()
打豆豆 ()
为什么是不同抽象层级呢?点外卖不一定是日常。可能自己偶尔也会做饭。
并且,从业务角度来说,吃饭,睡觉,打豆豆比较符合业务人员的认知。
同一抽象层级
def 点外卖吃饭 ():
用 XX 点外卖 ()
去厨房拿筷子 ()
等小哥送来外卖 ()
def 吃饭 ()
if 今天比较懒:
点外卖吃饭 ()
def 日常 ():
吃饭 ()
睡觉 ()
打豆豆 ()
这样写出来的函数,比较符合自顶向下的阅读方式。
控制参数
参数尽量不要超过三个。
因为超过三个之后,就很难用几个单词把这个方法的意义给概括出来。
如果超过三个,这就需要谨慎的考虑将其中的一部分参数是封装为结构体或者是类。
如何写函数
- 不要为了炫技而炫技。
- 先写对函数。
- 然后打磨函数,即修改名称,消除重复,适当重构
0x04 注释整洁之道
- 注释不能美化糟糕的代码。
- 适当解释意图。有的时候 hardcode 却是最好的解决方案。
- 能用变量名 / 方法名 / 函数名表达清楚的,就不要讲废话。
0x05 格式整洁之道
排版整齐
代码一定要排版整齐
- 缩进
- 每行最大字符串数?
- 字符串选择单引号还是双引号?
- 团队协作 diff 同一段代码的时候还是 diff 出来不同的效果。
反正是我记不住 pep8 里面要求的那些标准的。当然,flake8 为你提供了比较好的 lint 标准。
在这里推荐两个工具:
- flake8
- black https://github.com/ambv/black
至于 black, 是类似于 javascript 圈里面的 prettier 的存在。
在每次 commit 的时候,执行 flake8 和 black, 让你的代码整整齐齐。
上下文相关
- 如果我使用了一个方法,如果不是从其他文件里使用,一般会在我编写的代码块的上方不远处。
- 如果我使用了一个变量,应该只在起到作用的作用域里。
- 如果我针对一个概念编写方法,那么在这个文件里,相关概念的代码应该是放在一起的。
0xEE 参考连接
- 《代码整洁之道》
- 《架构整洁之道》