当前位置: 首页 > 文章教程  > 计算机与互联网 > 网络编程

第15章错误在所难免——调试与错误处理

8/31/2020 9:10:17 PM 人评论

第15章错误在所难免——调试与错误处理

第15章 错误在所难免——调试与错误处理

本章视频教学录像:34分钟

在编程中,难免会出现各种各样的错误。出现错误并不可怕,只要我们掌握了查找、调试、处理错误的方法,任何错误都可以被纠正。

本章要点(已掌握的在方框中打钩)

□ 错误的类型及产生原因

□ 查找错误

□ 捕捉和处理错误

□ 使用日志文件记录错误

□ 减少错误的秘诀

15.1 错误的产生原因及类型

本节视频教学录像:4分钟

我们在程序的编写过程中,不可避免地会出现一些错误,这些错误可能会导致程序的编译失败,也可能会导致程序在运行过程中异常的产生。当错误出现的时候,你一定很想知道它到底是从哪里来的?又是如何产生的?

15.1.1 错误的产生

错误产生的原因很多,不良的编程习惯和编码方式,以及编程中的一些疏忽都会导致错误的产生,甚至由于一些外界因素的作用也会使错误产生。

⑴手误导致错误。例如,将关键字return 写成了 retrun。

⑵语法错误。例如,将string[] name 写成了string name[]。

⑶执行一些非法操作导致错误。例如10/0,被除数为零。

⑷数据库连接异常。

⑸Web服务器崩溃或配置不当导致错误等。

提示

错误和异常是什么关系?

其实错误是因,异常是果。正因为产生了错误,所以程序才会出现异常。当然,我们更多的时候对错误和异常是不做区分的。

15.1.2 错误的类型

错误产生之后,我们还要分辨出错误是属于什么类型的。只有弄清楚错误的类型,才能更好地对不同的错误采取不同的对策。错误的类型主要包括以下两种。

⑴不能被捕捉处理的错误。对由于程序的逻辑错误造成的语义错误,编译器也是无能为力的,因此也是无法被捕捉到的。

⑵可以被捕捉处理的错误。这种错误指的是如上一小节所提到的语法错误、非法操作等,编译器会自动地把错误找出来,并在出现错误的代码下面用波浪线标识出来。

figure_0371_0640

注意

在可以被捕捉处理的错误中,有些错误可能在编译过程中并不能被检测出来,只有在运行过程中才能出现(比如说数据类型的转换错误),这就需要我们利用异常处理机制来进行捕捉处理。

15.2 查找错误

本节视频教学录像:8分钟

要想弄清楚错误的来源,需要我们一步一步地来查找错误。那么如何在错误出现之后,能尽快地找到错误的根源呢?

15.2.1 设置断点

追踪错误,就像刑侦人员侦破案件一样,根据罪犯留下的蛛丝马迹,层层深入,抽丝剥茧,最终找到真凶。办案的过程中,一定少不了蹲点守候,现场设伏。我们查找错误,同样也需要在可能出现错误的地方设置断点。当程序执行到断点处的时候会停下来,这时我们就可以用【调试】菜单中的命令,来对代码进行逐语句或者逐过程的执行,从而找到出错的地方。

设置断点的方法有以下三种。

⑴在需要设置断点的代码行的左边缘处单击,在编辑窗口的左边缘会出现一个红色的圆点,表示该行代码设有断点。

⑵在设置断点的代码行右击,选择【断点】figure_0372_0641【插入断点】命令。

⑶将光标落在要设置断点的代码行,选择【调试】figure_0372_0642【切换断点】命令。

设置断点的原则是:在可能出现错误的地方设置断点,当程序执行到该处时会自动处于中断状态,然后就可以从该断点处开始一步一步地调试,顺藤摸瓜找到错误的藏身之处。

figure_0372_0643

断点使用完毕后,当然需要删除。删除断点的方法有以下4种。

⑴单击断点处的红色圆点。

⑵右键单击断点处的红色圆点,选择【删除断点】命令。

⑶右键单击设置断点的代码行,选择【断点】figure_0372_0644【删除断点】命令。

⑷将光标落在要设置断点的代码行,选择【调试】figure_0373_0645【切换断点】命令。

15.2.2 启动调试

当程序通过编译后,即可进入调试阶段。选择【调试】/【启动调试】命令或者【调试】figure_0373_0646【逐语句】、【逐过程】命令执行程序并调试,也可以按【F5】键启动调试。如果程序中设有断点,程序则会一直运行到该断点处停住,此刻程序处于中断状态。如图所示。

figure_0373_0647

提示

什么是中断状态?

当程序在运行过程中遇到断点或者是错误的时候,程序会暂停运行,让我们来处理接下来的事情:或者让程序继续运行,或者让程序结束运行。

15.2.3 逐语句调试

逐语句调试是指在调试过程中,单击【调试】figure_0373_0648【逐语句】命令或按【F11】键,这时会发现有一个黄色的小箭头开始逐条语句地向下移动,这就是单步执行代码,即一次执行一行代码。当遇到函数调用时,黄色小箭头就会跟踪到函数内部单步执行,函数执行完后箭头会跳出该函数,跳回到函数调用的位置,继续向下一条语句执行。

15.2.4 逐过程调试

逐过程调试是指在调试过程中,单击【调试】figure_0373_0649【逐过程】命令或按【F10】键,就可以单步执行代码,即一次执行一行代码,在遇到函数调用之前的执行效果和逐语句调试完全一样。当遇到函数调用时,黄色小箭头不会进入到函数内部执行,而是直接执行函数,函数执行完之后则指向下一条语句,继续下一条语句的执行。

15.2.5 跳出

跳出是指在函数内部执行逐语句调试的过程中,单击【调试】figure_0373_0650【跳出】命令或按【Shift+F11】组合键,黄色箭头就可以从函数内部跳出,直接执行该函数之后的语句。

15.2.6 停止调试

停止调试则意味着程序彻底退出调试会话,结束运行。要想结束调试,可以单击【调试】figure_0374_0651【停止调试】命令,也可以按【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 }

⑶项目运行后,结果如图所示。

figure_0377_0652

很明显,程序出现了异常情况,不得已只得强制退出。为了避免这种情况的发生,我们需要使用异常处理机制来捕捉和处理异常情况。

⑷在原有代码的基础上修改代码如下。

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】键调试运行,结果如图所示。

figure_0378_0653

从结果中可以看出,加入了异常处理语句之后,程序不仅打印出了异常信息,而且还能够正常运行。

【范例分析】

在这个程序中,为什么会产生异常呢?就是因为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”, 单击【确定】按钮,将该文件引用到项目当中。

figure_0379_0654

⑵单击【确定】按钮后,可以看到log4net.dll文件已经被引用到项目当中。

figure_0379_0655

⑶在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&quot;.htm&quot;" />

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("生成错误日志成功!");

figure_0380_0656

【运行结果】

单击工具栏中的【启用调试】按钮figure_0381_0657,即可在项目文件夹中的Log目录下生成myError.log文件。

figure_0381_0658

打开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中编写一个程序,要求实现以下功能:

⑴从页面上输入两个任意的数,能够进行加法运算;

⑵要求进行错误的捕捉和处理;

⑶当出现异常时,能够将异常信息写入到后台日志中。

相关教程

共有条评论 网友评论

验证码: 看不清楚?