第15章 错误在所难免——调试与错误处理
本章视频教学录像:34分钟
在编程中,难免会出现各种各样的错误。出现错误并不可怕,只要我们掌握了查找、调试、处理错误的方法,任何错误都可以被纠正。
本章要点(已掌握的在方框中打钩)
□ 错误的类型及产生原因
□ 查找错误
□ 捕捉和处理错误
□ 使用日志文件记录错误
□ 减少错误的秘诀
15.1 错误的产生原因及类型
本节视频教学录像:4分钟
我们在程序的编写过程中,不可避免地会出现一些错误,这些错误可能会导致程序的编译失败,也可能会导致程序在运行过程中异常的产生。当错误出现的时候,你一定很想知道它到底是从哪里来的?又是如何产生的?
15.1.1 错误的产生
错误产生的原因很多,不良的编程习惯和编码方式,以及编程中的一些疏忽都会导致错误的产生,甚至由于一些外界因素的作用也会使错误产生。
⑴手误导致错误。例如,将关键字return 写成了 retrun。
⑵语法错误。例如,将string[] name 写成了string name[]。
⑶执行一些非法操作导致错误。例如10/0,被除数为零。
⑷数据库连接异常。
⑸Web服务器崩溃或配置不当导致错误等。
提示
错误和异常是什么关系?
其实错误是因,异常是果。正因为产生了错误,所以程序才会出现异常。当然,我们更多的时候对错误和异常是不做区分的。
15.1.2 错误的类型
错误产生之后,我们还要分辨出错误是属于什么类型的。只有弄清楚错误的类型,才能更好地对不同的错误采取不同的对策。错误的类型主要包括以下两种。
⑴不能被捕捉处理的错误。对由于程序的逻辑错误造成的语义错误,编译器也是无能为力的,因此也是无法被捕捉到的。
⑵可以被捕捉处理的错误。这种错误指的是如上一小节所提到的语法错误、非法操作等,编译器会自动地把错误找出来,并在出现错误的代码下面用波浪线标识出来。
注意
在可以被捕捉处理的错误中,有些错误可能在编译过程中并不能被检测出来,只有在运行过程中才能出现(比如说数据类型的转换错误),这就需要我们利用异常处理机制来进行捕捉处理。
15.2 查找错误
本节视频教学录像:8分钟
要想弄清楚错误的来源,需要我们一步一步地来查找错误。那么如何在错误出现之后,能尽快地找到错误的根源呢?
15.2.1 设置断点
追踪错误,就像刑侦人员侦破案件一样,根据罪犯留下的蛛丝马迹,层层深入,抽丝剥茧,最终找到真凶。办案的过程中,一定少不了蹲点守候,现场设伏。我们查找错误,同样也需要在可能出现错误的地方设置断点。当程序执行到断点处的时候会停下来,这时我们就可以用【调试】菜单中的命令,来对代码进行逐语句或者逐过程的执行,从而找到出错的地方。
设置断点的方法有以下三种。
⑴在需要设置断点的代码行的左边缘处单击,在编辑窗口的左边缘会出现一个红色的圆点,表示该行代码设有断点。
⑵在设置断点的代码行右击,选择【断点】【插入断点】命令。
⑶将光标落在要设置断点的代码行,选择【调试】【切换断点】命令。
设置断点的原则是:在可能出现错误的地方设置断点,当程序执行到该处时会自动处于中断状态,然后就可以从该断点处开始一步一步地调试,顺藤摸瓜找到错误的藏身之处。
断点使用完毕后,当然需要删除。删除断点的方法有以下4种。
⑴单击断点处的红色圆点。
⑵右键单击断点处的红色圆点,选择【删除断点】命令。
⑶右键单击设置断点的代码行,选择【断点】【删除断点】命令。
⑷将光标落在要设置断点的代码行,选择【调试】【切换断点】命令。
15.2.2 启动调试
当程序通过编译后,即可进入调试阶段。选择【调试】/【启动调试】命令或者【调试】【逐语句】、【逐过程】命令执行程序并调试,也可以按【F5】键启动调试。如果程序中设有断点,程序则会一直运行到该断点处停住,此刻程序处于中断状态。如图所示。
提示
什么是中断状态?
当程序在运行过程中遇到断点或者是错误的时候,程序会暂停运行,让我们来处理接下来的事情:或者让程序继续运行,或者让程序结束运行。
15.2.3 逐语句调试
逐语句调试是指在调试过程中,单击【调试】【逐语句】命令或按【F11】键,这时会发现有一个黄色的小箭头开始逐条语句地向下移动,这就是单步执行代码,即一次执行一行代码。当遇到函数调用时,黄色小箭头就会跟踪到函数内部单步执行,函数执行完后箭头会跳出该函数,跳回到函数调用的位置,继续向下一条语句执行。
15.2.4 逐过程调试
逐过程调试是指在调试过程中,单击【调试】【逐过程】命令或按【F10】键,就可以单步执行代码,即一次执行一行代码,在遇到函数调用之前的执行效果和逐语句调试完全一样。当遇到函数调用时,黄色小箭头不会进入到函数内部执行,而是直接执行函数,函数执行完之后则指向下一条语句,继续下一条语句的执行。
15.2.5 跳出
跳出是指在函数内部执行逐语句调试的过程中,单击【调试】【跳出】命令或按【Shift+F11】组合键,黄色箭头就可以从函数内部跳出,直接执行该函数之后的语句。
15.2.6 停止调试
停止调试则意味着程序彻底退出调试会话,结束运行。要想结束调试,可以单击【调试】【停止调试】命令,也可以按【Shift+F5】组合键完成。
15.3 捕捉和处理错误
本节视频教学录像:7分钟
知道了怎样查找错误,还需要掌握捕捉错误的方法。在捕捉到了错误之后,下一步当然就是要把错误处理掉。语法上的错误我们可以改正,可是还有很多错误是我们无法预料的,比如逻辑错误、网络连接错误等。当这些无法预料的错误出现了,该怎么办?是任由程序非正常结束,还是想办法把这些错误及时地处理掉,以保证程序的正常运行?
15.3.1 捕捉错误
捕捉错误的诀窍就是未雨绸缪。猎人捕捉猎物,需要在猎物的必经之路上设下圈套,等到猎物落入圈套。捕捉错误就像猎人打猎一样,要在可能出现错误的地方事先设下埋伏,等到错误出现的时候,就可以及时地捕捉到错误。
我们可以用异常处理机制来捕捉错误,常见的异常处理语句语法形式有以下3种。
1. try-catch形式
例如:
try
{
SqlConnection sqlConn=new SqlConnection(“Data Source=.;
Initial Catalog=student;Integrated Security=True”);
sqlConn.Open() ; //可能出现异常的语句
Response.Write(“数据库连接成功”);
}
catch(Exception ex)
{
Response.Write(ex.Message); //异常处理语句
}
该形式主要由try块来捕捉可能出现异常的代码,由catch块来捕获并处理错误。这里的代码块主要用于测试与数据库的连接,当与数据库的连接出现异常情况的时候,将会由catch块来捕捉和处理数据库连接的异常信息。
2. try-catch-finally形式
例如:
try
{
SqlConnection sqlConn=new SqlConnection(“Data Source=.;
Initial Catalog=student;Integrated Security=True”);
sqlConn.Open() ; //可能出现异常的语句
Response.Write(“数据库连接成功”);
}
catch(Exception ex)
{
Response.Write(ex.Message); //异常处理语句
}
finally
{
//一定会被执行的语句
if (sqlConn.State !=ConnectionState.Closed)
{
sqlConn.Close(); //关闭连接
}
}
该形式主要由try块来捕捉可能出现异常的代码,由catch块来捕获并处理错误,最后由finally块来进行一些必须要进行的操作。这里的代码块主要用于测试与数据库的连接,当与数据库的连接出现异常情况的时候,将会由catch块来捕捉和处理数据库连接的异常信息,而后由finally块进行关闭数据库连接的操作。
3. try-finally形式
例如:
try
{
SqlConnection sqlConn=new SqlConnection(“Data Source=.;
Initial Catalog=student;Integrated Security=True”);
sqlConn.Open() ; //可能出现异常的语句
Response.Write(“数据库连接成功”);
}
finally
{
//一定会被执行的语句
if (sqlConn.State !=ConnectionState.Closed)
{
sqlConn.Close(); //关闭连接
}
}
该形式主要由try块来捕捉可能出现异常的代码,而后由finally块来进行一些必须要进行的操作。这里的代码块主要用于测试与数据库的连接,当与数据库的连接出现异常情况的时候,并不做任何异常处理和显示,只由finally块进行关闭数据库连接的操作。
在以上3种形式的异常处理语句中,一个try块可以有如下几种情况。
⑴有一个finally块,没有catch块。
⑵有一个或者多个catch块,没有finally块。
⑶有一个或者多个catch块,同时有一个finally块。
如果有多个catch语句,则用于捕捉不同类型的错误,如数据库访问异常、数组越界异常等。finally语句则更多被用于程序中的清理善后工作,如关闭数据库连接、释放对象所占用的内存资源等。
提示
Exception是什么?
System.Exception类是所有异常类的基类,用它可以捕捉到所有的异常类型,而异常信息可以通过ex.Message()方法来获得。本章中的大部分程序都是简单的控制台程序。
15.3.2 处理错误
捕捉到了错误之后,接下来就是如何来处理这些错误。正如前一小节所说,由catch块语句来进行错误的捕捉处理。一般情况下,直接在页面或者控制台区域输出错误信息即可。
下面我们来完成一个错误的处理,介绍如何运用异常处理机制来捕捉和处理错误信息,并将错误信息显示出来。
【范例15-1】求一个数组中各个元素相加的总和,并使用异常处理语句来捕捉和处理错误信息。
⑴在Visual Studio 2010中,新建一个名为“ExceptionTest”的ASP.NET空网站。
⑵添加一个Default.aspx页面,然后在Default.aspx.cs文件中添加如下代码。
01 protected void Page_Load(object sender,EventArgs e)
02 {
03 int result= 0;
04 if (!IsPostBack)
05 {
06 Response.Write("准备执行加法运算<br>");
07 result=Multiplication(); //执行加法运算后,返回结果
08 Response.Write("执行加法运算完成 ,结果是:"+ result.ToString());
09 }
10 }
11
12 protected int Multiplication()
13 {
14 int[] y=new int[3];
15 int sum= 0;
16 for (int i= 0; i< 4; i++)
17 {
18 y[i]= i; //给数组元素赋值
19 sum= sum+ y[i]; //求出数组元素之和
20 }
21 return sum;
22 }
⑶项目运行后,结果如图所示。
很明显,程序出现了异常情况,不得已只得强制退出。为了避免这种情况的发生,我们需要使用异常处理机制来捕捉和处理异常情况。
⑷在原有代码的基础上修改代码如下。
01 protected void Page_Load(object sender,EventArgs e)
02 {
03 int result= 0;
04 if (!IsPostBack)
05 {
06 Response.Write("准备执行加法运算<br>");
07 try
08 {
09 result=Multiplication(); //执行加法运算后,返回结果
10 Response.Write("执行加法运算完成 ,结果是:"+ result.ToString());
11 }
12 catch(Exception ex)
13 {
14 Response.Write(ex.Message); //打印错误信息
15 }
16 }
17 }
18
19 protected int Multiplication()
20 {
21 int[] y=new int[3];
22 int sum= 0;
23 for (int i= 0; i< 4; i++)
24 {
25 y[i]= i; //给数组元素赋值
26 sum= sum+ y[i]; //求出数组元素之和
27 }
28 return sum;
29 }
【运行结果】
按【F5】键调试运行,结果如图所示。
从结果中可以看出,加入了异常处理语句之后,程序不仅打印出了异常信息,而且还能够正常运行。
【范例分析】
在这个程序中,为什么会产生异常呢?就是因为Multiplication()方法中的整型数组y的长度为3,而在for循环中则循环了4次来对数组中的元素赋值,结果导致数组越界异常的产生。为了防止程序产生异常后非正常终止,使用了try、catch语句来捕捉异常。
提示
输出在页面上的错误信息如果是给程序员看的,那么将系统提示的错误信息直接打印出来即可。如果错误信息是给用户看的,那么就需要打印出比较友好的错误提示信息。
15.4 使用日志文件记录错误
本节视频教学录像:6分钟
错误产生之后,如果想把这些错误的详细信息以文本的形式记录下来,以便后期进行错误分析和处理,应该怎样做呢?
log4net是apache组织开发的日志组件,可以从http://logging.apache.org/log4net/download_log4net.cgi下载log4net 1.2.13-bin- newkey.zip这个文件,然后在其src目录下找到log4net.dll这个文件。用户如果要在自己的程序里加入日志功能,只需将log4net.dll引入工程即可。
提示
log4net是一个开源项目,可以以插件的形式应用在系统中。
本节继续使用前一个错误处理的示例,介绍如何运用log4net将错误信息写入日志文件中。
【范例15-2】在ASP.NET中使用log4net生成错误日志。
⑴打开【ExceptionTest】网站,在【解决方案资源管理器】窗口中选择项目名,单击右键,在弹出的快捷菜单中选择【添加引用】菜单项,弹出【添加引用】对话框,在【浏览】选项卡中找到“log4net. dll”, 单击【确定】按钮,将该文件引用到项目当中。
⑵单击【确定】按钮后,可以看到log4net.dll文件已经被引用到项目当中。
⑶在web.config根节点 configuration 中加入如下代码。
01 <configSections>
02 <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
03 < /configSections>
⑷在web.config根节点 configuration 中加入如下代码。
01 <log4net>
02 <logger name="logerror">
03 <level value="ERROR" />
04 <appender-ref ref="ErrorAppender" />
05 < /logger>
06 <appender name="ErrorAppender" type="log4net.Appender.FileAppender">
07 <param name="File" value="Log\\myError.log" />
08 <param name="AppendToFile" value="true" />
09 <param name="MaxSizeRollBackups" value="100" />
10 <param name="MaxFileSize" value="10240" />
11 <param name= "StaticLogFileName" value="false" />
12 <param name="DatePattern" value="yyyyMMdd".htm"" />
13 <param name="RollingStyle" value="Date" />
14 <layout type="log4net.Layout.PatternLayout">
15 <param name="ConversionPattern" value="异常时间:%d%n异常级别:%-5p%n%m%n"/>
16 < /layout>
17 < /appender>
18 < /log4net>
⑸在Default.aspx.cs中的类代码外加入如下代码。
[assembly: log4net.Config.XmlConfigurator(Watch = true)]
⑹在Page_Load方法中的catch语句中加入如下两行代码。
log4net.ILog log = log4net.LogManager.GetLogger("logerror");
log.Error("生成错误日志成功!");
【运行结果】
单击工具栏中的【启用调试】按钮,即可在项目文件夹中的Log目录下生成myError.log文件。
打开myError.log文件,内容如下。
异常时间:2014-05-03 08:31:17,005
异常级别:ERROR
生成错误日志成功!
【范例分析】
在这个程序中,为什么会在程序的根目录下生成Log文件夹,并在该文件夹下产生myError.log文件呢?因为在“appender”节点中“type”属性指定为“FileAppender”(文本文件输出格式),并指定了目标文本文件所在位置以及日志文件名称(value="Log\\ myError.log")。日志文件的内容是由log. Error方法来决定的,而日志的格式则是由“layout”标签下“param”标签的“value”属性来指定的。
【代码详解】
步骤⑸中的[assembly: log4net.Config.XmlConfigurator(Watch = true)],指的是程序在运行过程中,加载Web.config中log4net的配置项。
步骤⑹中的log4net.LogManager.GetLogger("logerror")中的“logerror”与Web.config文件中的标签<log4net>\<logger>的name属性的值相对应,通过该值,得到写入日志的等级、文件类型、路径和文件名等。
15.5 减少错误的秘诀
本节视频教学录像:7分钟
在前几节中,我们已了解了错误的来源、类型,以及怎样捕捉、处理这些错误,并且知道了错误可能会导致程序的编译失败,也会导致程序在运行过程中异常的产生。那么错误是不是真的不可避免呢?我们如何避免错误出现呢?本节将为你揭晓答案。
15.5.1 好的编程习惯
要减少错误,首先要养成良好的编程习惯,主要包括以下几点。
⑴出现错误时,要知道如何处理,才能让你编写的程序运行顺畅,不会中途抛锚。
⑵尽量采用简短的语句,提高执行的效率。越简短的语句越不容易出现错误,同时还能提高执行的效率。
⑶懂得复用写好的代码,提高工作的效率。就好比一部机器,缺少了一个零件,只要能找到同等型号的零件,就可以组装上来,使其很快地重新运转起来。
提示
这些都是在实际工作中总结出来的经验,能够帮助我们节省大量的时间和精力。
15.5.2 好的编码方式
有了良好的编程习惯,同样还需要有好的编码方式。好的编码方式可以使你的程序看起来整洁、美观、井井有条、可读性强,更能减少错误出现的几率。主要包括以下几点。
1. 标识符要符合命名规范
编码过程当中变量名、函数名、类名等的命名方式要符合一定的命名规范,这样才能使整个程序看起来标识清楚、意思明确。
⑴标识符最好采用英文单词或其组合,也可以用大家基本可以理解的缩写,便于记忆和阅读。例如,不要把CurrentValue写成NowValue。
⑵标识符的长度一般以不超过15个字符为宜。例如,变量名maxval就比maxValueUntilOverflow好用。
⑶变量的名字应当使用“名词”或者“形容词+名词”。例如:
double value; //名词
double oldValue; //形容词+名词
⑷函数的名字应当使用“动词+名词”(动宾词组)。例如:
DrawLine(); //动词+名词
2. 编写的代码要符合代码编写规范
代码中的运算符、复合表达式、选择语句、循环语句等都要按照代码编写规范来写,从而使程序简洁、明了、运行效率高。
⑴如果代码行中的运算符比较多,应当用括号确定表达式的操作顺序,避免使用默认的优先级。例如:
if ((a | b) && (a & c))
⑵不要编写太复杂的复合表达式。例如:
i= a>=b&& c<d&& c+ f<=g+ h ; //复合表达式过于复杂
⑶对if语句来说,程序中有时会遇到if/else/return的组合,应该将其中不良风格的程序改为良好的风格。以下为不良风格的程序。
if (condition)
return x;
else
return y;
以下为良好风格的程序。
if (condition)
{
return x;
}
else
{
return y;
}
3. 代码中的注释同样必不可少
代码中的注释就像古文中的注解一样,能够让别人很快读懂你写的代码。那么在调用自己或别人所写的代码时,就能极大地降低出错的几率。当然,注释也并非随意添加的,比如注释的写法、格式、位置等都需要符合一定的注释规范。
ASP.NET中C#语言的注释方式主要有以下两种。
⑴单行注释:用于注释单行代码或注释为一行内容。使用格式如下。
//注释一行
⑵多行注释:用于注释多行代码或注释文字为多行。使用格式如下。
/*
*注释
*注释
*注释
*注释
*/
注释要注意以下几个问题。
⑴如果代码本来就很容易理解,则不必加注释,否则多此一举,令人厌烦。例如:
i++; // i加 1,多余的注释
⑵注释的位置应与被描述的代码相邻,可以放在代码的上方或右方,不可放在下方。例如:
sum=sum+i; //求和
或
//求和
sum=sum+i;
以上两种方式都是正确的。
sum=sum+i;
//求和
以上这种注释方式是错误的。
⑶当代码比较长,特别是有多重嵌套时,应当在一些段落的结束处加注释,这样便于阅读。例如:
if (…)
{
……
while(…)
{
} //while结束
……
} // if结束
⑷在每个源文件的头部要有必要的注释信息,包括文件名、版本号、作者、生成日期、模块功能描述(如功能、主要算法、内部各部分之间的关系、该文件与其他文件的关系等)、主要函数或过程清单及本文件历史修改记录等。例如:
/*
*文件名:File.cs
*作者:×××
*版本号:1.02
*生成日期:2009-11-10
*功能描述:执行打开文件,读取文件操作。
* ××× 于 2009-11-30 进行修改
*修改内容:增加了写入文件功能。
*/
⑸在每个函数或过程的前面要有必要的注释信息,包括函数或过程名称、功能描述,输入、输出及返回值说明,调用关系及被调用关系说明等。例如:
/*
*函数介绍:
*输入参数:
*输出参数:
*返回值:
*/
void Function(double x, double y, double z)
{
……
}
15.6 高手点拨
本节视频教学录像:2分钟
1. 什么时候需要用到try/catch语句?
当你写的一段代码在运行中可能出现异常时,就需要用到try/catch语句将这段代码包括起来。否则,运行时异常就会导致程序崩溃从而退出。而加上try/catch语句后,如果出现异常程序则会执行catch中的语句,而不至于导致程序崩溃。
2. 什么样的错误容易导致异常?
语法错误不会导致异常,编译器在编译的时候就会报错而编译不通过。只有逻辑错误才会导致异常,这是编译器无法检测到的错误,需要编程人员根据经验判断和避免,如果有些异常无法避免则需要异常处理机制。
3. 断点调试技巧
在断点调试的过程中,应尽量使用快捷键进行操作;按Ctrl+F10组合键可以使调试直接跳到光标处;只有当程序满足了开发人员预设的条件后,条件断点才会被触发,调试器中断;可以通过局部变量窗口,查看各个变量的状态和当前值。
15.7 实战练习
在ASP.NET中编写一个程序,要求实现以下功能:
⑴从页面上输入两个任意的数,能够进行加法运算;
⑵要求进行错误的捕捉和处理;
⑶当出现异常时,能够将异常信息写入到后台日志中。
共有条评论 网友评论