遗留代码移交最佳实践


66

几个月后,一位同事将继续进行一个新项目,而我将继承他的一个项目。为了准备,我已经下令迈克尔·费瑟斯(Michael Feathers)有效地使用旧版代码

但是本书以及到目前为止我发现的有关遗留代码的大多数问题都与按原样继承代码的情况有关。但是在这种情况下,我实际上可以访问原始开发人员,并且我们确实有时间进行有序移交。

我将继承的一段代码背景:

  • 它的功能是:没有已知的错误,但是随着性能要求的不断提高,在不久的将来将需要进行一些优化。
  • 未记录:在方法和类级别上几乎有零个文档。但是,该代码应该在更高级别上执行的操作已被很好地理解,因为多年来我一直在使用其API(作为黑匣子)进行编写。
  • 只有更高级别的集成测试:只有集成测试可以测试通过API与其他组件之间的正确交互(再次是黑匣子)。
  • 非常低级的,针对速度进行了优化:由于此代码是整个应用程序系统的核心,因此多年来,许多代码已进行了多次优化,并且非常低级(对于某些结构,一部分具有自己的内存管理器) /记录)。
  • 并发和无锁:虽然我对并发和无锁编程非常熟悉,并且实际上为该代码贡献了一些内容,但这又增加了另一层复杂性。
  • 大型代码库:这个特定的项目有超过一万行代码,因此我无法向我解释所有内容。
  • 用Delphi撰写:尽管我不认为该语言与该问题紧密相关,但我将把它放在这里,因为我认为这种类型的问题与语言无关。

我想知道如何才能最好地度过他离开之前的时间。这里有一些想法:

  • 让一切都可以在我的机器上构建:尽管应该将所有内容检查到源代码控制中,但谁也不会忘记偶尔检查一次文件,因此这应该是首要任务。
  • 更多测试:虽然我希望进行更多的类级单元测试,以便在进行更改时可以尽早发现我引入的任何错误,但现在的代码不可测试(大型类,长方法,太多代码)相互依赖)。
  • 文档内容:我认为对于初学者来说,最好将文档重点放在代码中那些本来就难以理解的领域,例如由于其低级/高度优化的特性。恐怕其中有些事情看起来很丑陋,需要重构/重写,但是实际上其中存在一些优化,这是我可能会错过的一个很好的原因(参见Joel Spolsky,您应该做的事情)从不做,第一部分
  • 如何记录:我认为最好是一些体系结构的类图和关键功能的序列图以及一些散文。
  • 向谁记录:我想知道让他写文档或让他向我解释会更好,这样我可以写文档。我担心,对他而言显而易见但对我而言不明显的事情将无法得到适当覆盖。
  • 使用成对编程进行重构:由于时间限制,这可能无法实现,但是也许我可以重构他的一些代码,以使其在他仍然在提供有关事物为何如此的方式的输入时更加可维护。

请评论并添加到此。由于没有足够的时间来完成所有这些工作,因此我对如何确定优先级特别感兴趣。

更新:移交项目结束后,我在下面的答案中根据自己的经验扩展了此列表。


2
重点记录了为什么的优化功能的!

我希望代码在源代码控制之下。如果是这样,您将受益于为每个更改输入的注释(如果有)。
伯纳德

很好地使用Michael Feathers的“有效使用旧版代码”。绝对需要开始围绕您认为最可能需要修改的模块编写这些测试用例。如果您现在就开始,那么更容易获得正确的期望。
Bill Leeper

重构之前有一个阶段,我对此感到怀疑,即哪个Internet似乎在答案上很差:顶级程序员如何理解他人的复杂且难以理解的代码?
sergiol

Answers:


25

当您可以访问开发人员时,您可以进行代码询问:

  • 哪些模块最难编码/实现。问题是什么,如何解决。

  • 哪个模块产生最多的错误。

  • 哪些模块导致了最难以解决的错误。

  • 他最引以为豪的是哪段代码。

  • 他确实想重构哪些代码,但是还没有时间。

