什么是好代码

Posted on Fri 16 February 2024 in Journal

Abstract 什么是好代码
Authors Walter Fan
 Category    learning note  
Status v1.0
Updated 2024-02-16
License CC-BY-NC-ND 4.0

在我心目中,好代码必须要符合以下四条标准

  1. 正确
  2. 易懂
  3. 易改
  4. 高效

而烂代码,只有一个衡量标准,那就是你在阅读或修改代码时骂的脏话的程度与次数

1. 正确

这是最基本的要求,代码当然要满足需求,运行起来正确无误,这一点并不那么容易做到,尤其是运行环境比较复杂,各种异常情况较多的时候。

好代码要考虑周到,各种逻辑流程和意外情况的处理要面面俱到, 单元和模块测试要覆盖异常逻辑和边界。

对于服务质量 SLA 要考虑周全, 简单说起来就是满足用户的七大基本需求

  1. 功能性,2. 稳定性,3. 可靠性,4. 性能,5. 可维护性,6. 可移植性,7. 灵活性

一般正确性主要强调功能性实现正确无误,但是稳定可靠,快速灵活也是不可或缺的

我们的代码既要防错,也要容错,足够的健壮,不易出错,不怕出错,网络崩了,电源断了,磁盘满了等异常情况,都要有相应的应对措施。

2. 易懂

  • 好代码必须是看起来很舒服,很干净,容易理解

象一篇好文章,不罗嗦,容易懂,有头有尾。

各个层次,模块及函数分工明确,各司其职, 望文知义,代码无需注释即可自我描述。

接口即契约,要足够简单,易懂易用, 窄接口好过宽接口。

其实只要符合代码规范,命名简单易懂,代码就没那么丑。

参见 Google 的代码风格指南

有空翻翻“重构”那本书中的臭味介绍, 可以提高品味。

有一些基本软件开发的普适原则,能遵守尽量遵守。

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. 美比丑好
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 国际许可协议进行许可。