打印到控制台/标准输出是一种好的调试策略吗?


11

假设我们有一个像这样的函数:

public void myStart()
{
    for (int i = 0; i<10; i++) myFunction(i); 
}


private int myFunction(int a)
{

    a = foo(a);
    a = bar(a);
    return a; 
}

private int foo(int a)
{
    //do something here

    //something gnarly here

    //etc
    return aValue;
}

private int bar(int a)
{
    // do something here
    //return aValue;
}

现在无论出于何种原因,我们的代码都无法正常工作。可能是抛出错误,可能是返回了错误的值,或者它陷入了无限循环。

第一年的程序员要做的第一件事就是打印到控制台/标准输出(在学习使用调试器之前已经学会了如何打印Hello World)。

例如,要调试此代码,他们可以执行以下操作:

private int myFunction(int a)
{
    print("before foo: a=" + a); 
    a = foo(a);
    print("before bar: a=" + a);
    a = bar(a);

    return a; 
}

private int foo(int a)
{
    //do something here
    print ("foo step1: a=" + a); 

    //something gnarly here
    print ("foo step2: a=" + a + " someOtherValue="+ someOtherValue + " array.length= " + someArray.length()); 
    //etc
    return aValue;
}

private int bar(int a)
{
    // do something here
    //return aValue;
}

现在,他们运行代码,得到较大的控制台打印结果,可以跟踪出现问题的地方。

当然,另一种方法是设置断点,并在每个点处逐步执行代码。

打印到控制台的一个主要优点是,开发人员可以一次性查看值的流程,而无需单击步骤等。

但是缺点是,您的代码中充满了所有这些需要删除的打印语句。

(是否有可能告诉调试器仅将某些值打印到日志中?然后可以在不实际修改代码的情况下轻松地添加或删除断点。)

我仍然将控制台打印作为主要的调试方法,我想知道与其他方法相比,这种方法有多普遍/有效。


1
您需要学习如何使用调试器并使用适当的日志记录框架。它比打印到控制台(并不总是存在)要快乐得多。

7
学习使用调试器很重要,但是,在许多情况下,printf调试是唯一可用的调试方法。
whatsisname 2014年

Answers:


26

打印语句和调试器不是互斥的。它们只是您可以用来查找/识别错误的不同工具。有些人声称自己永远不会接触调试器,有些人在编写的代码中的任何地方都没有一个日志记录/打印语句。我的建议是,您不想加入其中任何一个。

而是学习使用日志记录并学习使用调试器。有了经验,您(几乎无需考虑)就会选择正确的工具,并准确高效地完成工作。没有经验,有时您会彼此取舍,也许您会花更多时间在变量上四处寻找或浏览日志文件,但这只是学习过程的一部分。

因此,回答您的特定问题。是。使用打印来跟踪执行是一种良好且广泛使用的调试策略。然而...

与其使用打印语句,不如考虑使用日志记录框架。日志记录框架具有日志记录级别的概念,因此您可以添加一堆日志消息,但可以为每个消息级别选择一个级别。当您的应用程序在正常条件下运行时,您的级别将为ERROR或WARNING,以便仅报告重要的内容。但是,当您遍历代码并需要了解执行流程时,可以将记录器更改为INFO或DEBUG,现在代码中已有的所有“打印”语句将报告其他信息。

使用日志记录框架...

  1. 完成后,您将不需要删除所有打印件。
  2. 您在代码中留下的打印内容,可以在将来帮助您或任何其他开发人员调试相同的代码
  3. 您将可以在现场调试此代码,而不必每次都仅添加已删除的打印件就重新构建它。
  4. 您将能够将日志消息重定向到任意位置,而不仅仅是控制台。他们可以转到文件,系统日志,数据库,套接字等

