何为”更好”的代码本身就是仁者见仁,我们在这里主观地选择一个评价标准:
代码要满足安全性 ,可用性 ,可维护性 ,简洁性 , 高性能的要求,这几项的重要性递减。
毫无疑问编写代码本身是一件很难的事,但是在遵循一些指导原则的话,我们可以相对编写出更好的代码。
命名
有一句不太可考的名言是”计算机科学只存在两个难题:缓存失效和命名”。不是每个程序员都要经常编写用到各种缓存机制的代码,但是我们每天都在不断地命名,好的命名需要遵从一些规则:
名副其实
我们要选择有意义的命名,date
比d
要好,index
比i
要好,list1
和list2
并没有比l1
和l2
更容易理解,users
和items
是更好的选择。
做有意义的区分
以不同的数字后缀命名是依义命名的对立面,如果一个转账函数的签名是def transfer(a1: Acccount, a2: Account, n: int)
,我们很难确认到底是谁转账给谁,def transfer(source: Account, destimation: Account, quantity: int)
就会明确很多。
废话都是冗余,我们并不需要很多的类型后缀,nameString
和nameStr
是完全没有必要的,没有name
会是数字或者数组的。account
,accountInfo
,accountData
之间的区别也不是那么的明显。
使用读的出来的名称
我曾经工作过的一家公司的数据库表的命名规范是对应的中文名称全拼的首字母缩写,例如”登录记录”表的名称是dljl
,这样的表是如此得多以至于要有一个文档来维护每个表对应的数据的信息。如果我是技术主管的话,大概会选login
这样的名字。
类名和方法名
类名应该是名词或者名词短语,例如Customer
,UserAddress
,方法名应该是动词或者动词短语,例如save
,insertPage
。
函数
下面是一个创建活动的函数:
代码语言:javascript复制def create_activity(user: User, act_dict: dict):
if act_dict['time_start'] < datetime.now():
raise ActTimeError()
if act_dict['time_end'] - act_dict['time_start'] > timedelta(days=30):
raise ActTimeError()
for award in act_dict['awards']:
if award['type'] == 0:
ret = award_api.create_a_award(user.session, award)
elif award['type'] == 1:
ret = award_api.create_b_award(user.session, award)
elif award['type'] == 2:
ret = award_api.create_c_award(user.session, award)
else:
raise BadAwardsTypeError()
award['aid']= ret['id']
activity = activity_repo.create_activity(act_dict)
return activity
然后是对这个函数的一些小的重构。
代码语言:javascript复制def create_activity(user: User, act_dict: dict):
check(act_dict)
create_awards(user,act_dict['awards'])
activity = activity_repo.create_activity(act_dict)
return activity
def check(act_dict: dict):
if act_dict['time_start'] < datetime.now():
raise ActTimeError()
if act_dict['time_end'] - act_dict['time_start'] > timedelta(days=30):
raise ActTimeError()
def create_awards(user: User, awards: dict):
for award in act_dict['awards']:
create_award(user, award)
def create_award(user: User, award: dict):
award_creater = [award_api.create_a_award, award_api.create_b_award, award_api.create_c_award]
if award['type'] < len(award_creater):
ret = award_api.create_a_award(user.session, award)
award['aid']= ret['id']
else:
raise BadAwardsTypeError()
短小
函数应该足够短,一般不要超过十几行。每个代码块(if
,while
,for
)中的代码也不宜很多,最好只是一条函数调用(有一个有意义的名称)。这也意味着函数的嵌套结构不要太复杂。
只做一件事
一般来说很长的函数也做了很多的事。一个有效的判断方式是能够再拆出一个函数,例如将校验参数的部分提取到专门的函数中。
switch/if 语句
消除过多 switch/if 语句的一个方法是使用抽象工厂,在动态类型语言中可以使用字典映射。
函数参数
- 最理想的参数数量是零。
- 确实需要很多参数的时候应该封装为参数对象。
- 慎用只有一个布尔类型参数的函数。
副作用
副作用可能是不可避免的,但是要使用合适的名称。例如init
,create
之类的名字。
命令查询分离 CQS
函数要么做什么事,要么回答什么事。函数应该修改某对象的状态,或者返回对象的有关信息,两样都干会产生混乱。
DRY
不要重复你自己的行为。
注释
好的代码描述自身的意图,当我们很难做到这一点时,才需要添加注释。 注释最大的问题是和代码相比,注释是缺乏维护的。 注释不能美化糟糕的代码
好注释
- 法律信息
- 提供信息的注释(例如给一条正则表达式注明它匹配到的内容)
- 对意图的解释
- 警示
- todo
坏注释
- 多余的注释
- 误导性注释
- 注释掉代码
格式
- 水平方向:行宽,空格
- 垂直方向
- 使用标准化工具和团队规范。
对象
SOLID 原则(单一职责原则,开放关闭原则,里氏替换原则,接口分离原则,依赖倒置原则)