这些问题将使您深入了解可能导致最多问题的原因,并且可能更重要的是可以理解原始开发人员的思维过程和观点。


我喜欢挑选您提到的零件的想法。凭直觉,我会按照自上而下的顺序进行操作,但那样一来,埋在代码深处的最讨厌的部分可能要到很晚才出现,甚至可能太迟了。我认为,您的方式更有意义。您对“如何记录”部分有什么建议吗?UML?文本?
PersonalNexus

@PersonalNexus。您也可以将此方法延续到文档中。询问哪些文档最有用,哪些文档不可靠或过时(请相信95%的文档属于最后一类!)。
詹姆斯·安德森

17

随着移交项目的结束,我想我会花点时间写出自己的答案,其中包含最适合我的内容。

  • 使所有内容均受版本控制:确保需要构建的所有内容均受版本控制后,我还搜索了旧开发人员的硬盘,以寻找有助于部署和/或测试应用程序的其他脚本或实用程序,尚未签到。
  • 自上而下:首先,我将对主要课程进行高层次的考察,并与主要领域的老开发人员进行导览。然后,我将自己深入研究其余内容,并用//TODO标记标记出对我而言毫无意义的内容。
  • 自己编写所有文档:当我让老开发人员仔细检查自己的作品以确保正确时,我坚持自己编写所有内容。通过这种方式,我相信这本书对我而言不仅对老开发人员有意义。
  • 处处注释:我向每个类和每个方法添加了XML文档摘要。通过这种方式,我确保至少查看了每段代码,并且具有足够的理解力来总结它在句子中的作用。随着IntelliSense收集这些信息,它也使使用汇总方法/类的方法的理解变得更加容易。我还可以轻松地确定仍然需要查看的代码区域。
  • 靠近源代码的文档为了简化源代码和文档之间的连接,我将大部分文档放在了源代码中。对于描述各种子系统之间交互作用的高级文档,我使用了Wiki,因为将这些信息仅放在代码中的一个位置是行不通的。所有文档都应该是电子的,并且可以全文检索。
  • 图表:为了获得基本概述,我使用了不同子系统的各种粒度的类图。对于并发部分,对象图和交互图确实很有帮助。另请参阅我关于该主题的其他问题
  • 一对重构:尽管我与旧开发人员进行了一些重构,以了解代码并使其更易于维护,但这是一个耗时且风险很大的过程,因为缺少良好的重构工具和很多讨厌的东西各部分之间的依赖性。迈克尔·费瑟斯(Michael Feathers)的《有效使用旧版代码》对这确实是一个很好的帮助,尽管在没有适当工具支持的情况下进行重构仍然很痛苦。重构时,我会让他控制鼠标和键盘,因为这对他来说更有趣(另请参阅我的最后一个要点),而且我可以自由写下自己所学的内容。
  • 单独签入以进行注释和更改:在通过在上写注释而无意引入了错误之后override,我非常小心地在单独签入中进行注释和更改。在签入之前,我使用了一个小工具从源代码中剥离所有注释,因此仅注释签入的差异将显示0差异。所有更改(例如,删除未使用的字段)都与旧的开发人员进行了仔细的同行评审,以确保我没有删除仍然需要的东西。
  • 关键段落的逐行浏览:对于最优化/最复杂的段落,我将与老开发人员(有时甚至与第三位同事)逐行阅读代码。这样,我对代码有了透彻的了解,并且随着越来越多的人对代码进行审查,我们实际上发现了一些错误以及一些可以进一步优化的东西。
  • 快一点,并保持老开发人员的积极性:我注意到老开发人员对他的最后一天越来越近的兴趣越来越小(不足为奇)。因此,我将确保最关键的部分已首先移交,其余部分留给我自己解决(如果需要)。我还尝试将更多有趣的事情(例如,在进行配对编程时控制键盘)留给他,并做一些无聊的事情,例如编写自己的文档。
  • 识别功能请求:我发现向老开发人员索要人们要求但尚未添加的功能列表很有帮助。在我看来,有些事情很容易添加,但是有一个很好的理由却没有添加它们,因为在实现它们时,它们会破坏我最初认为的其他东西。

