在我心目中,好代码必须要符合以下四条标准
- 正确
- 易懂
- 易改
- 高效
而烂代码,只有一个衡量标准,那就是你在阅读或修改代码时骂的脏话的程度与次数
1. 正确
这是最基本的要求,代码当然是满足需求,运行起来正确无误,这一点并不那么容易做到,尤其是运行环境比较复杂,各种异常情况较多的时候。
好代码要考虑周到,各种逻辑流程和意外情况的处理要面面俱到, 单元和模块测试要覆盖异常逻辑和边界。
对于服务质量 SLA 要考虑周全, 简单说起来就是满足用户的七大基本需求
- 功能性,2. 稳定性,3. 可靠性,4. 性能,5. 可维护性,6. 可移植性,7. 灵活性
一般正确性主要强调功能性实现正确无误,但是稳定可靠,快速灵活也是不可或缺的
我们的代码既要防错,也要容错,足够的健壮,不易出错,不怕出错,网络崩了,电源断了,磁盘满了等异常情况,都要有相应的应对措施。
2. 易懂
- 好代码必须是看起来很舒服,很干净,容易理解
象一篇好文章,不罗嗦,容易懂,有头有尾。
各个层次,模块及函数分工明确,各司其职, 望文知义,代码无需注释即可自我描述。
接口即契约,要足够简单,易懂易用, 窄接口好过宽接口。
其实只要符合代码规范,命名简单易懂,代码就没那么丑。
有空翻翻"重构"那本书中的臭味介绍, 可以提高品味。
有一些基本软件开发的普适原则,能遵守尽量遵守。
KISS: Keep It Simple and Straight
保持简单和直接, 适当隐藏复杂性
或者
KISS: Keep It Simple and Stupid 保持简单, 象傻瓜一样, 不要让别人多加思考
软件接口或 API 的设计要让人一看就明白, 一看就知道作者的意图和想法, 如何使用它, 有什么结果和可能的异常及副作用. 看过很多代码, 踩过许多坑, 大多是作者或者我自己使用了出乎意料的实现, 不做好必要的抽象和封装, 复杂的判断和算法到处都是
DRY: Don't Repeat Yourself
别重复你自己, 如有重复代码, 请抽象或重用,切勿将重复的代码散落在多处,否则修改代码时就知道有多痛苦了
SRP: Single Responsibility Principle
单一职责原则: 就一类而言, 应该仅有一个引起它变化的原因,就一个函数来说,它应该只做好一件事,贪多嚼不烂。
也有一个别称 DOTADIW - Do One Thing and Do It Well 就做一件事并做好它
OCP: Open Close Principle
"software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification
一个软件实体如类、模块和函数应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。
我们可以在原有的类中添加字段或方法进行扩展,或者给函数添加新的参数(C++可赋予一个默认值, Java 干脆重载一个新函数),以及扩展出一个新的子类。
而对于已经暴露出去的API 以及接口, 类及公有函数,尽量不要修改它们,因为你需要说服你的客户(其他使用这块代码的模块)做相应的修改,而你往往并不清楚有多少客户在使用它。
LSP: Liskov Substitution Principle
LSP替换基类原则: 子类型应该可以替换掉它们的基类型,或者说父类型拥有子类型的全部共性,子类型拥有父类型的全部特征。
ISP: Interface Segregation Principle
ISP接口隔离原则: 不应该强迫客户依赖于它们不用的方法, 接口属于客户, 不属于它所在的类层次结构,接口是生产者与消费者之间的契约,应该精炼简约,生产者只通过接口提供服务,而消费者也只通过接口来调用服务。
DIP: Dependency Inversion Principle
DIP依赖倒置原则: 抽象不应该依赖于细节, 细节应该依赖于抽象。
我们要依赖于高层接口,而不是依赖于底层实现,接口是不会轻易改变的,而实现可以修改和替换 时下常用的插件机制,依赖注入方法都遵循这一原则
-- 上述 5 个原则常被统称为 SOLID 原则
3. 易改
好代码要易于测试和修改, 适当的抽象,封装和模块化有利于后期的修改和重构。
封装好复杂性,区分开经常变化与基本不变的代码, 适当抽取易变参数作为配置。 为未来的变化设计有限的灵活性,无需多改就能很容易的扩展新功能。
还是书里那句话,高内聚,低耦合。将依赖倒置,做好抽象,封装和模块化,代码就不难改。
例如最常用的 MVC 模式,为什么我们要分成模型,视图和控制器三块,原因之一就在于分开易变的与不易变的,分开不会在一起变化的部分,减小每次修改的范围。
如果某个方面的功能需要修改,最好是改个配置, 其次是扩展个类或函数, 或者传个不同参数,最差的就是改多个地方,要做多个判断, 还要做霰弹式的修改。
好代码要易于观测和调试,也就是说日志和度量数据要完备,可以分级,分模块进行日志和度量数据的调整与分析,自带度量 API, 调试 API 及控制台为佳
好代码还要与时俱进,自我蜕变,写代码时要将不变的与易变的分开,技术再怎么变,人性不会变,挣钱的业务变化也不会太大。一般来说,要封装好业务逻辑,核心业务不会大变,即使推到重写也要理解和参照老系统的业务流程。
人会变老,代码也会,新业务,新技术,新架构,新框架层出不穷,要大胆试验,小心引入,逐步演进,不必抱残守缺,也不要盲目冲动,把握住变与不变的平衡,经常变的地方应该尽量少,且尽量方便修改。
引述一下,Python 之禅,虽然说的是Python, 其实适用于多数编程语言
英文 | 中文 |
---|---|
Beautiful is better than ugly. | 美比丑好 |
vbnet
Explicit is better than implicit. | 明显比隐晦好
Simple is better than complex. | 简单比复杂好
Complex is better than complicated. | 复杂比难懂好
Flat is better than nested. | 扁平比嵌套好
Sparse is better than dense. | 稀疏比稠密好
Readability counts. | 可读性很重要
Special cases aren't special enough to break the rules. | 特例也不要打破这个原则
Although practicality beats purity. | 尽管实践会破坏纯洁性
Errors should never pass silently. | 错误还是不能让其悄然滑过
Unless explicitly silenced. | 除非你明确声明不用理会它
In the face of ambiguity, refuse the temptation to guess. | 别让人来猜测不确定的可能性
There should be one-- and preferably only one --obvious way to do it. | 应该有一个且只有一个比较好的明显的方法来做事
Although that way may not be obvious at first unless you're Dutch. | 尽管那个方法可能并非一开始就显而易见
Now is better than never. | 现在就做比永远不做好
Although never is often better than right now. | 尽管永远不做经常比马上就动手做好
If the implementation is hard to explain, it's a bad idea. | 如果实现很难解释清楚, 那它不是一个好主意
If the implementation is easy to explain, it may be a good idea. | 如果实现很容易说清楚, 那它是个好主意
Namespaces are one honking great idea -- let's do more of those! | 命名空间是个绝妙点子, 让我们那样做得更多
4. 高效
好的代码应该充分利用可用的资源,性能达标,没有无谓的浪费和等待。 使用合适的算法和数据结构,对 CPU, 内存,网络等资源的使用有节制。
在关键算法,关键路径上要做算法复杂度分析,应用大 O 分析法以及Amdahl加速定律等进行定性和定量分析,再结合性能度量数据进行调优。
我们不但要给业务数据制定 KPI, 也要给性能数据制定 KPI, 例如通常用的到 UPS - Usage, Performance 和 Saturation, 通过观测,度量和分析这些 KPI 再做有的放矢的调优。
压力测试(stress testing)和性能分析(performance profiling)是必不可少的方法。 一般来说,高效率要在设计,实现和测试多方面都要做有针对性的考虑,通过度量驱动,想清楚代码上线怎么生成和收集度量数据,如何通过度量进行调优。
参考资料
- Agile Software Development, Principles, Patterns, and Practices by Robert C. Martin
- Clean code by Robert C. Martin
- Refactoring: Improving the Design of Existing Code Martin Fowler
本作品采用 [知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](http://creativecommons.org/licenses/by-nc-nd/4.0/)进行许可。