实战遗留代码

jopen 12年前
   <p> 什么是遗留代码?没有自动化测试的代码就是遗留代码,不管它是十年前写的,还是昨天写的。</p>    <p> 关于遗留代码,《<a href="/misc/goto?guid=4958348615190071588" target="_blank">修改代码的艺术</a>》迄今为止依然是在具体手法上讲得最好的一本书。</p>    <p> 不过,这本书上来就直奔代码,还有一些东西是技法之外的。</p>    <p> <strong>搭建基础设施</strong></p>    <p> 做软件,没有自动化,基本上都算刀耕火织。关于基础设施,我曾写过<a href="/misc/goto?guid=4958348615984667783" target="_blank">一篇很长的文章</a>,以我实际的一个项目为例,介绍了一些设施的基本样子。</p>    <p> 那篇文章很长,具体到面对遗留代码,有哪些特别之处呢?</p>    <p> 我们的目标是给没有测试的遗留系统增加代码,那么,那么增加代码覆盖率检查是一个不错的选择,各种语言都有自己的测试覆盖率方案。与单纯使用测试覆盖率不同的是,我们需要保证测试覆盖率只能提升,不能降低。所以,这里可能会略有一些开发的工作量。</p>    <p> 此外,遗留代码的质量往往不高,除了测试覆盖率工具,我们还可以引入各种代码检查工具,同时,采用同上面类似的做法,保证各种错误只能减少,不能增加。</p>    <p> 这里的基本想法很简单,不要在废墟上继续破坏。</p>    <p> <strong>补测试</strong></p>    <p> 理想中,我们要为所有代码补上测试,但是,这种想法不现实,很简单,欠账太多。</p>    <p> 一个更为实际的做法是,任务驱动。根据要解决的问题,先把周边代码清理干净,然后再实现新需求。</p>    <p> 动手清理之前,我们要先补写验收测试,这样做主要是为了保证我们不会产生大规模的破坏。对于比较复杂的场景,要补的测试可能会比较多,但请记住,这是欠账,昨日的帐,今天补当然会很痛苦。</p>    <p> 补完验收测试,就该补单元测试了,补单元测试的做法有几种,无论哪种做法,我们都要先理解一下要动到的代码。</p>    <p> 如果面对的函数比较小,理解起来比较容易,我们就可以直接为它补充单元测试。如果函数比较大怎么办呢?一次看懂所有的逻辑几乎就是一项不可能完成的任务,但通常,即便不能理解所有的代码,但至少我们可以理解一个片段。我们能做的就是把这个可以理解的片段提取到一个单独的函数里,然后,测试这个单独的片段。</p>    <p> 在具体操作的层面上,经常出现的问题是,我们理解了这段丑陋的代码,脑子里常会不经意闪过这段代码未来的样子,所以,一动手就直奔目标样子而去,请停下来。先把这段完整的复制到待测函数里,然后给它写测试,测试通过之后再说重构的事。请记住,步子大了,容易扯到蛋。</p>    <p> 这个片段函数做完之后,我们再回到原来的函数里,在原来片段的地方,调用这个新函数。然后,再继续理解,继续分解,继续补测试,继续重构,继续替换原有调用。如此N番,你会发现,那个复杂的大函数也不像原来那样不可理喻了,这时候,如果还需要,对这个函数,我们也可以如法炮制。</p>    <p> <strong>添加新代码</strong></p>    <p> 所有一切准备就绪,才是添加新代码的时机。</p>    <p> 或许,这种做法会让曾经在开发中健步如飞的你有一种举步维艰的感觉,但请记住,曾经的健步如飞是你的错觉,因为当初的你并没有完成你该做的事情,那是欠账。</p>    <p> 出来混,迟早要还的。</p>    <div id="come_from">    来自:     <a id="link_source2" href="/misc/goto?guid=4958348616777015053" target="_blank">dreamhead.blogbus.com</a>    </div>