如果世界上有一个人能够保证一次写出来的代码是百分之百正确的,那么毫无疑问,他一定是世界上最优秀的程序员,没有之一。为什么要求代码写好过后要进行充分的自测(包括单元测试和集成测试)?就因为是人皆会犯错,是程序就会有bug。作为一名软件开发人员,必须要学会对程序进行测试,也就是要学会程序的调试。
一般而言,对代码的调试有以下几种方法:
第一,凭肉眼看。在开发阶段,我们编写的每一行代码都需要用我们的“火眼金睛”多审查几遍。如果要问,最好的代码调试工具是什么?我认为是人眼。不管是代码还是文档,在用工具检查之前,都需要先过了我们眼睛这一关。
第二,对代码进行编译,以发现语法错误。编译器能够帮助我们发现代码中存在的语法错误,但对于那些隐蔽性的错误(如逻辑错误等)无能为力。
第三,用代码检查工具(如Pclint等)来走查代码。如果代码编译通过,并不表示它就没有问题了。在学校的时候,我们一般认为只要程序能够运行就可以了。但在实际的软件开发项目中,程序能够跑起来,只是“万里长征走完了第一步”。用代码检查工具可以发现很多编译器无法发现的错误,如变量定义了未引用、不同数据类型之间相互赋值、函数未声明便被调用等。
第四,对代码进行调试。对于运行正常而输出结果不正确的程序,我们可以用设置断点并进行单步跟踪调试的方法来发现其中存在的问题。例如,在VC++ 6.0里面,可实现对代码的单步调试,并输出变量在某一步产生的值,可据此判断程序的逻辑的正确与否。
第五,对程序的日志文件进行分析。对代码的单步调试只在代码行数较少的时候比较适用,如学校教材上面的程序。但在实际的软件项目中,代码少则几千行,多则数万行,用单步调试的方法显然不恰当。为了跟踪某一变量值的变化,用该方法可能要花费几个小时,这对工作效率产生了严重影响。为了解决大程序文件代码调试问题,日志系统应运而生。在程序中的重要地方打印日志,之后对产生的日志进行分析,可找到对应代码的问题。因此,日志文件分析成了大型软件项目中代码调试的主要手段。
本文对日志相关内容进行详细的说明。
1.什么是日志文件?
在业务软件系统中大量使用日志,日志能够起到“按图索骥”的作用,它对于故障定位和系统正常运行维护具有举足轻重的作用。
日志文件是程序中写日志函数产生的记录程序执行情况的文件。写日志函数也是用C语言编写的,同C函数一样被调用。在恰当的地方调用该函数,可对整个程序的运行状况有一个全面的了解,方便对程序的跟踪调试。
2.有关日志等级和日志配置说明
(1)日志等级
事有轻重缓急,日志信息也有重要与不重要之分。一般按照重要程度,将日志等级分为几类。在作者参与过的软件开发项目中,共有7个等级,用宏定义表示如下:
//日志等级定义
#define LOG_FATAL (int)1 //严重错误
#define LOG_ERROR (int)2 //一般错误
#define LOG_WARN (int)3 //警告信息
#define LOG_INFO (int)4 //一般信息
#define LOG_TRACE (int)5 //跟踪信息
#define LOG_DEBUG (int)6 //调试信息
#define LOG_ALL (int)7 //全部
开发人员根据所要打印的日志的具体情况采用不同的日志等级。
(2)日志配置
由于不同产品程序行数、部署情况、实现功能等的差别,对日志打印的要求也不尽相同,因此需要有配置来控制日志的产生数量和显示情况。
在配置文件中,有一个专门的[LOG]配置段,其中的配置项如下:
[LOG]
;日志等级, 0-Fatal 1-Error 2-Warn 3-Info 4-Trace 5-Debug 6-All
LogLevel=
;每个日志文件的最大容量
LogMaxSize=
;是否输出该条日志在代码中的行数, 1-Yes 0-No
LogPosition=
其中,LogLevel用于控制打印日志的等级,代码中日志等级比配置值大的日志信息均不在日志文件中显示;LogMaxSize用于控制生成一个日志文件的大小的上限,超过该值后,便重新生成文件;LogPosition用于控制是否在日志文件中显示代码行数,方便将日志与代码对应起来。
3.如何调用写日志函数?
日志函数的调用遵循一般函数的调用规则。有两类写日志函数,如下所示:
(1)第一类形如:WriteLog(LogLevel, LogInfo)。其中,参数LogLevel指日志等级(见第2节中的说明);参数LogInfo是具体要打印的日志信息,我们据此信息来检查程序的运行情况。该函数的调用示例如:WriteLog(LOG_INFO, "The value of this integer is 3."),日志等级为LOG_INFO,日志信息为“The value of this integer is 3.”(该信息会输出到日志文件中)。
(2)第二类形如:WriteLogEx(LogLevel, LogInfo, ParaInfo)。这是扩展的日志函数,不但能够输出日志信息,还能够在日志信息中显示变量的值。该函数的调用示例如:WriteLogEx(LOG_INFO, "The value of integer iInt is %d.", iInt),该日志要输出整型变量iInt的值,可以将该函数的调用与printf函数的调用比较起来看(可以认为WriteLogEx函数只是在printf函数中增加了一个日志等级参数)。