我的任务是维护和重构遗留 Java 系统。我目前使用 C# 和 .NET,虽然我熟悉 Java。
遗留系统使用 RMI,一种客户端/服务器架构,专为 1.4 JVM 设计。它用于 UI(据我所知)、Swing 和 AWT。
我的问题是:与我刚刚收到的代码库达成协议的最佳方式是什么?我正在考虑屏幕流程图、定义 RMI 调用之间的边界以及编写单元测试(针对可测试的位)。
当你收到一个不熟悉的代码库时,你会怎么做?
谢谢!
-贾罗德
我的任务是维护和重构遗留 Java 系统。我目前使用 C# 和 .NET,虽然我熟悉 Java。
遗留系统使用 RMI,一种客户端/服务器架构,专为 1.4 JVM 设计。它用于 UI(据我所知)、Swing 和 AWT。
我的问题是:与我刚刚收到的代码库达成协议的最佳方式是什么?我正在考虑屏幕流程图、定义 RMI 调用之间的边界以及编写单元测试(针对可测试的位)。
当你收到一个不熟悉的代码库时,你会怎么做?
谢谢!
-贾罗德
对于收到的任何新代码,我做的第一件事就是查看现有的单元测试。编写新测试通常是第二件事。
这在很大程度上取决于代码库的质量。实际上,您可以更狭义地定义它——这取决于代码的清晰程度以及评论的好坏(我写这篇文章是作为一个反复处于继承记录不佳的遗留代码库的人)。
代码库越差,你就越需要从外部推断它的功能——如果你不能弄清楚一个函数在做什么,你至少可以弄清楚 GUI 似乎暗示了什么它正在做的事情。拥有 RMI 接口可能是一件好事,因为它为您提供了 GUI 需要查看的简化图 - 从远程接口抽象是一个非常好的主意。
如果代码库真的很差,单元测试不太可能对您有所帮助,因为即使正确实施,您也可能正在测试错误的东西。我继承的最后一件事的一个例子:(故意删除了名称和描述 - 在任何情况下它们对原件都没有帮助)
public static int[] a (List<int[]> input){
... lots of opaque rubbish
return b(input);
}
public static List<int[]> b{ List<int[]> input)
{
... more crap....
return some horribly mangled list of int[];
}
事实证明,所完成的是按数组的第二个元素的值对数组进行排序。在这种情况下,测试b以使其正常运行将毫无用处。
帮助您保持理智的建议:
当然,如果代码库写得好,大部分这些都是不必要的,但无论如何你的工作都会容易得多。还有祝你好运。
帮助我处理对我来说是新的代码的一件事——这对于编写良好的代码来说不太必要——是在一两天内对其进行大规模重构,然后丢弃我的所有更改。这个过程帮助我理解代码的作用;使用代码可以帮助我理解它。它也开始教会我代码的哪些部分是脆弱的。
如果您有机会迁移到 Java 的较新版本,那么对所有集合进行泛化将有助于了解传递的数据类型。
当然,我是在测试实验室中安装软件并稍微使用它以了解它的作用之后执行此操作的。
编辑:考虑我的回答,启用所有诊断跟踪和日志记录,使用系统,然后研究日志也很有用。如果存在通信协议跟踪,那么查看此跟踪将有助于了解代码使用的通信协议,也许与相同测试的 Wireshark 跟踪有关。
另一个有用的迁移是从旧的并发库迁移到新的 Java 5(和 6)并发库。这将帮助您了解线程的位置以及它们何时启动以及何时关闭。
当然,对于不熟悉的代码库上的任何代码更改,我假设进行了适当的测试以确保没有任何损坏!然而,为了平衡这一点,我了解到在重构糟糕的代码之后,新引入的错误通常比重构之前存在的错误更容易找到。
收到新代码时我做的第一件事就是“尝试让它工作”!我的意思是:
首先安装它(根据安装说明,如果有的话)
然后熟悉它(通过阅读用户手册并运行一些重要的用例)
然后进行一些逆向工程以找出主要层。类和依赖项
然后我可以考虑编写新的测试用例(首先在接受级别,这对以后的回归测试很有用)
然后我给自己一些关于添加这个特性(即使它不是必需的)或改进现有特性的性能的“挑战”:这是挖掘现有代码库的好方法
当然,如果有一些已知的待处理错误/RFE,我会先处理这些
在您的特定情况下,我还将尝试记录 RMI 调用、执行什么调用或调用序列,并尽可能将其与使用级功能相关联。
此外(实际上这应该首先出现),要知道的重要一件事是该系统的主要目标(您的客户为什么要使用该系统?)。了解目标并牢记它们将避免您在维护代码的同时远离这些目标。
Mike Hill 参加了一个演讲,他在演讲中谈到了他用来处理不良遗留代码的过程。他称之为“微测试”,它基本上隔离了你想要测试的每一件事(在它自己的函数或小对象中),并围绕它编写测试。这些测试并不意味着在商业意义上断言事物——它们只是为了捕捉在被测行执行后系统所处的状态。通常,他会断言变量具有它们在调试器中的值。当然,这些测试最初会通过,但在他编写了足够多的测试之后,他将有效地获得系统的快照。
一旦这些测试到位,他就可以开始重构那段代码以尝试理解它试图做什么。他可以安全地这样做,因为如果他稍微改变函数(或对象)的行为方式,他的“微测试”就会失败。虽然这意味着他无法对设计进行重大更改,但他可以消除很多噪音,并且可以了解系统正在做什么。一旦他对代码的目标有了清晰的认识,他就可以开始改进它,发现错误等。
这方面的免费资源不多,这就是我不提供链接的原因。希望我已经设法描述足够给你一些想法。
经验告诉我,学习遗留系统有 3 个主要目标。
所有这三个部分都非常重要,并且有一些技巧可以帮助您入门。
首先,抵制只是通过 ctrl-click(或任何您的 IDE 使用的方式)围绕代码来理解所有内容的诱惑。您可能无法以这种方式将所有内容都放在脑海中,尤其是当每一行都迫使您查看多个其他类以了解它是什么时。
尽可能阅读文档;它通常可以帮助您快速获得构建后续所有内容的思维框架。
尽可能运行测试用例。
不要害怕问知道你是否有问题的人。诚然,您不应该将其他员工的时间浪费在无意义的查询上,但如果有些事情您根本不理解(这对于更多概念性问题尤其如此,例如,“将其实现为更有意义吗? ___”或其他东西),在你把事情搞砸并且不知道为什么之前找出答案可能是值得的。
当你最终开始阅读代码时,从一个合乎逻辑的“主要”位置开始,然后从那里开始。不要只是从上到下阅读代码,或者按字母顺序,或者任何东西(这可能很明显)。
带有 RMI 的 Java 1.4 不是遗留的,从某些标准来看这实际上是新的!
尽你所能熟悉事物所在的位置——单元测试、跟踪代码/调用、处理一些 UML 图等。
尝试从其中的一小部分开始并跟踪代码路径以熟悉事物的位置,或者为自己分配一些需要查看代码的小修复/改进。
构建并运行它将是我的第一件事。开始了解它试图解决的问题。
也许您可以将它导入到 JUDE 之类的 UML 工具中,并了解类如何交互。
编写 JUnit 测试是一个很好的建议。我想看看应用程序的分层程度。如果它很难测试,也许它太耦合了。单元测试会告诉你。如果您决定进行一些重构,它们还会为您提供安全网。
JDK 1.4?它的支持寿命已经结束。我还想看看代码是否至少可以在 JDK 5 下构建和运行,最好是在 JDK 6 下。也许您可以将其中一些 JUnit 测试放入 JMeter 并与 5 个同时用户进行快速的穷人负载测试。
如果您有一个数据模型,请将其拉入 ERWin 并开始查看表格、对象和屏幕是如何一起流动的。
当然你也可以试试单步执行。它很慢,但花一天时间浏览代码可能会节省一周的错误搜索......
您也可以尝试寻宝方法,列出每个功能并为其寻找代码......
首先浏览代码以了解其结构。然后,在调试模式下运行应用程序并运行几次。这将向您展示流程和结构。
使用 Netbeans 对类图进行逆向工程。