在大型项目工作期间,很快就会转移到一个已经处于维护阶段的项目。你最终会拥有一个巨大的代码C / C ++代码库,并没有太多关于设计的文档最后一个可以给你一些关于代码的知识转移的人已经离开了公司并且增加了你的恐惧,没有足够的时间来熟悉代码并开发对整个模块的理解。在这种情况下当您需要修复模块上的错误(核心转储,功能,性能问题等)时,您将采取什么方法?
所以问题是: 在尝试修复错误时,调试不太熟悉的C / C ++代码库的常用步骤是什么?
编辑:Enviornment是Linux,但代码也在Windows上移植,因此对这两者的建议都会有所帮助。
答案 0 :(得分:20)
如果可能,从main()到有问题的区域逐步执行,并按照执行路径执行。在此过程中,您将很好地了解不同部分如何共同发挥作用。
使用静态代码分析工具(如CppDepends甚至Doxygen)来确定模块之间的关系并能够以图形方式查看它们也会很有帮助。
答案 1 :(得分:8)
使用笔和纸,或一般的图像/图表/图表来确定哪些部件属于哪里并绘制一些箭头等等。
这有助于您构建和查看图像,随着您对图像的熟悉程度,图像会在您的脑海中得到改善。
我使用类似的方法攻击一个地狱般的系统,这个系统有10个单身人士,彼此相互包容。为了适应一切,我不得不重新绘制几次,但在你面前看它有帮助。
在构造依赖关系图时使用Graphviz可能也很有用。这样你只需列出所有内容(在文本文件中)然后该工具将绘制(通常难看的)图片。 (这就是我为上述系统中的#include依赖所做的事情)
答案 2 :(得分:6)
正如其他人已经建议的那样,编写单元测试是进入代码库的好方法。这种方法有许多优点:
它可以让你测试你的 关于代码如何的假设 作品。添加通过测试证明 那是你的假设 你是一小段代码 测试是正确的。更多 通过你写的测试,越好 你了解代码。
重现的失败单元测试 你要修复的bug会通过 当你修复bug时你就知道了 你成功了。
您编写的单元测试充当 未来的文件。
你写的单元测试行为 回归测试,因为更多的错误 固定的。
当然,将单元测试添加到遗留代码并不总是一件容易的事。令人高兴的是,一位名叫Michael Feathers的绅士写了an excellent book on the subject,其中包括一些关于在没有单元测试的情况下向代码库添加测试的“食谱”。
答案 3 :(得分:5)
一些指示:
答案 4 :(得分:3)
我还没看到三件事:
编写一些使用库/接口的单元测试。证明/验证您对它们的理解并促进其可维护性。
有时候创建一个特殊的断言宏来检查其他工程师的假设是否与你的一致是很好的。你可以:
重构也可以提供帮助。难以阅读的代码是一种迹象。
答案 5 :(得分:2)
第一步应该是尝试阅读代码。尝试查看bug所在的代码。遵循从主要到该点的代码,并尝试看看可能出错的地方。阅读代码中的注释(如果有的话)。通常,函数名称很有用。了解每个功能的作用 一旦了解了代码,就可以开始调试代码了。将断点放在您不理解代码或您认为错误的位置。逐行开始遵循代码。调试就像性。最初很痛苦,但慢慢开始享受它。
答案 6 :(得分:2)
cscope + ctags。如果你给他们机会,这些工具将成为你不可或缺的工具。虽然,像Visual Studio这样的IDE也可以很好地使用代码浏览工具。
在像你这样的情况下,由于时间的限制,你会受到症状的驱使。我的意思是你没有时间重建大图/设计/架构。因此,您要专注于症状并向外工作,每次重建尽可能多的大局,以满足您对特定问题的需求。但是不要急于做出“本地”决定。有耐心看到做出高质量决定所需的大局。并且不要陷入创可贴综合症,即任何旧的修复都会起作用。您的工作是保留底层架构/设计(如果有的话,以及您可以发现它的任何程度)。
一开始这将是一场斗争,因为你的思想过度“狩猎”。但很快就会出现设计/架构中的主题,所有这些都将开始变得有意义。想想,不思考,草蜢:)
答案 7 :(得分:1)
你必须有一个完全可靠的IDE,它有很多debbugging工具(断点,手表等)。熟悉大量代码的最佳方法是使用它来查看数据如何从一种方法传递到另一种方法。此外,您可以对代码进行反向工程,以便可以看到类的关系。 :D祝你好运!
答案 8 :(得分:1)
while (!codeUnderstood)
{
Breakpoints();
Run();
StepInto();
if(needed)
{
StepOver();
}
}
答案 9 :(得分:1)
对我而言,只有一种方法可以了解流程 - 互动。确定流程/系统的接口。然后确定输入/输出关系(这些步骤可能不是线性的)。一旦你这样做,你就可以开始修改代码,因为你知道它应该做什么,那么它只是找到“它是如何实际完成的”。但对我来说,了解系统的界面(不一定是用户界面)是关键。说穿了 - 不要先触摸代码!!!
答案 10 :(得分:1)
不确定C / C ++,但来自Java和C#,单元测试会有所帮助。在Java中有用于单元测试的JUnit和TestNG库,在C#中有NUnit和mstest。不确定C / C ++。
阅读Martin Fowler,Kent Beck等人的着作“重构:改进现有代码的设计”。在那里会有很多提示,我相信这会有所帮助,并为您提供一些改进代码的指导。
一个提示:如果它没有破坏,请不要修理它。如果它工作,不要试图修复一些库或真正复杂的功能。专注于存在错误的部分。
编写单元测试以重现代码应该工作的场景。测试最初会失败。修复代码,直到单元测试成功通过。重复:)
一旦大部分代码,过于复杂而无法手动调试和修复的重要部分都在自动化单元测试中,您将拥有回归测试的安全工具,这将使您在更改时更有信心现有的代码库。
答案 11 :(得分:1)
我并不试图按照许多人的建议来概述整个系统。如果有需要修复的东西,我会学习代码的最小部分,我可以修复bug。下一次出现问题时,我会更熟悉一点,而且我会学到一些东西。最终我能够支持整个社交。
如果管理层建议我对我不熟悉的事情进行重大更改,请确保他们了解时间尺度,如果事情非常混乱则表示重写。
答案 12 :(得分:1)
通常,相关程序会产生某种输出(日志,控制台打印输出,对话框)。
printf( "Calling xxx\n" );
,所以你可以准确地指出问题开始的位置。现在你可以看到你拥有的玩家并开始分析你是如何到达错误的地方的。
希望调用堆栈上方法的名称比 a , b 和 c (看到这个)更有意义,并且那里是某种评论,方法文档比calling a
更有意义(多次见过)。
如果来源记录不完整,一旦弄清楚发生了什么,不要害怕留下你的评论。如果程序设计允许它为您修复的问题创建一个单元测试。
答案 13 :(得分:1)
感谢您提供了很好的答案,还有很多要点。我已经多次研究过这种情况,这是我遵循的常规程序:
答案 14 :(得分:0)
您可以尝试使用GNU cFlow工具(http://www.gnu.org/software/cflow/)。 它将为您提供图表,绘制程序内的控制流程。