law
一: 一切都与复杂度有关 二: 代码应当易于理解
对人:
"好程序员”应当竭尽全力, 把程序写得让其他程序员(以及以后的自己)容易理解.
对代码:
代码被阅读的次数远多于编写和修改的次数
E = mc2 (Error = more codes)
对项目:
公式: 可行性=(当前价值+未来价值)/(实现成本+维护成本). 即相比降低实现成本, 降低维护成本更加重要
基础: 风格
团队成员遵守统一的风格, 保持风格的一致性, 减少理解难度
遵循基础的编码风格:
请仔细阅读, 使用对应编辑器插件工具协助检查
遵循 pep8 风格 利用pep8工具(编辑器相关插件)来解决这个问题, 在review之前处理. 以避免在review过程中出现此类问题.
遵循 Google Code Style / 中文版
不要吝啬空行, 把相关的代码行分组, 形成代码块. 声明按块组织起来, 并且把代码分成”段落”(按步骤/顺序/逻辑结构分), 排版合理
每行只写一个语句, 每行只声明一个变量
注释
注释应该有很高的价值(传递信息/空间占用)
代码本身应该尽力做到自说明
注释, 记录了在写代码过程中的思考, 保持紧凑, 简单准确的描述
不要使用尾注释. 容易被整行拷贝/不容易被编辑修改/逐渐腐烂
x = 1 # bad comment
# good comment
x = 1
不需要的代码, 维护到版本库后(写明commit info), 然后删除. 不要注释起来
不要给不好的命名加注释, 应该去修改命名
不要给那些从代码本身就能快速推断出来的事实写注释.(不要为了注释而注释)
对于复杂的计算逻辑, 要给出注释, 可以通过列举例子, 简单的输入输出来描述
对于大段的逻辑或模块, 需要给总结性注释
注释代码时, 应注重-为何做, 而不是-怎么做
每行注释前用一个空行分开. 注释缩进要和相应代码一致
命名
把信息装入名字中.(自说明)
尽量短, 但是要包含足够的信息(刨掉其中毫无意义的词)
命名一定要有意义, 尽量少使用单个字符作为命名, 除非短表达式(列表解析/lambda等)以及小的作用域范围
常量大写, 变量小写, 类名驼峰, 函数名小写加下划线, 不要混用下划线和驼峰.
不要使用关键字命名, 例如type 和 dir
避免使用容易混淆的命名, 防歧义
慎用首字母缩略词和缩写, 除非团队成员都理解(不要妄图用注释来解决这个问题, 即, 不要注释不好的命名)
不要使用大小写来区分不同对象
同一个变量, 在多个地方, 前后端/数据库/不同函数/请求等, 尽量保持命名一致性
不要害怕过长的命名, 保证易于理解和阅读(现代编辑器可以搞定自动补全和批量变更的问题)
使用具体的名字, 而不是泛化的名字, 例如params/args等, 没有隐含任何信息
bool类型, 除非名字本身有True/False的含义, 否则建议统一使用is_前缀
不要使用双重否定的命名: is_not_pass
for a in b, 注意 a 和 b 的单复数区分
常量
常量大写
作用于同一个模块/逻辑的多个常量, 建议使用统一的前缀
将常量统一组织到某个文件/某几个文件, 并写明注释.
函数/循环中的正则, 请预先compile, 放入变量中.
善用Enum, 对可读性提升很大
同一个枚举变量中, 其包含类型应当一致
变量
减少变量: 变量越多, 越难全部追踪其动向. a.减少没有价值的中间变量 b.减少中间结果(可以通过提前返回来消除) c.减少控制流变量
缩小变量作用域: 避免全局变量(命名空间污染). 需要做到让你的变量对尽量少的代码行可见.
变量定义尽量靠近其使用的地方, 或者, 在使用时定义.
不要使用import *, 会出现各种突如其来的变量名, 可能导致名字空间污染, 造成诡异问题
数据结构
dict, 不要使用for key in d.keys(), 直接使用for key in d
表达式
原则: 保持简短, 易懂.(拆分超长表达式)
抽取反复出现的长表达式到变量或者函数调用
使用解释变量, 将超长表达式中的自表达式抽取城一个解释变量.(抽取, 然后使用变量, 而不是每次都重复表达式)
总结变量: 一个表达式不需要解释, 但是装入一个新的变量中仍然有用. 短名字替代一大块代码. 例如: numbers[0]['obj'].name
使用摩根定理: not a and not b to not (a or b)
删除公共子表达式:如果发现某个表达式老是在你面前出现,就把它赋值给一个变量
中文, 请统一使用u"中文"
表达式中避免使用魔数, 使用常量/枚举替代之
控制流: 分支
if/else顺序: a. 先处理正逻辑而不是负逻辑. b. 先处理掉简单的情况, 还能保证if/else在同一个屏幕内都可见(否则到了else需要回头查) c.先处理有趣或可疑的逻辑
return early, 从函数中提前返回. 使用guard clause来实现. 某些情况返回后, 将不必要思考某个分支出口, 剩余注意力集中在为数不多的情况. 另一个好处是, 能有效减少代码缩进.
减少嵌套: 嵌套很深的代码很难理解, 每个嵌套层次会在读者’思维栈’上又增加了一个条件. 使用return early来减少嵌套. 而循环中的减少嵌套方式, 可以使用if condition: continue/break来进行提早返回.
减少嵌套: 当你对代码进行改动的时候, 从全新的角度审视它, 把它作为一个整体来看待.只关心局部, 不敢动旧有代码, 很容易一层层逻辑嵌套往里加导致深层嵌套
使用is来判定是否是None, 而不是==
条件语句中参数顺序: 左侧变量, 右侧字面值/常量
默认情况都使用if...else, 三目运算只有在最简单的情况下才使用
if condition: return 则不需要else
注意if/else的多层嵌套, 在某些情况下, 判断条件中恒真/恒假的情况
控制流: 循环
善用enumerate而不是维护index变量( enumerate 还可以从1开始计数)
除非必要(逻辑确实如此且带break), 否则不要使用for...else.(增加理解成本)
不要使用for _ in l: _.x, 可读性太差
减少循环内的if...else...嵌套层次, 可以使用if condition: continue
控制流: 异常处理
异常日志同注释, 应该有很高的价值(传递信息/空间占用)
不要把所有代码放到try except中, 只捕获会出异常的代码片段. 注意粒度, 不要放入不必要的代码
不要吞掉异常, 处理或抛出, 同时要打日志(使用logging而不是print打日志)
谨慎使用except Exception捕获所有异常.
不要在finally语句中使用return进行返回, 有坑.
异常的错误信息要有用, 即足够明确, 对问题排查有帮助.
不要使用异常控制程序的流程. 滥用异常, 异常不应该处理正常逻辑
不要滥用异常, 底层被调用函数早已try...except处理了, 调用方不需要再次处理
函数
函数不要太大, 嵌套不要太深
参数命名的一致性: 多个参数, 选择一个有意义的顺序, 并始终一致地使用它(可读性更好, 更容易发现问题)
不要使用可变对象作为函数默认参数的值
一次只做一件事, 注意函数大小, 注意抽象/拆分
抽取不相关的子问题到独立的函数中, 例如纯工具代码, 通用代码, 项目专属代码
抽取反复出现重复的代码到独立函数中
return 值不要使用0/1来代表True/False
同一个函数可能存在多个return, 返回值要保持一致(个数/类型)
return early, 减少阅读代码时的逻辑堆积, 减少贯穿函数始终用于最终判断return的变量数量. 超过3个就变得有些难以维护了, 阅读过程中确定其值有困难
如果函数调用链中, 参数或者return的值反复出现pack/unpack, 可以考虑用dict封装来进行传递.
如果发现每次调用一个函数后, 还需要对返回值进行二次处理, 则是函数封装得不够导致的. 需重构函数, 将处理加进去. (防止某次调用忘了二次处理导致的bug)
类
不要使用type进行类型检查, 用isinstance
使用新式类, 驼峰命名
假设类的某个属性, 每次取出来都需要进行处理(格式化, 转换等, 例如日期格式), 使用property封装这层处理, 同时处理异常情况.
模块
import顺序: 标准库/第三方库/本项目, 之间使用空行隔开
多行import, 请使用from a import (b, c, d)而不是\来进行换行
不要使用from A import *
当相对独立的多个逻辑代码混杂放在一起, 或者发现constant文件超大包含了大量不同逻辑的产量, 可以考虑模块切分
抽象
一定不要机械地复制粘贴代码(会出现大量的重复代码), 应该从全局考虑是否可以抽象
多个函数之间, 如果仅有一两行代码不同, 则可以进行抽象提取
当一段相似的代码出现两次以上, 需要考虑封装(注意粒度)
设计
软件设计三大误区: 1.编写不必要的代码 2.代码难以修改 3.过分追求通用
思考足够充分, 减少过度设计
其他
熟悉标准库, 减少土制轮子的概率, 可以少写代码
熟悉框架/优秀第三方库提供的接口及特性, 原因同上
项目发布前, 移除所有print语句
文件/函数是否写明作者信息? 不, 版本记录中有作者信息. 容易形成领地, 他人不敢修改/不敢大改, 容易造成代码腐烂. 占用空间且没啥用.
参考书目
编写可读代码的艺术
简约之美—软件设计之道
编写高质量代码—改善Python程序的91个建议