14

在类似情况下,我认为以下情况也值得考虑:

  • 确保您可以进行部署并进行测试:从头开始自己进行产品部署-并确认这要离开的人所做的相同。这样可以确保您清楚所有脚本和说明,并且可以捕获任何意外的疏忽,例如尚未将其检入版本控制系统中的成分。(我并不是说这发生,只是如果它已经发生了,这将是一个更容易对付,现在,人离开前)

    (这可能与您无关,例如,如果您已经进行了持续集成或持续部署,但是值得一提,以防万一...)

  • 编写更多测试:这是测试您对系统的了解的一种非常好的方法。这将使您(或迫使您)更仔细地查看代码区域,或者确认代码是否像您怀疑的那样没有错误,或者揭示您认为您理解了意图的区域,但实际上您是需要在他离开之前要求你的同事澄清

  • 成对编写文档:这是编写概述的有效方法。我建议你让你的同事描述的特征或区域,然后把它写起来,在文档中,用自己的话。我们发现当两个人在一起完成时,这非常容易

我个人认为编写测试比编写文档具有更高的优先级,因为测试可能会给您带来更多(或更坚定)的理解。

关于使用成对编程进行重构,我唯一要说的是,这有可能成为无底坑的危险,特别是考虑到您说您只有高级测试。您可能会发现它最终使用了比您预期更多的可用时间。


+1项测试。没有足够的测试。
Sardathrion 2011年

10

为您的问题已有的答案+1!

导览图
10k行代码很多,但是我认为让另一个人给您一个“导览图”仍然不是不可能的。您坐在代码的前面,他带领您从上到下的旅程,深入“层”。您需要短时间进行操作-一劳永逸会杀死你们俩。

放大,缩小
这样做的好处是,当他向您解释时,几乎可以肯定会有一些“哦,是的,也有这样的时刻”,如果他只是想记录下来的话,他可能不会靠他自己。您的问题将有助于您将注意力集中在对他却对其他人不明显的地方。这种放大/缩小交互只能一对一地进行,试图写或读类似的东西很麻烦。

文档化
我认为你们俩都应该独立地对材料进行文档化-他应该从最底层开始(以防您没有时间聚在一起),并且应该根据自己的理解从头开始。他的导游,好像是给别人的 [在以前的工作中,我继承了很多“遗留”代码,只剩下时间在离开自己之前记录下来:)]。

哪里啊
其中大多数的目的是使您能够了解发生的事情。因此,鉴于特定的错误或修改,您可以非常快速地在代码中找到需要重点关注的地方。您可以通过列出旧错误列表并查看是否可以准确预测问题所在来进行自我测试。

抽干他
,不管他是否恨你(微笑),都没关系,您的工作是在可用的时间内从该人的大脑中获取尽可能多的信息。确保获得您的支持,并确保他们优先考虑知识转移,而不是“仅在他离开之前修复最后几个错误”(除非您将它们固定在一起...)。


+1试图自己修复一些旧错误以测试我对代码的理解
PersonalNexus

1
“不管他是否最终恨你”-小心,“这是一个小世界”;)
撤退

另外,打开一个word文档,并记录一切内容,包括大量的屏幕截图。当您处于信息超载状态时,它为我节省了很多时间!
Ben Power

7

我建议采取以下措施(除了已经确定的措施之外)-首先,请您的经理给您尽可能多的时间与这个人一起工作,并在他被要求进行更改时尝试与他坐在一起。您不必知道他正在做的一切,而要尽可能多地抓住他。最重要的是和他成为朋友。

将移交视为项目,并制定计划并让管理人员参与。

0-确保您知道如何使用系统。