更新:最后,我刚刚注意到您在问,“是否有可能告诉调试器仅将某些值打印到日志中”。取决于您使用的调试器。许多现代的调试器都允许您定义一个动作,以在遇到断点时调用。在某些情况下(我已经在VS和WinDbg中完成了此操作),可以指定“打印并继续”。Visual Studio称它们为“跟踪点”而不是“断点”


+1为第一段。调试器和日志解决了两个非常不同的问题。
Blrfl 2014年

1
但是您是否不同意将日志语句保留在代码中会使其变得难看呢?
dwjohnston 2014年

1
取决于您保留哪种日志语句。我刚刚找到并删除了这个:“ logger.error(“ WHAT”)“。否则,我会像其他代码一样对待日志记录代码。对其进行格式化,使其外观精美并提供更多信息。是的,每隔一行撒上一份印刷声明实在太多了,有时候我不得不求助于此。但是通常,在整个文件中具有10-20条日志语句(文件/类的大小合理且不巨大)根本不是一件坏事。
DXM 2014年

我得到的,如果某些错误留下一些日志报表可能,如果你有,因为他们已经习惯了,所以你可能要被记录各种重要变量生产系统是有用的,例如不会突然出现,你可以看到你的产品立即发生了什么环境。但是,就发展而言,这似乎很混乱。例如,您正在编写解析函数,而您的正则表达式还不太正确-因此您放入了一系列日志语句,只是为了检查正在发生的事情,并最终对其进行整理。我不认为它增加值前进离开他们进来
dwjohnston

1
@Izkata:所有这些都取决于特定错误的细节以及代码已执行的日志量。我要说明的要点是,拥有日志记录语句并将它们置于不同级别始终是有价值的:ERR-> WARN ...-> DEBUG。如果您需要更多特定用途的东西,请务必确保开始添加打印语句。但是我对使用“打印”作为日志记录语句的人的答案将始终切换到框架并开始使用它。
DXM 2014年

3

日志记录/打印和调试器是互补的技术,它们具有不同的长处和短处-最好同时使用两者。但是总的来说,我想说调试器在大多数情况下是上乘的工具,应该首先使用,日志记录/打印仅用于实际上更擅长的事情。

调试器的优点:

  • 您可以检查整个程序状态,而不仅仅是检查您认为事先要打印的值。通过将反馈周期缩短到不到一秒钟,可以大大加快调试过程。在花费一些时间来修复错误时尤其重要。
  • 您可以查看调用的来源,甚至可以检查堆栈跟踪中的值。
  • 您可以(在某种程度上取决于语言/平台)调试由于您只有编译的二进制或字节代码而无法轻易修改的代码。

记录/打印的优点:

  • 它不需要特殊的调试版本或配置。它始终有效。
  • 可用于分析难以重现且很少发生的错误
  • 可以用于分析与时序有关的错误,其中调试器导致的暂停可以使错误轻松消失。
  • 给您永久信息,您可以在闲暇时进行分析。您可以在程序流程中不同点的输出之间来回跳转。

日志记录的另一个优点是,当您仅在代码遍历中遇到该问题时,实际上可以使用一些关于如何到达那里的回溯信息。大多数调试器没有反向按钮,但是可以记录调用更高级别函数的值,并且通常至少可以使您更接近可重现的小情况。
Christopher Creutzig 2014年

1

以某种方式打印到标准输出可能是调试代码的好策略,例如

  • 我使用很多打印语句来查看代码不同级别中发生的情况,特别是如果我不完全理解该错误时

  • 也许该错误没有任何详细的信息,可能使我指出正是代码的哪一部分导致了问题。

但是,您需要习惯调试工具,根据使用的语言或平台,有很多工具可供使用。

另一种方法是记录日志,我在Android应用程序上工作时始终记录错误,还带有异常处理,因此可以轻松记录堆栈跟踪或引发异常的错误消息。


3
这篇文章很难阅读(文字墙)。您介意将其编辑为更好的形状吗?
蚊蚋

我使它更具可读性。对不起,以前的!
Plaix 2014年
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.