编写可读代码的艺术:末·代码块&测试
前言:上篇记录了如何改变程序的“循环与逻辑”来让代码更有可读性,包括几种技巧,这些技巧都需要对代码结构做出微小的改工,具体见这里。本文在前两篇的基础上,讨论第三部分,在函数级别对代码做更大的改动,以及第四部分,可读测试代码的重要性。
*第一部分:重新组织代码 *
如何编写可读代码
- 简化命名、注释和格式的方法,使每行代码都言简意赅。
- 梳理程序中的循环、逻辑和变量来减小复杂度并理清思路。
- 在函数级别解决问题,例如重新组织代码块,使其一次只做一件事。
- 编写有效的测试代码,使其全面简洁,同时可读性更高。
抽取不相关的子问题
在工程学中,主要研究的问题是关于把大问题拆成小问题,再把这些问题的解决方案放回一起。带代码中也类似,主要考虑的问题如下:
(1)针对某个函数或代码块,思考这段代码的高层次目标是什么?
(2)针对每一行代码,思考其是否为了直接目标而工作,其高层次目标是什么?
(3)若由足够的行数在解决不相关的子问题,抽取代码到独立的函数中。
也就是,把一般代码和项目专有的代码分开。使得最后大部分都是一般代码,通过建立一大组库和辅助函数来解决一般问题,剩下的只是让程序与众不同的核心部分。
纯工具代码
一些核心任务,大多数程序都会做,例如操作字符串、使用哈希表以及读/写文件。通常,这些“基本工具”是由编程语言中内置的库来实现的,对于没有的方法,需要自己来完成空白,也就是一个不相关的子问题,并应抽取到一个新的函数中。比如:读取文件函数:ReadFileToString()。
创建大量通用代码
在项目中广泛适用,常常有个专门的目录来存放这种代码(例如util),方便重用。通用代码有很多好处,完全从项目的其他部分中解耦出来,容易开发,容易测试,并且容易理解。
简化已有接口
创建自己的包装函数,隐藏不理想的接口。
过犹不及
为代码增加一个函数存在一个小的(却有形的)可读性代价,要注意适度,根据项目需求,付出代价满足得到成效才可以。
一次只做一件事
- 应该把代码组织得一次只做一件事情,如果所有代码纠缠在一起,对于每个任务都很难靠其自身来理解它从哪里开始,到哪里结束。
- 如果代码很长很难理解,尝试把其所做的所有任务列出来,其中一些任务可以很容易地变成单独的函数(或类)。其他的可以简单地成为一个函数中的逻辑“段落”。
- 难点:准确描述程序所做的小事情。
把想法编变成代码
简单技巧:用自然语言描述程序然后用这个描述来写出更自然的代码。所做的任务如下:
(1)像对着一个同事一样用自然语言描述代码要做什么
(2)注意描述中所用
(3)写出与描述所匹配的代码
具体的需要做到如下几点:
1、清楚地描述逻辑
2、了解函数库是有帮助的
3、把这个方法应用于更大的问题
(1)用自然语言描述解决方案
(2)递归使用(1)
少写代码
所写的每一行代码都是要测试和维护的,通过重用库或者减少功能,可以节省时间并且使代码库保持精简节约。可以通过以下几种方法少写代码:
- 保持小代码库
(1)创建越多越好的“工具”代码来减少重复代码
(2)减少无用代码或没有用的功能
(3)项目保持分开的子项目状态
(4)保持代码又轻又灵 - 删除独立的函数很简单,不让无用代码交织在项目中。
- 熟悉周边的库:每隔一段时间,花15分钟来阅读标准库中的所有函数/模块/类型的名字。
- 重用库有着很多好处,不仅节省时间,而且少写代码。在一个成熟的库中,每一行代码都代表相当大量的设计、调试、重写、文档、优化和测试。任何经受了这样达尔文进化过程一样的代码行都是很有价值的。
第二部分 测试与可读性
测试:任何仅以检查另一段代码的行为为目的的代码。
-
使测试易于阅读和维护。
测试代码的可读性和非测试代码是同样重要的,可以把测试代码看做非正式的文档,它记录了真实代码是如何工作和应该如何使用。测试应当具有可读性,以便其他程序员可以舒服地改变或者增加测试。 -
测试原则:对使用者隐去不重要的细节,以便更重要的细节会更突出。
-
测试基本内容:对于这样的输入/情形,期望有这样的行为/输出。
这个目的很多时候可以用一行代码来表达,使代码紧凑而又易读,让测试的表述保持很短还会让增加测试变得很简单。 -
让错误消息具有可读性:如果测试失败了,所发出的错误消息应该能容易跟踪并修正这个bug。
-
测试输入:选择一组最简单的输入,能完整的使用被测代码。
-
TDD测试驱动开发
测试驱动开发是一种编程风格,在写真实代码之前就写出测试。一般来讲,如果在设计代码时发现,这对测试来说是噩梦,那么就应该重新考虑这个设计。
如表所示为可测性差的代码的特征,以及它所带来的设计问题:
特征 | 可测性的问题 | 设计问题 |
---|---|---|
使用全局变量 | 对于每个测试都要重置所有的全局状态(否则,不同的测试之间会相互影响) | 很难理解哪些函数有什么副作用。没办法独立考虑每个函数,要考虑整个程序才能理解是不是所有的代码都能工作 |
对外部组件有大量依赖的代码 | 很难给它写出任何测试,因为要先搭起太多的脚手架。写测试会比较无趣,因此人们避免写测试。 | 系统会更可能因某一依赖失败而失败。对于改动来讲很难知道会产生什么样的影响。很难重构类,并且要考虑更多恢复路劲 |
代码又不确定的行为 | 测试会很古怪,而且不可靠。经常失败的测试最终会被忽略。 | 这种程序更可能会有条件竞争或者其他难以重现的bug。这种程序很难推理。产品中的bug很难跟踪和改正。 |
另一方面,如果设计的代码容易写出测试,是个好现象。如下表所示为可测性较好的代码的特征,以及它所产生的优秀设计:
特征 | 对可测性的好处 | 对设计的好处 |
---|---|---|
类中只有很少或者没有内部状态 | 很容易写出测试,因为测试一个方法只要较少的设置,并且有较少的隐藏状态需要检查 | 有较少状态的类更简单,更容易理解 |
类/函数只做一件事 | 要测试它只需要较少的测试用例 | 较小/较简单的组件更加模块化,并且一般来讲系统有更少的耦合 |
每个类对别的类的依赖很少;低耦合 | 每个类可以独立地测试(比多个类一起测试容易的多) | 系统可以并行开发。可以很容易修改或者删除类,而不会影响系统的其他部分 |
函数的接口简单,定义明确 | 有明确的行为可以测试。测试简单接口所需的工作量较少 | 接口更容易让程序员学习,并且重用的可能性更大 |
小结
《编写可读代码的艺术》这本书看完了,也总结了三篇读书笔记,分别为编写可读代码的艺术:初·代码审美、编写可读代码的艺术:次·循环逻辑优化、编写可读代码的艺术:末·代码块&测试。 这三篇文章中,对代码的优化的范围越来越大,难度也越来越高,从基本的变量名字修改到循环逻辑优化,再到本文中整个代码块的设计和测试代码的可读性,到这里时,已经不是寥寥数语可以记录的,知识点和理论很少,说起来也很简单,我甚至把四大部分中的后两部分合并写做一篇博客,因为讲起来真的很容易,但需要的是大量的实践和总结。原来觉得一个高级程序员或者说有经验的程序员,就是代码量很多,做过很多项目,现在才觉得,代码的设计,项目的结构,合理的测试等等,这些才是更重要的。写代码这件事情,并不是表面看起来那么的体力活,或许每个程序员都应该把自己看做一个作家,虽然这个世界上有很多种语言,我们的目的不是熟练某一种语言,也不是仅仅能够写出完整的“作品”就可以了,更重要的,是写出“艺术感”的“作品”。知识的海洋太过浩渺,世界上的聪明人数不甚数,撸起袖子加油干,共勉。