原文链接:本着什么原则,才能写出优秀的代码?
作为一名程序员,最不爱干的事情,除了开会之外,可能就是看别人的代码。
有的时候,新接手一个项目,打开代码一看,要不是身体好的话,可能直接气到晕厥。
风格各异,没有注释,甚至连最基本的格式缩进都做不到。这些代码存在的意义,可能就是为了证明一句话:又不是不能跑。
在这个时候,大部分程序员的想法是:这烂代码真是不想改,还不如直接重写。
但有的时候,我们看一些著名的开源项目时,又会感叹,代码写的真好,优雅。为什么好呢?又有点说不出来,总之就是好。
那么,这篇文章就试图分析一下好代码都有哪些特点,以及本着什么原则,才能写出优秀的代码。
初级阶段
先说说比较基本的原则,只要是程序员,不管是高级还是初级,都会考虑到的。
这只是列举了一部分,还有很多,我挑选四项简单举例说明一下。
- 格式统一
- 命名规范
- 注释清晰
- 避免重复代码
以下用 Python 代码分别举例说明:
格式统一
格式统一包括很多方面,比如 import
语句,需要按照如下顺序编写:
- Python 标准库模块
- Python 第三方模块
- 应用程序自定义模块
然后每部分间用空行分隔。
import os import sys import msgpack import zmq import foo 复制代码
再比如,要添加适当的空格,像下面这段代码;
i=i+1 submitted +=1 x = x*2 - 1 hypot2 = x*x + y*y c = (a+b) * (a-b) 复制代码
代码都紧凑在一起了,很影响阅读。
i = i + 1 submitted += 1 x = x * 2 - 1 hypot2 = x * x + y * y c = (a + b) * (a - b) 复制代码
添加空格之后,立刻感觉清晰了很多。
还有就是像 Python 的缩进,其他语言的大括号位置,是放在行尾,还是另起新行,都需要保证统一的风格。
有了统一的风格,会让代码看起来更加整洁。
命名规范
好的命名是不需要注释的,只要看一眼命名,就能知道变量或者函数的作用。
比如下面这段代码:
a = 'zhangsan' b = 0 复制代码
a
可能还能猜到,但当代码量大的时候,如果满屏都是 a
,b
,c
,d
,那还不得原地爆炸。
把变量名稍微改一下,就会使语义更加清晰:
username = 'zhangsan' count = 0 复制代码
还有就是命名要风格统一。如果用驼峰就都用驼峰,用下划线就都用下划线,不要有的用驼峰,有点用下划线,看起来非常分裂。
注释清晰
看别人代码的时候,最大的愿望就是注释清晰,但在自己写代码时,却从来不写。
但注释也不是越多越好,我总结了以下几点:
- 注释不限于中文或英文,但最好不要中英文混用
- 注释要言简意赅,一两句话把功能说清楚
- 能写文档注释应该尽量写文档注释
- 比较重要的代码段,可以用双等号分隔开,突出其重要性
举个例子:
# ===================================== # 非常重要的函数,一定谨慎使用 !!! # ===================================== def func(arg1, arg2): """在这里写函数的一句话总结(如: 计算平均值). 这里是具体描述. 参数 ---------- arg1 : int arg1的具体描述 arg2 : int arg2的具体描述 返回值 ------- int 返回值的具体描述 参看 -------- otherfunc : 其它关联函数等... 示例 -------- 示例使用doctest格式, 在`>>>`后的代码可以被文档测试工具作为测试用例自动运行 >>> a=[1,2,3] >>> print [x + 3 for x in a] [4, 5, 6] """ 复制代码
避免重复代码
随着项目规模变大,开发人员增多,代码量肯定也会增加,避免不了的会出现很多重复代码,这些代码实现的功能是相同的。
虽然不影响项目运行,但重复代码的危害是很大的。最直接的影响就是,出现一个问题,要改很多处代码,一旦漏掉一处,就会引发 BUG。
比如下面这段代码:
import time def funA(): start = time.time() for i in range(1000000): pass end = time.time() print("funA cost time = %f s" % (end-start)) def funB(): start = time.time() for i in range(2000000): pass end = time.time() print("funB cost time = %f s" % (end-start)) if __name__ == '__main__': funA() funB() 复制代码
funA()
和 funB()
中都有输出函数运行时间的代码,那么就适合将这些重复代码抽象出来。
比如写一个装饰器:
def warps(): def warp(func): def _warp(*args, **kwargs): start = time.time() func(*args, **kwargs) end = time.time() print("{} cost time = {}".format(getattr(func, '__name__'), (end-start))) return _warp return warp 复制代码
这样,通过装饰器方法,实现了同样的功能。以后如果需要修改的话,直接改装饰器就好了,一劳永逸。
进阶阶段
当代码写时间长了之后,肯定会对自己有更高的要求,而不只是格式,注释这些基本规范。
但在这个过程中,也是有一些问题需要注意的,下面就来详细说说。
炫技
第一个要说的就是「炫技」,当对代码越来越熟悉之后,总想写一些高级用法。但现实造成的结果就是,往往会使代码过度设计。
这不得不说说我的亲身经历了,曾经有一段时间,我特别迷恋各种高级用法。
有一次写过一段很长的 SQL,而且很复杂,里面甚至还包含了一个递归调用。有「炫技」嫌疑的 Python 代码就更多了,往往就是一行代码包含了 N 多魔术方法。
然后在写完之后漏出满意的笑容,感慨自己技术真牛。
结果就是各种被骂,更重要的是,一个星期之后,自己都看不懂了。
其实,代码并不是高级方法用的越多就越牛,而是要找到最适合的。
越简单的代码,越清晰的逻辑,就越不容易出错。而且在一个团队中,你的代码并不是你一个人维护,降低别人阅读,理解代码的成本也是很重要的。
脆弱
第二点需要关注的是代码的脆弱性,是否细微的改变就可能引起重大的故障。
代码里是不是充满了硬编码?如果是的话,则不是优雅的实现。很可能导致每次性能优化,或者配置变更就需要修改源代码。甚至还要重新打包,部署上线,非常麻烦。
而把这些硬编码提取出来,设计成可配置的,当需要变更时,直接改一下配置就可以了。
再来,对参数是不是有校验?或者容错处理?假如有一个 API 被第三方调用,如果第三方没按要求传参,会不会导致程序崩溃?
举个例子:
page = data['page'] size = data['size'] 复制代码
这样的写法就没有下面的写法好:
page = data.get('page', 1) size = data.get('size', 10) 复制代码
继续,项目中依赖的库是不是及时升级更新了?
积极,及时的升级可以避免跨大版本升级,因为跨大版本升级往往会带来很多问题。
还有就是在遇到一些安全漏洞时,升级是一个很好的解决办法。
最后一点,单元测试完善吗?覆盖率高吗?
说实话,程序员喜欢写代码,但往往不喜欢写单元测试,这是很不好的习惯。
有了完善,覆盖率高的单元测试,才能提高项目整体的健壮性,才能把因为修改代码带来的 BUG 的可能性降到最低。
重构
随着代码规模越来越大,重构是每一个开发人员都要面对的功课,Martin Fowler 将其定义为:在不改变软件外部行为的前提下,对其内部结构进行改变,使之更容易理解并便于修改。
重构的收益是明显的,可以提高代码质量和性能,并提高未来的开发效率。
但重构的风险也很大,如果没有理清代码逻辑,不能做好回归测试,那么重构势必会引发很多问题。
这就要求在开发过程中要特别注重代码质量。除了上文提到的一些规范之外,还要注意是不是滥用了面向对象编程原则,接口之间设计是不是过度耦合等一系列问题。
那么,在开发过程中,有没有一个指导性原则,可以用来规避这些问题呢?
当然是有的,接着往下看。