1-明确列出解决方案组件,每个组件的来源以及它所在的位置(在差异存储库中)

2-立即获取并管理(如果可能)管理不同服务器的密码。确保您拥有所有管理员帐户信息

3-获取每个外部组件的许可证,除非其不在您的范围内(例如,特殊的dll,数据库等)

4-从开发人员和您的客户(如果他们在您的公司本地)获得有关系统当前状态的书面报告

5-获取有关业务规则,计算公式等的文档。您可以和他一起做。向他询问要提供给您的电子邮件,会议信息,用户要求文档,设计文档等。

6-获取软件必须响应的计划事件(每月作业,每周作业)的列表

7-了解备份/还原过程

8-了解用于构建应用程序的框架

9-了解请求的/预期的/计划的修改以及所有未决用户请求的状态。开始尝试确定如何独自完成这些任务。

10-确保您的测试和开发环境非常相似。

11-尝试识别不容易发现的主要依赖关系(在其他系统上或在组件之间)。

12-确定并记录每种软件使用的必需版本及其供应商联系(如有必要)

13-确定他正在使用的任何您没有的特殊工具,以防它对您有所帮助。

14-获得高级系统流程。并开始构建您的文档库

15-了解如何管理应用程序的用户安全

16-获取错误日志并尝试了解操作以及该操作如何影响较旧的数据(如果适用)

17-了解花费时间太长的进程以及需要注意什么(例如,异常文件大小,重复文件的ftp等)。

18-检查生产服务器时钟

19-确定配置在哪里,并将每个环境配置与生产环境进行比较,以了解哪些参数不同以及为什么

20-获取此人的联系信息

21-如果系统是内部系统,则与系统用户安排一次会议(您需要了解他们是谁以及每个人扮演什么角色)并向他们介绍。听听他们对系统及其当前问题(如果有的话)的看法。确保尽快将您包含在电子邮件中(在您的经理批准之后)

22-在离开前1周评估您的理解,并报告您认为有风险的任何问题

因为您提到您没有数据库,所以此列表变短了。

祝好运。


@SSamra:感谢您的好评。我需要 :)
NoChance 2011年

非常详尽,包括一些我可能会错过的重要点,例如,涉及管理层和我们(内部)客户。
PersonalNexus

5

我将首先考虑最复杂的,针对性能优化的零件。我会请他先记录这些部分并一次向您解释,然后再尝试针对这些部分编写测试(包括性能测试之前和之后的内容,以便您可以查看新的优化是否会使情况更好或更糟) ),并让其他人检查测试。他记录并解释了这种方式,您使用解释来编写测试(而他正在记录其他区域),而他的复审将有助于确保您了解应该进行的测试。这样,您还可以获得应用程序某些最关键部分的附加测试收敛性以及专用性能优化的文档。

如果有足够的时间解决这些问题,接下来,我将对类似应用程序的部分进行类似的处理,这些部分是多年来最需要更改的部分,但这些部分不在文档中。

然后记录所有剩余的内容。


5

我认为处理大型代码的最佳方法是自上而下的方法。尝试先了解全局,然后逐步逐步深入了解各个组成部分。

现在,在每个挖掘层次上,请他将需要最注意的部分放在优先位置。让他尽可能多地向您解释,但请务必自己记录下来。

自己记录的最好的部分是,当您稍后再回来时,当他向您解释时,您可以毫无疑问地回忆起自己所处的相同认知状态。您可以更轻松地了解比别人没有写过。以我的经验,两个人记录相同的代码不会产生相似的文本。

我想这也解决了您的“内容和方法”问题。当他向您解释所有内容时,您可以自行决定返回代码时希望记录的内容,并且仅记录那些部分。

这个想法是首先(在他在场的情况下)完全理解代码,然后编写/执行所有可能使您稍后(在他不在的情况下)使用它的方法。

通过完全理解代码,我的意思是您需要了解全局-以及每个组件与该全局的关系。我发现跟踪每件作品的总和特别有用。不要试图孤立地理解任何东西-永远不要忘记它的上下文。

