如何在不首先进行深度重构的情况下修复遗留应用程序中的错误?
有一个中型遗留应用程序(20K LoC,150 个 java 类)。设计和代码的质量非常低,没有单元测试,没有文档。然而它有效。
您受雇维护该应用程序。有一个非常具体的正确报告的错误列表。很难理解如何修复它们。时间有限,您应该在几周内开始解决这些错误(您根本没有时间首先进行深刻的重构)。如何做到这一点?
There is a mid-size legacy application (20K LoC, 150 java classes). The quality of design and code is very low, no unit tests, no documentation. However it works.
You're hired to maintain the application. There is a list of very specific properly-reported bugs. It's very difficult to understand how to fix them. Time is limited, you should start solving these bugs within a few weeks (you simply don't have time for a profound refactoring first). How to do this?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
从第一个错误开始。也许是最简单的,也许是最高优先级的。任何对您的商店有意义的东西。
不要修复它。
而是找出它无法正常工作的原因。根据您的描述,它不太可能采用某种简洁、漂亮、可单元测试的方法;它可能在某个地方深藏着一些复杂的逻辑。找到它。
不要修复它。
想办法找出罪魁祸首。也许是提取方法;也许是提取类;也许其他一些技术会有所帮助。但把它拿出来,独立于其他代码。仍然失败,只是更容易使用。现在,编写一个测试来演示失败。
好的,现在修复它。
你做了什么?您已经隔离了问题,修复了它,使一小部分代码更容易处理,并开始进行适当的测试覆盖。
起泡沫,冲洗,重复。
Start with the first bug. Maybe the easiest, maybe the highest-priority. Whatever makes sense for your shop.
Don't fix it.
Instead, figure out why it's not working correctly. From your description, it's not likely to be in some neat, nice, unit-testable method; it's probably deep in some convoluted logic somewhere. Find it.
Don't fix it.
Work out a way to extract the culprit. Maybe it's Extract Method; maybe it's Extract Class; maybe some other technique will help. But get it out there, independent of other code. Still failing, just easier to work with. Now, write a test that demonstrates the failure.
OK, now fix it.
What have you done? You've isolated the problem, fixed it, made a small section of your code easier to work it, and begun to get test coverage in place.
Lather, rinse, repeat.
我觉得你的处境不太好。
我首先要“重构”这项工作。向你的老板明确表示情况非常困难。你还应该明确表示,既然你不应该为制造混乱负责,那么他现在需要承诺不会让你为失败负责,但你会尽最大努力。如果他在这种情况下不愿意预先做出承诺,那么我会去找另一份工作。
现在的问题是如何进行。无论您做什么,您都会发现代码中的问题,并且随着您的操作,问题会变得更容易。
首先,您必须浏览代码并获得一些结构感。无论您认为自己理解了什么,都可以插入评论;如果您不这样做并且很重要,请插入带有问题的评论。如果可以的话,添加一个断言。如果这些新断言被打破,如果它们很容易修复,那就这样做;如果没有,您可以通过简单地删除它们或将它们变成问题评论来对它们进行分类。这些评论、问题和断言至少会记录你(不)理解的底层细节。
我还会得到一个工具,可以让您可靠地应用重命名重构(强调后者:你不能让这些破坏代码比现在更糟糕)。疯狂而虔诚地重命名将有助于稳定代码和您的词汇,并摆脱坏名字。您可以在浏览/处理代码的同时执行此操作,只需很少的时间成本,并在可读性方面获得巨大的回报。
为了找到错误的(潜在)来源,我会使用测试覆盖率工具。这样的工具会告诉您运行“测试”时执行了哪些代码。创造性地使用这样的工具,您可以运行一个“测试”(手动或自动,视实际情况而定)来测试错误;代码点亮了测试覆盖率工具必须在某个地方包含错误。您可以运行其他不显示该错误的“测试”;他们执行的错误跟踪中常见的代码可能不包含该错误。
问题是如何计算执行代码中的这种“差异”?一些测试覆盖率工具将帮助您确定这一点。我的公司(Semantic Designs)提供这些:适用于多种语言的测试覆盖率工具,正是这样做的。通常,测试覆盖向量用于显示代码,并用覆盖层显示覆盖范围。我们的工具将让您处理独立的测试覆盖率集:相交、比较、并集等,以了解生成向量的测试之间的关系,并显示叠加在代码上的结果。通过这种方式,您可以直接查看错误的测试覆盖率向量与非错误的测试覆盖率之间的差异。
一旦您知道错误在哪里,就可以在相关代码中添加更多断言并再次运行错误生成测试。触发的是您正在接近的指标。
我最初会避免需要认真重构代码才能修复的问题,因为您面临时间压力。 (显然,除非您了解问题所在,否则您无法很好地了解这一点;探索是不可避免的成本)。如果你能及早解决一些问题,你就会购买政治积分。您可以利用它们来获得更多时间,从而获得进行更多重构的机会。
I don't think you have a good situation.
I'd start by "refactoring" the job, first. Make it clear to your boss that the situation is very difficult. You should also make it clear that since you weren't responsible for making the mess, that he needs to commit now to not making you responsible for not succeeding, but that you will give it a best effort. If he isn't willing to make that commitment up front under the circumstances, then I'd find another job.
Now the question is how to proceed. No matter what you do, you have find the problems in the code, and make it easier to do so as you go along.
First, you'll have to skim the code and get some sense of structure. Whereever you think you understand something, insert a comment; where you don't and it matters, insert a comment with a question. If you can, add an assertion. Where these new assertions break, if they are easy to fix do so; if not, you can triage them by simply removing them or turning them into a question-comment. These comments, questions and assertions will at least record the low level details of what you (don't) understand.
I would also get a tool that will let you apply rename refactorings reliably (emphasis on the latter: you can't have these breaking the code worse than it is). Renaming furiously and religiously will help stabilize the vocabulary both for the code, and for you, and get rid of bad names. And you can do this while skimming/working on the code, at very little cost in time, and big payoff in readability.
To find the (potential) source of the bugs, I'd use a test coverage tool. Such a tool tells you what code got executed when you run a "test". Using such a tool creatively, you can run a "test" (manual or automated, as practical) which exercises a bug; the code lit up the by test coverage tool must contain the bug somewhere. You can run other "tests" which do not exhibit the bug; the code they execute which is common with the bug trace likely does not contain the bug.
An issue there is how can you compute this "difference" in the executed code? Some test coverage tools will help you determine this. My company (Semantic Designs) offers these: Test Coverage tools for many languages that do precisely this. Normally test coverage vectors are used to display your code with overlays showing the coverage. Our tools will let you process independent test coverage sets: intersected, diff'd, union'ed, etc. to understand the relation between the tests that produce the vectors, and display those results displayed superimpmosed over you code. This way you can directly see the diff between the test coverage vector for the bug, and test coverage for non-bugs.
Once you have an idea where the bug is, put more assertions into the relevant code and run the bug-generating test again. The ones that trigger are indicators you getting close.
I'd initially avoid problems that required serious code refactoring to fix, since you are under time pressure. (You obviously can't know that very well until after you have sense of where the problem is; exploration is an unavoidable cost). To the extent that you can fix some of the problems early, you will buy political points. You can use them to gain more time, and therefore the opportunity to refactor more.