有printf,asserts,编辑和继续,日志框架等等,你最喜欢哪个?
ASSERT(断言)是必须的。
我的个人代码库有30万行(不包括注释),高度分离和重用,其中大约15%(猜测)是模板,5万行代码是测试代码。
如果一个习惯用语被重复使用,则将其制作为函数/方法。个人认为,容易剪切和粘贴是魔鬼的发明,旨在膨胀代码并传播缺陷。
该库的大约4%是ASSERTS和调试代码(非常少的printf和几乎所有输出都是排队输出到低优先级任务的cout流中,因为屏幕IO太昂贵,因此会改变时间)。也许50%的asserts是为了保证类不变量和方法执行后条件的正确性。
当我重新访问可能匆忙或只是在接口/对象耦合设计中犯错的代码时,我会无情地重构,比如说方法的主体对象真正属于对象对象,并且该方法属于最初的对象对象之一(参数对象)。宽松使用assert似乎能保护我免受愚蠢的错误,如果我进行了实质性的重构。这种情况并不经常发生,但是有时候确实会发生。
我有一个DEBUGGING宏,它的作用类似于ASSERT,因此我可以将代码包含在其中:
DEBUGGING(... code....);
在非调试版本中不会编译它。
我不使用供应商提供的assert。我的assert不会中止和核心转储,它们只是弹出一个消息框并调用调试器。如果这是新代码并且方法是const方法,则能够返回到该方法,然后使用相同的参数集重新执行它(该方法)非常有用。有时即使某些数据发生了更改也与问题无关,人们也可以重新调用以获得知识收益。
我绝对讨厌命令行调试器。就像回到25年前一样-最好使用电传打字机和2400波特率线路。我需要和想要一个完整的IDE,可以右键单击数据结构并打开、关闭、追踪指针执行方法等等等等。
我会逐行检查我的新代码,并检查每个(我的)变量是否有预期的行为。在这里使用可以突出更改的IDE是非常宝贵的。要通过GDB做到这一点,必须成为一位具有卡纳克大师的记忆力的音乐家。
对于新开发,当遇到异常情况时,我还尝试捕获流数据/消息数据。这对UDP服务器特别有用,经常可以提前开始可重现性。
我也喜欢拥有可以“包围应用程序并驱动它并从中消耗并进行验证”的模拟器。(联合/耦合源/汇模拟器)我的几乎所有代码都是无头或至少与人类交互与功能无关,因此“包围”应用程序通常是可能的。我认为拥有理解测试数据创建非常重要且测试数据收集构建为能够演变成全面回归/冒烟测试套件的良好支持和管理非常重要。
我曾经喜欢将操作系统调度量子设置得非常低。对于多线程应用程序,这种短的量子更容易带出线程错误。我特别喜欢使用许多线程来驱动线程安全的对象方法-十几个甚至上百个线程。一般来说,如果应用程序是人驱动的,则无法在适当位置测试线程安全对象。因此,确实需要自定义测试驱动程序,这些驱动程序处于更低(组件定向)级别。并且在这些测试中,asserts可以让您知道是否出现了问题。显然,这并不能证明代码是正确的,但确实会给出一些信心。
这些偏好也可能反映了我所担任的更具类库/可重复使用性的角色和观点。当您编写库代码时,通常没有“生产”问题,因为根据定义,库被广泛使用和经过充分测试。日志记录和那种历史记录似乎更加面向应用程序而不是面向库。
冗长:仅在调试低级别问题(如协议错误)时想看到的内容;可能被编译为发布二进制文件,因此你可以将一些不想让最终用户发现的内容放在这个级别
内部:比普通级别更低级别,在频繁有用但不需要一直查看的情况下使用
普通:默认输出级别,对于任何观察系统正常运行的人都有用的东西
问题:程序知道如何应对的运行时错误; 你可能选择在发布版本中运行此级别,而不是 普通
致命错误:像内存不足之类的“尖叫和死亡”类型消息
printf
来打印这条消息”的思路。它还作为一个清晰的提醒,在调试完成后删除打印输出。 - John Kugelman一般来说,printf函数非常有用。它们可以轻松地对程序进行切片,并且不需要除编辑器和编译器之外的任何工具。
响应措施:
使用一个好的调试器,尤其是集成的调试器。使用一个能够与您的代码进行可视化交互的调试器。
调试策略。熟悉您的工具。了解常见陷阱。演绎过程。使用假设和不同想法的分支。需要时返回以前的想法。跟踪您的调试进度。科学方法。
分析工具(例如Dependancy Walker、.NET Reflector)、内存转储、堆栈跟踪和日志。
预防措施:
小型增量构建。在活动开发期间,逐步添加到您的项目并测试每个新加入的部分。不要一下子写出一堆代码然后运行并轻率地修正每个错误。逐步添加程序的每个部分,并花时间修复所有问题。尝试使每个项目增量“原子化”。
多级记录,如其他人已经指出。从跟踪到致命错误等有一个严重性等级。您的生产应用程序应记录每一个操作以及其成功或失败的原因,但当不需要极端详细信息时,您也应该能够更改日志记录级别。日志应首先易于人类阅读。
故障安全记录。不要依赖您的日志记录机制来处理异常情况。始终有备选方案 - 事件日志、平面文本文件、最后一道防线的电子邮件 - 以备不时之需。
良好的源代码控制策略。至少按照定期时间表进行频繁的检查,以及针对任何和所有变化组进行检查,特别是广泛或可能破坏性的变化。
commands
来动态创建“printf”语句。只需确保最后一个命令是continue。#assume two breakpoints, 1 and 2
commands 1
silent
echo calling baz\n
set $print_foobar=1
continue
end
commands 2
silent
echo calling foobar\n
if $print_foobar
set $print_foobar=0
backtrace
end
continue
end
我最近爱上了这种技术,因为它让我可以为已经运行的代码创建printf()
语句。此外,虽然GDB脚本功能有限,但在决定要打印什么时,它可以做很多事情。到目前为止,我还没有发现一个命令部分不如printf()
好或更好的情况。
我推荐使用gdb进行调试,特别是当你只有一个核心文件时。
当你大致知道错误出现在哪里时,printf非常好用。但是当测试团队向你反馈一个核心文件和一个奇怪的问题描述时,能够分析核心转储将会为你的调试工作提供巨大的帮助。
在进行多线程操作时,我无法没有日志记录。在C++中进行日志记录时,我使用templog库。它允许多个严重性(有多糟糕?)乘以多个日志目标(谁可能对此感兴趣?),并结合尽可能多的过滤日志汇(将其写入何处?),因此您不会淹没在噪音中。它通过使用模板元素来帮助编译器消除多余代码,同时不会陷入assert(do_something_important())
陷阱,从而在效率方面走得更远。
另外,它非常小(我认为它分布在半打头文件中不到1kLoC),并且配有宽松的boost许可证,因此您不必依赖创建者来保持稳定。如果他们这样做-您可以自己维护它。
我只希望这些人能最终将当前代码所在的分支转换为新的主干。
通过跟踪,修改内存中的变量以达到某些晦涩代码分支。很少编辑并继续,由于某种原因我无法完全信任它保持健康状态,所以在更改后进行完整运行。
当无法跟踪时(例如,在Windows上使用gdb速度太慢,每次触发断点需要30秒),则使用printf。垃圾代码会扰乱多线程错误的时间,但有时这是唯一的方法。
反汇编调试器用于在没有调试信息的情况下调试发布版本(Olydbg在工作时很好用)。
适当的日志记录很好用,设置和使用需要付出努力,但在需要时非常有价值。
将崩溃的堆栈跟踪发送回家更好。
在需要的地方散布断言。
对于用户机器上的崩溃,使用Minidumps。(可重现的崩溃是最好的。如果不能依赖错误及时显示,那还能依赖什么呢?)