做为软件工程师不可避免会遇到的一个场景是:咱们在改变或添加一个功能到不是咱们建立的、咱们不熟悉的、与咱们负责的系统部分无关的代码中时,会遇到麻烦。虽然这可能会是一个繁琐而艰巨的任务,可是因为使用其余开发人员编写的代码有很大的灵活性,因此咱们能够从中获得大大的好处,包括增长咱们的影响范围,修复软件腐烂以及学习咱们之前不了解的系统部分(更况且,还能够学习其余程序员的技术和技巧)。程序员
考虑到使用其余开发人员编写的代码既有其厌烦之处,又有其优点所在,因此咱们必须当心不要犯一些严重的错误:框架
因为开发人员,包括咱们本身,是人,因此在处理其余开发人员编写的代码时,处理好不少人的天性问题是颇有用的。在这篇文章中,咱们将经过咱们可使用的五种技术来确保将对人性的理解成为咱们的优点,从现有代码和原做者汲取尽量多的帮助,并使得其余开发人员编写的代码最后变得比原来更优秀。虽然这里列出的5个方法并不全面,可是使用下面的技术将确保在结束改动其余开发人员编写的代码时,咱们有信心保持现有功能的工做状态,同时确保咱们的新功能与现有的代码库协调一致。ide
1.确保测试的存在工具
要想确保在其余开发人员编写的代码中所存在的现有功能实际可以按照预期的方式工做,而且咱们对其进行的任何更改都不会影响到功能的实现,惟一真正使人信心十足的方式是用测试来支持代码。当咱们遇到另外一位开发人员编写的代码时,代码有两种所处的状态:(1)没有足够的测试水平,或(2)有足够的测试水平。遇到前一种状况,咱们得负责建立测试,而在后一种状况下,咱们可使用现有的测试来确保咱们作出的任何更改都不会破坏代码,并尽量多地从测试去了解代码的意图。学习
建立新测试测试
这是一个悲伤的例子:咱们在改变其余开发人员的代码时,要对更改结果负责,可是咱们没有办法保证咱们在进行更改时不破坏任何东西。抱怨是没有用的。不管咱们发现代码处在什么样的条件下,咱们总归是要接触代码,所以若是代码坏掉了,就是咱们的责任。因此咱们在改变代码时,必定要掌控本身的行为。肯定不会破坏代码的惟一方法是本身写测试。网站
虽然这是乏味的,但它容许咱们经过编写测试来学习,这是它的主要优势。假设代码如今能够正常工做,而咱们须要编写测试,以便预期的输入会致使预期的输出。在咱们完成这个测试的过程当中,咱们逐渐了解到代码的意图和功能。例如,给出如下代码this
咱们对代码的意图以及为何在代码中使用Magic number知道得并很少,可是咱们能够建立一组测试,已知输入产生已知输出。例如,经过作一些简单的数学和解决构成成功的阈值薪水问题,咱们发现若是一我的的年龄在30岁如下,且每一年大概赚68,330美圆,那么他被认为是成功的(按照本规范的标准)。虽然咱们不知道那些magic number是什么,可是咱们知道它们确实减小了初始的薪水值。所以,68,330美圆的阈值是扣除前的基本工资。经过使用这些信息,咱们能够建立一些简单的测试,例如:.net
经过这三个测试,咱们如今对现有代码的工做方式有了大体的了解:若是一我的不到30岁,且每一年赚$ 68,300,那么他被认为是成功人士。虽然咱们能够建立更多的测试来确保临界状况(例如空白年龄或工资)功能正常,可是一些简短的测试不只使咱们了解了原始功能,还给出了一套自动化测试,可用于确保在对现有代码进行更改时,咱们不会破坏现有功能。设计
使用现有测试
若是有足够的代码测试组件,那么咱们能够从测试中学到不少东西。正如咱们建立测试同样,经过阅读测试,咱们能够了解代码如何在功能层面上工做。此外,咱们还能够知道原做者是如何让代码运行的。即便测试是由原做者之外的人(在咱们接触以前)撰写的,也依然可以为咱们提供关于其余人对代码的见解。
虽然现有的测试能够提供帮助,但咱们仍然须要对此持保留态度。测试是否与代码的开发更改一块儿与时俱进是很难说的。若是是的话,那么这是一个很好的理解基础;若是不是,那么咱们要当心不要被误导。例如,若是初始的工资阈值是每一年75,000美圆,然后来更改成咱们的68,330美圆,那么下面这个过期的测试可能会使咱们误入歧途:
这个测试仍是会经过的,但没有了预期的做用。经过的缘由不是由于它正好是阈值,而是由于它超出了阈值。若是此测试组件包含这样一个测试用例:当薪水低于阈值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。执行的第一个重构是提取方法,以更好地封装我的净工资的逻辑:
在咱们进行这种改变以后,咱们从新编译并运行咱们的测试套件,测试套件将继续经过。如今更容易看出,成功是经过一我的的年龄和净薪酬定义的,可是getNetSalary方法彷佛并不像Person类同样属于SuccessfulFilter(指示标志就是该方法的惟一参数是Person,该方法的惟一调用是Person类的方法,所以对Person类有很强的亲和力)。 为了更好地定位这个方法,咱们执行一个Move方法将其移动到Person类:
为了进一步清理此代码,咱们对每一个magic number执行符号常量替换magic number行为。为了知道这些值的含义,咱们可能得和原做者交流,或者向具备足够领域知识的人请教,以引领正确的方向。咱们还将执行更多的提取方法重构,以确保现有的方法尽量简单。
从新编译和测试,发现系统仍然按照预期的方式工做:咱们没有改变外部行为,可是咱们改进了代码的可靠性和内部结构。有关更复杂的重构和重构过程,请参阅Martin Fowler的Refactoring Guru网站。
5.当你离开的时候,代码比你发现它的时候更好
最后这个技术在概念上很是简单,但在实践中很困难:让代码比你发现它的时候更好。当咱们梳理代码,特别是别人的代码时,咱们大多会添加功能,测试它,而后前行,不关心咱们会不会贡献软件腐烂,也不在意咱们添加到类的新方法会不会致使额外的混乱。所以,本文的所有内容可总结为如下规则:
前面提到过,咱们须要对类形成的损坏和对改变的代码负责,若是它不能工做,那么修复是咱们的职责。为了打败伴随软件生产而出现的熵,咱们必须强制本身作到离开时的代码比咱们发现它的时候更佳。为了避免逃避这个问题,咱们必须偿还技术债务,确保下一个接触代码的人不须要再付出代价。说不定,未来多是咱们本身感谢本身这个时候的坚持呢。