最后,完成上述操作后,请主动进行控制。确定自己需要进行单元测试的范围。需要(或可以)优化哪些部分,如何重构某些组件,等等。请相信,如果您了解系统,那么在他离开后就可以做出所有决定。


您应该如何记录?纯文本 ?维基?源代码中的注释?
2011年

任何可以使您恢复编写文档时对代码的理解的东西。
treecoder

5

我为你感到。

一些建议:

  1. 记录您与即将离开的程序员的每次对话!
  2. 寻求“大”问题背后的动机。理解API是件好事,但是请您深入了解内部决策-为什么将代码按原样分区?有什么责任。
  3. 尽力真正地学习代码。当您承担维护和支持职责时,有时会面临“不断学习代码”的压力。如果可以,请抗拒,并认真研究代码。
  4. 寻找方案。您知道API-查看代码的行为。我想到的一个例子是传真模块。作为API的用户,您必须先准备好页面图像,然后向代码发送命令以传输页面。让即将离任的程序员与您一起在代码上进行跟踪,以了解这种情况如何进行。然后,当然要转到“接收页面”方案。
  5. 80/20-首先尝试涵盖更常见的情况。
  6. 考虑重写。如果代码陈旧且接口定义正确,则可能是技术已发生足够的变化以证明其合理性。
  7. 我讨厌这样说,但是考虑找一份新工作。

我喜欢每次谈话都用录音的想法,这样在他离开后我可以回到他原来的话。建议#7,但是,不是一种选择;-)
PersonalNexus,

3

如果您想要合理的文档来轻松购买我在Delphi项目中使用过的Pascal Analyzer(PAL)的副本,那太好了-他们现在可能已将文档功能分解为我不熟悉的产品(Pascal Browser)因此您可能必须同时购买两者(<300美元),但PAL是了解在何处使用变量,从etc等处调用函数以及了解代码中各种潜在问题的好工具。

使用PAL可以了解代码的结构,并且如果我的经验可以继续的话,还可以列出大约1000项建议的改进。仔细研究清单将提高代码的质量,大大简化代码,并使您的生活更加轻松。Delphi本身支持最新版本(最近5年左右)中的重构。您确实需要将所有内容都包含在dpr文件中,以使其在我执行此操作时能够真正正常工作,因此请记住这一点。

如果您要进行单元测试,请下载DUnit并开始使用原始编码器创建一些测试-这可能是至少使用部分时间的一种建设性方式。


2

尽管您没有提到后端数据库,但是假设有一个后端数据库,您应该

  1. 获取记录的数据模型,尤其是列和PK-FK
  2. 设置一个sql跟踪并记录所有在使用该应用程序时触发的查询。查询的执行顺序将使您对应用程序的流程有个很好的了解,并有助于调试

总的来说很不错,但是在我的特殊情况下没有数据库。
PersonalNexus

1
可能会对其他人有所帮助
NRS

2

在同样的情况下,我们的建筑师搬到澳大利亚,留下了过去8年他在公司工作时留下的很多遗产。他本人是从承包人以前的建筑师那里继承下来的遗产。

您和其他人已经提到了优点,但这是我们在他离开后面临的问题,可能是您可以做得更好...

1)(技术人员)与他打交道的客户的详细联系方式。

2)他购买软件许可证,每年需要更新的密钥所用的帐户以及更新许可证的过程/成本。

3)与您的产品集成的第三方软件库/组件和产品的安装文档。我们奋斗了4天,以找回一台由于IT清理空间而丢失的机器,并将错误的指令传递给他们。

4)他用于将源代码存放到软件存放公司(例如托管公司)的文件/步骤。

5)名单仍然很长,但可能对您也不适用

我也不知道这是否是第一次。对我而言,我曾与5/6位雇主合作,并且总是继承带有不良文档或根本没有文档的代码。因此,连同所有文档一起保持积极:)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.