这个测试还是会通过的,但没有了预期的作用。通过的原因不是因为它正好是阈值,而是因为它超出了阈值。如果此测试组件包含这样一个测试用例:当薪水低于阈值1美元时,过滤器就返回false,这样第二个测试将会失败,表明阈值是错误的。如果套件没有这样的测试,那么陈旧的数据会很容易误导我们弄错代码的真正意图。当有疑问时,请相信代码:正如我们之前所表述的那样,求解阈值表明测试没有对准实际阈值。
另外,要查看代码和测试用例的存储库日志(即Git日志):如果代码的最后更新日期比测试的最后更新日期更近(对代码进行了重大更改,例如更改阈值),则测试可能已经过时,应谨慎查看。注意,我们不应该完全忽视测试,因为它们也许仍然能为我们提供关于原作者(或最近撰写测试的开发人员)意图的一些文档,但它们可能包含过时或不正确的数据。
2.与编写代码的人交流
在涉及多个人的任何工作中,沟通至关重要。无论是企业,越野旅行还是软件项目,缺乏沟通是损害任务最有效的手段之一。即使我们在创建新代码时进行沟通,但是当我们接触现有的代码时,风险会增加。因为此时我们对现有的代码并不太了解,因此我们所了解的内容可能是被误导的,或只代表了其中的一小部分。为了真正了解现有的代码,我们需要和编写它的人交流。
当开始提出问题时,我们需要确定问题是具体的,并且旨在实现我们理解代码的目标。例如:
- 这个代码片段最适合放到系统的哪里?
- 你有什么设计或图表吗?
- 我应该注意什么陷阱?
- 这个组件或类是做什么的?
- 有没有什么你想放到代码里,但当时没有做的?为什么?
始终要保持谦虚的态度,积极寻求原作者真正的答案。几乎每个开发人员都碰到过这样的场景,他或她看着别人的代码,自问自答:“为什么他/她要这样做?为什么他们不这样做?”然后花几个小时来得出本来只要原作者回答就能得到的结论。大多数开发人员都是有才华的程序员,所以即使如果我们遇到一个看似糟糕的决定,也有可能有一个很好的理由(可能没有,但研究别人的代码时最好假设他们这样做是有原因的;如果真的没有,我们可以通过重构来改变)。
沟通在软件开发中起次要副作用。1967年最初由Melvin Conway创立的康威定律规定:
设计系统的任何组织…都将不可避免地产生一种设计,该设计结构反映了组织的通信结构。
这意味着,一个庞大、紧密沟通的团队可能会生成一体化,紧密耦合的代码,但一些较小的团队可能会生成更独立、松散耦合的代码(有关此相关性的更多信息,请参阅《Demystifying Conway’s Law》)。对于我们来说,这意味着我们的通信结构不仅影响特定的代码段,也影响整个代码库。因此,与原作者密切沟通绝对是一个好办法,但我们应该自检不要太过于依赖于原作者。这不仅可能会惹恼原作者,还可能在我们的代码中产生无意识的耦合。
虽然这有助于我们深入研究代码,但这是在假设可以接触原作者的情况下。在很多时候,原作者可能已经离开了公司,或恰巧不在公司(例如正在休假)。在此种情况下我们该做什么?询问可能对代码有所了解的人。这个人不一定要曾真正工作于代码,他可以是在原作者编写代码时就在周围,也可以是认识原作者。哪怕仅是从原开发者周围的人中得到只言片语,也可能会启迪其他未知的代码片段。
3.删除所有警告
心理学中有一个众所周知的概念,称为“破窗理论”,Andrew Hunt和Dave Thomas在《 The Pragmatic Programmer 》(第4-6页)中详细描述了这个概念。这个理论最初是由James Q.Wilson和George L. Kelling提出的,描述如下:
假设有一个建筑物有几扇破了的窗户。如果窗户没有修好,那么破坏者会趋向于打破更多的窗户。最终,他们甚至可能会破门而入,如果建筑物是没人住的,那么他们可能会非法占有或者在里面点火。也可以考虑人行道的情况。如果道路上面有垃圾堆积,那么不久之后,就会有更多的垃圾累积。最终,人们甚至会开始往那里扔外卖垃圾,甚至打破汽车。
这个理论指出,如果似乎已经没人关心这个物品或事物,那么我们就会忽视对物品或事物的照顾,这是人的天性。例如,如果一栋建筑物看上去已经凌乱不堪,那么它更有可能被肆意破坏。在软件方面,这个理论意味着如果开发人员发现代码已经是一团糟,那么人的本性会让他弄坏代码。从本质上说,我们心里想的是(即使心理活动没有这么丰富),“既然最后一个人不在乎这代码,我为什么要在乎?”或“都是乱糟糟的代码,谁知道是谁写的。”
但是,这不应该成为我们的借口。只要我们接触以前属于其他人的代码,那么我们就要对这些代码负责,并且如果它不能有效工作的话,我们得担负后果。为了战胜这种人的天性行为,我们需要采取一些小措施以避免我们的代码更少地被弄脏(及时更换破掉的窗户)。
一个简单方法是删除来自我们正在使用的整个包或模块中的所有警告。至于未使用或添加注释的代码,删除它。如果我们稍后需要这部分代码,那么在存储库中,我们总是可以从先前的提交中检索它。如果存在无法直接解决的警告(例如原始类型警告),那么使用@SuppressWarnings注解注释该调用或方法。这样可以确保我们对代码进行过仔细的考虑:它们不是因为疏忽而发出的警告,而是我们明确地注意到了警告(如原始类型)。
一旦我们删除或明确地禁止所有警告,那么我们就必须确保代码保持免除警告。这有两个主要作用:
- 迫使我们仔细考虑我们创建的任何代码。
- 减少代码腐败的变化,现在的警告会导致以后的错误。
这对其他人,以及我们自己都有心理暗示作用——我们其实关心我们正在处理的代码。它不再是条单行线——我们强逼着自己更改代码,提交,然后永不回头。相反,我们认识到我们需要对这代码负责。这对之后的软件开发也是有帮助的——它向将来的开发人员展示,这不是一间窗户都破了的仓库:而是一个维护良好的代码库。
4.重构
在过去几十年中,重构已经成为了一个非常重要的术语,并且最近被当作是对当前工作代码做任何改变的代名词。虽然重构确实涉及对当前正在工作的代码的更改,但并非整个大局。Martin Fowler在他关于这个话题的重要着作——《Refactoring》一书中将重构定义为:
对软件的内部结构进行更改,使其更容易理解并且修改起来更便宜,而不改变其可观察的行为。
这个定义的关键在于它涉及的更改不会改变系统可观察的行为。这意味着当我们重构代码时,我们必须要有方法来确保代码的外部可见行为不会改变。在我们的例子中,这意味着是在我们继承或自己开发的测试套件中。为了确保我们没有改变系统的外部行为,每当我们进行改变时,都必须重新编译和执行我们的全部测试。
此外,并不是我们所做的每一个改变都被认为是重构。例如,重命名方法以更好地反映其预期用途是重构,但添加新功能不是。为了看到重构的好处,我们将重构SuccessfulFilter。执行的第一个重构是提取方法,以更好地封装个人净工资的逻辑: