提升代码能力(•̀⌄•́) 可惜是比较针对java,我又不懂java,只能借鉴一下思想了。
—— By Jihan
主题:如何编写好的代码
类别:计算机->实用性->提升代码编写能力
概要:如何定义优秀的代码,如何编写优秀的代码,给出示例具体分析。
前言
写过有几年的代码了,最近在代码架构设计,函数设计,类设计等方面总是不那么通透,期望寻找一些较好的理论和原则,写出更加优雅的代码。期望阅读完本书后,能有一些代码设计编写方面的体会和收获,给出一些理念和原则。
怎样区分好和差代码
程序员的职业操守
应当自己为自己的code负责,应尽力写出整洁优雅的代码,当有狗屎需求来,或者要求效率赶工的时候,应当提出自己的反对观点,指出其后续缺陷影响。要是领导还是让那么干,就屎上堆屎吧,堆不动了就溜了吧。😎
怎么样判断是否是好代码
让人改无所改,没有可优化的余地,尝试改了一圈,还是回到原点。
一些好代码的特性
- 能通过所有测试
- 没有重复的代码
- 体现设计中的全部思想理念,而且尽量简单的体现。
- 包括尽量少的实体,比如方法,类,函数等。(主要体现系统架构和逻辑,就是系统只做某一类事情,而不是某一件事情)
- 写代码的过程中,读写的比例是10:1,因此让你的代码更易读,可以帮助你自己,也能帮助他人
- 时时保持代码的整洁。每次修改代码时,或许你只需要清除无用的代码,修改变量名,清除一点重复的代码……
良好的命名
- 良好的命名能够帮助你很多,一旦发现好的命名,就要替换旧的命名。好的命名,能一眼看出你想干什么,你代表什么。
- 避免误导性的命名,比如fp, socket等,或者语言特定称呼,比如xxxList,除非真的是list类型,不然最好用其他命名。
- 做有意义的区分,而不是一些含义相同,拼写不同的命名。比如productInfo和productData
- 不要自己造单词,或者不可读单词。比如什么gmssssasl这种乱七八糟的
- 避免使用一些编码和数字,而是用一些宏定义替代它。
- 类名和变量名,都应该是名词;函数都应该是动词。
- 同一个概念应该对应同一个词汇。
函数
- 函数应该做一件事情,做好这一件事情。并且只做这一件事情。
- 判断函数是否做一件事情的方法,是函数是否还能拆分,从而改变其抽象层级。
- 每个函数中应当只包含一个抽象层级。
- 使用表述性词汇,即能描述出函数的功能。而且越精简的函数越好命名。
- 尽量少的传递参数,如果超过了三个以上的参数,就需要考虑用类封装了。
- 标识参数异常丑陋,因为这表明函数不止做一件事情。
- 函数尽量不要暗含其他操作。如果有最好进行说明
注释
- 优美易读的代码不需要注释。
- 有用的注释:
- 法律信息
- 提供一些信息,比如wiki链接
- 提供意图,为何这么干
- 进行一些补充说明
- 一些警告,比如前置条件一类
- TODO
- 避免一些误导性注释,以及一些废话。
- 避免循环性注释,也就是每个函数都加什么参数说明这种注释。
- 注释也要讲求精炼。
格式
良好的格式更易于阅读。
- 适当的缩进和换行
- 被调用的函数写在更下面,以方便顺序阅读。C这种有语言限制的除外。
- 水平对齐,更方便查看。
- 团队应当保持统一的编码风格。
对象和数据结构
- 面向过程的代码,便于在已有的数据结构下添加新的函数, 这是以数据结构为中心。 面向对象,便于在已有的函数下添加新的类,以函数为中心,也就是接口为中心。
- 什么时候使用面向对象,什么时候使用面向过程,都是值得考虑。而混用是否导致混乱和不必要,也需要进行考虑。比如go中封装的db库,即把它当成一个对象,来操作其方法,又直接获取其子成员包含方法。即没有把对象内部细节屏蔽,又使用了对象的封装,以求实现屏蔽细节。矛盾,需要细细思考。TODO
- 对象暴露行为,隐藏数据。数据结构暴露数据,没有什么行为。在合适的地方使用合适的结构。(个人理解,如果提供一种能力,那么则可以使用对象。如果是提供一种状态,则可以使用数据结构。对外提供能力,对内传输数据。向上提供接口,向下读写数据。)
边界
- 如何应对第三方接口的修改?
- 在第三方接口上进行封装。但是其能力会降低?
- 编写相关测试代码,测试第三方程序。以及相应的更新文档。以确定新版本的改动。(风险只能降低,不能避免)
- 对于还不存在的代码,我们可以先定义其行为和交互的数据。给一个统一的接口,代码来了直接装填就能用。
- 编写代码前,我们应当清晰的定义出软件的边界,以及哪些地方可扩展。对于可扩展的地方要小心谨慎对待,系统对接的地方也是如此,以便以后尽量少的改动我们的代码。
单元测试
- 测试应当随着代码的开发跟进开发
- 太久不维护测试代码,会导致测试代码失效,导致修改代码的故障率增加,以至于不想不敢去改原始代码。比如我写的AAD模块。
- 测试代码覆盖的越全面,你就能更加轻易的修改功能代码。
- 测试代码要求简单、精悍、足具表达力。
- 一类测试应当有一个断言。
- 测试应当是一类作为一个测试,而测试数据应当想办法独立,以保障最大的覆盖面。
- 整洁的测试还遵循以下5条规则:
- 快速(Fast)测试应该够快。测试应该能快速运行。测试运行缓慢,你就不会想要频繁地运行它。如果你不频繁运行测试,就不能尽早发现问题,也无法轻易修正,从而也不能轻而易举地清理代码。最终,代码就会腐坏。
- 独立(Independent)测试应该相互独立。某个测试不应为下一个测试设定条件。你应该可以单独运行每个测试,及以任何顺序运行测试。当测试互相依赖时,头一个没通过就会导致一连串的测试失败,使问题诊断变得困难,隐藏了下级错误。
- 可重复(Repeatable)测试应当可在任何环境中重复通过。你应该能够在生产环境、质检环境中运行测试,也能够在无网络的列车上用笔记本电脑运行测试。如果测试不能在任意环境中重复,你就总会有个解释其失败的接口。当环境条件不具备时,你也会无法运行测试。
- 自足验证(Self-Validating)测试应该有布尔值输出。无论是通过或失败,你不应该查看日志文件来确认测试是否通过。你不应该手工对比两个不同文本文件来确认测试是否通过。如果测试不能自足验证,对失败的判断就会变得依赖主观,而运行测试也需要更长的手工操作时间。
- 及时(Timely)测试应及时编写。单元测试应该恰好在使其通过的生产代码之前编写。如果在编写生产代码之后编写测试,代码难以测试。你可能会认为某些生产代码本身难以测试。你可能不会去设计可测试的代码。
类
- 类应当尽量短小,一个类不要承载过多的公共函数和变量。
- 一个类应当遵循单一原则,只有一个修改原因。
- 应当对类加以组织,减少修改带来的影响。
系统
- 系统的构建和运行应当分离,意思是系统架构和实际运行的功能应当分离。
- 主系统在执行的时候,默认实际运行的功能已经是ok的,主程序不依赖任何实际运行的功能。
- 依赖注入,一种强大的机制可以实现分离构造与使用。在依赖管理情景中,对象不应负责实体化对自身的依赖。反之,它应当将这份权责移交给其他“有权力”的机制,从而实现控制的反转。
- 扩容是系统必须考虑的事情之一。但是无法一次性规划好系统,只需要持续将关注面进行切分。个人觉得,netfilter的架构是扩展性很好的架构。
- 模块化和关注面切分成就了分散化管理和决策。
迭代
Kent Beck关于简单设计的四条规则:
- 运行所有测试
- 不可测试的系统,不可验证,不可验证的系统不能上线
- 良好的测试能促使进一步完善代码。
- 不要有重复
- 表达程序员意图
- 尽可能的减少方法和类
良好的遵循以上四条规则,能够更好的帮助你重构某个不合理的模块,迭代增加新的功能。
并发编程
并发是一种时机上的解耦
- 并发并不总能提高性能。
- 并发可能伴随很大程度上的系统改造。
- 并发可能需要对系统有更加深入的理解。
- 并发开发建议:
- 分离并发代码和其他代码
- 严谨控制数据的作用域
- 尽量使用副本。比如内存池和线程池。
- 线程应当尽可能独立。
- 详细了解基本的并发之间的基础模型:读写模型,生产者消费者模型,哲学家圆桌模型。
- 明确读写锁,死锁,互斥,线程饥饿等概念。
- 警惕同步方法之间的依赖,保持同步区域的微小。
- 考虑子线程死锁后如何退出线程。TODO
- 偶发性错误可能是多线程之间隐藏的难易发现的错误,也要重视。尝试稳定复现。
- 编写可插拔的线程代码:可控线程数量,可控线程运行速度,可配置程序运行环境。
结语
总的来说,在有一定开发经验的基础上来阅读本书,是很有帮助的,其中最让我受益的是重新理解了测试的重要性,以及一些使代码整洁的基本原则。
可惜了是java为主的代码示例,让我有些看不懂,或者感触不深,毕竟毫无java开发经验。