写程序,从看懂报错开始

【杂谈】写程序,从看懂报错开始

从编译报错,到程序运行出错,经常会收到各种各样的求助或者反馈,有的是使用我的device源码编译系统出错的,亦或者是自己发布的ROM或软件什么的出现问题用户反馈的,还有作业之类的上机题出错的……

所有的反馈也好,提问求助也好,有一样东西是必须的,那就是日志,编译出错得提供编译日志的出错部分,系统运行故障等需要抓取log一并提交,这似乎是不需要再多说的内容,然而在接二连三收到各种各样无效的报告、无效的求助、以及日志已经指名的显而易见的错误和异常原因以后,我才发现,原来有不少的开发者(或学生)并不具备基本的对日志进行判读的能力。本文也将对此进行进一步的讨论。

什么是无效报告/求助

       总的来说,无效的报告和求助,通常是由于求助者对问题的阐述不清而导致的,比如求助时为能够在求助中说明其诉求,问题的情境等,错误报告为能够说明清楚异常出现的情境和异常的复现方法等等。然而在更多情况下,无效反馈的产生原因是提交了完全没有价值的日志,甚至是重要问题完全没有日志文件。

 

无效反馈实例与正确反馈做法

下面我将举若干个由于日志问题而导致反馈无效的例子,并会逐个讨论其正确的反馈方式:

对方:我用了你提供的device和内核源码 ,但是编译的时候报错了。

我:日志呢?什么错误

对方:make[3]: *** [xxx] Error 2

make[3]: *** No rule to make target `xxx’, needed by `xxx’.  Stop.

make: *** Waiting for unfinished jobs…

我:没了?

对方:是的

我:……

这里反馈方提供的日志,虽说是导致编译停止的直接原因,但并不是编译出错的根本原因。其根本原因的错误输出呢,在前面呢。总所周知make的话是根据Makefile进行编译的,按照预定的内容去调用编译器或者是其他程序去执行相关的编译操作,并且监测调用的程序返回的结果(main函数的返回值)若是0则是执行成功,若是非零值则会判断为程序执行错误,进而返回如下错误:

make: *** [MODULE_NAME] Error RETURN_VALUE

在这种情况下仅仅是看make抛出的错误提示对排错的帮助并不大,在很多情况下是毫无帮助的,而真正由编译器抛出的错误信息,往往在前面。通常情况下向上翻查就可以找到原本错误的信息了。但是在多线程编译下,某一线程出错时可能会导致错误信息被其他线程的输出所淹没,比如曾经有一个模块在编译的时候出错,此时电脑又在编译着内核,结果等到编译终止时,错误信息早已被编译内核的输出所淹没了。因此,make的话在出现错误时,最好使用单线程重新编译一次,再把出错有用的日志提交。

 

用户:湖南电信用你的ROM有短信音啊。。。

我:请提交log。

(很久以后)

我:人呢?……

先科普一下,在以前开发电信的设备时,短信音是一个特别烦人的东西,这个东西的表现为在特定基站下(据说是中兴的基站?),电话在收到短信后,其听筒会不断发出奇怪的脉冲式类似蜂鸣器的声音,并持续不断直到用户重启设备,这种声音当初我找到了解决方法并写成RIL的framework层模块,用于解决该问题。

对于程序系统错误,尤其是非显而易见的,有强烈地域性的bug,是无论如何都要提供log的,而Android的Log分为两层,一是logcat产生的日志,二是dmesg日志,通常情况下只需要提供logcat的日志即可,由于logcat日志产生速度极快,有时候用的tag也不一定是Error或者Debug,因此通常要求用户提供完整的Log,再由开发者这边筛选。当然,这一类的系统级的bug在有官方运营维护的系统中通常有可视化的一键bug提交机制,这就不需要用户操心了。但是如果使用的是第三方个人或小团队维护的系统的话,就需要用户手动去提交bug了,至于不提交log的反馈,国外开发者社区有一句很有话,那就是“Logcat or GTFO”

直接放截图吧,知乎看到的一个提问:

这个情况下呢,提问者通常的确是提供了可以判别错误的bug了,但是这个编译的错误信息,提问者似乎未对代码进行任何思考就选择求助。

对于编译错误信息的输出,我举几类常见错误代码及其输出为例子吧。

案例一:

源程序:

#include<iostream>

int main(){

cout<<“Hello World!”<<endl;

return 0;

}

输出:

test.cpp:3:5: error: use of undeclared identifier ‘cout’; did you mean

      ‘std::cout’?

cout<<“Hello World!”<<endl;

    ^~~~

std::cout

/Library/Developer/CommandLineTools/usr/bin/../include/c++/v1/iostream:54:33: note:

‘std::cout’ declared here

extern _LIBCPP_FUNC_VIS ostream cout;

                                ^

test.cpp:3:27: error: use of undeclared identifier ‘endl’; did you mean

      ‘std::endl’?

cout<<“Hello World!”<<endl;

                          ^~~~

std::endl

/Library/Developer/CommandLineTools/usr/bin/../include/c++/v1/ostream:1032:1: note:

‘std::endl’ declared here

endl(basic_ostream<_CharT, _Traits>& __os)

^

2 errors generated.

错误信息是显而易见的:erroruse of undeclared identifier ‘cout’ did you mean  ‘std::cout’?

中文:错误:使用了未定义标识符cout,你是不是指std::cout?

编译器都画公仔画出肠了,就是漏了声明使用那个命名空间,然而这种错误却有不少人选择上网求助……

 

样例二:

程序:

#include<iostream>

using namespace std;

int main(){

cout<<“Hello World!”<<endl;

int a

return 0;

}

输出:

test.cpp:5:10: error: expected ‘;’ at end of declaration

int a

         ^

;

1 error generated.

同样的,也不说什么了,漏了个分号,都提示成这样了,但是网上还是能够找到一大堆这样的求助……

也许,是英语不过关?还是要提高你们的英语知识水平啊。

失败恐惧症?

       提问上述这些显而易见的错误的,通常情况下是一些初学者提出的问题,对于初学者来说,在写程序的时候会对编译出错怀有一种恐惧之心,而不愿意去自己面对错误,进而转向其他人去求助。这某种程度上似乎是失败恐惧症(Or Error恐惧症?)的体现。初学者其实更应该去学习阅读编译器的错误输出,并试图独自进行troubleshoot,这才能够有利于提升自己的水平。

总结

当你在反馈bug、遇到问题询问开发者、或者是纯属是程序设计类作业遇到问题时,学会阅读日志,学会找到日志与错误相关部分尤为重要,也是你能够从他人得到有用反馈的关键。

 

denghaoqing.com与HQ杂谈同步发文

通过CC-BY-NC-SD协议授权规范转载

 


若您觉得这些内容对您有帮助,希望您能为我提供捐助,以便让我们更好地运营下去。

Bitcoins donations ar accepted

3MNvM1gW2sLPm3HTcM2Zp4GdaHDHeFkjMh

发表评论

电子邮件地址不会被公开。 必填项已用*标注