陈 娟
(国防科技大学计算机学院,湖南长沙410073)
编程能力的培养一直被认为是程序设计课程的重要内容之一。2013年底,美国前总统奥巴马呼吁每个美国人都要学习编程,为此还专门录制了一段视频,呼吁全民写代码,在全美引起了不小的反响[1]。奥巴马的讲话中提到“学习编程技能不仅对你的未来有重要的意义,它对于美国国家的未来也非常重要”。2013年12月,美国科技界最大的新闻之一是code.org发起的“编程一小时”活动[2]。可见,美国政府及社会各界对程序设计的重视程度非常高,关注范围也非常广。
我国对程序设计课程的教学改革也十分重视,十分关注学生实际编程能力的提高,并将此作为课程改革的重要目标之一,且目前已经取得了不错的效果。尽管如此,程序设计课程的实践能力培养仍然会遇到诸多新问题和新挑战。其中,学生在编程过程中的自我查错与自我纠错能力、教师对各种错误案例的分类与整理能力、教学活动中对于错误案例的使用和作用发挥程度等,都从很大程度上影响了学生编程能力的进一步提高。
2014年,ACM计算机教育杰出贡献奖获得者RobertM.Panoff博士在SIGCSE2014大会所作的主题报告中提出一个鲜明观点:“Right Answer=WrongAnswer+Corrections”(正确答案=错误答案+更正)[3]。由此可以看出,错误对于找到正确答案的重要性。该观点在教材方面也有相关书籍可以参考,如《C陷阱与缺陷》[4]《品悟C——抛弃C程序设计中的谬误与恶习》[5]等。《C陷阱与缺陷》一书从词法分析、语法语义、连接、库函数、预处理器、可移植性缺陷等方面分析了C语言编程中可能遇到的问题,最后给出一些具有实用价值的建议。
在程序设计教学中,虽然我们已经意识到编程错误的重要性,但是仍然缺乏系统的分析与研究以及教学实践,如错误产生的理论基础是什么、错误的产生、错误的分类与更正方法、错误的避免方法、错误案例的搜集与分类方法、错误与编程能力培养之间的关系等。研究这些问题,有助于提高程序设计课程的教学理论水平以及开展编程实践活动的水平,有助于真正提高学生的编程能力。
国际上,计算机教育领域的广大教育研究者长期关注着程序设计中的编程错误。20世纪70年代就出现了关于编程错误的相关研究。文献[6]对程序员所犯的错误给出了系统化的描述,通过对42名编程员的数据搜集,洞悉各种编程错误的关联及重要性,其研究中所关心的问题包括程序员易犯的错误类型、如何找到并更正错误、不同的编程语言之间是否有差异、编程经验对程序员犯错有什么影响等。
ACM计算机科学教育大会是ACMSIGCSE的旗舰会议,是计算机教育领域的国际顶级会议。以SIGCSE2017为例,就有多项与帮助学生识别编程错误相关的教学研究,如肯特州立大学开发了一个工具[7]帮助C语言的初学者学习编程,对于错误的类别进行了区分,包括编译器能够识别的语法错误和编译器不能识别的语法错误。研究者主要关心以下3个问题:对于学生来说,什么错误是最难定位的、学生需要花多长时间找到并修正错误以及学生修正错误的效果如何。为了回答上述问题,研究者收集了以下数据:识别出的和遗漏的错误类型、定位错误所需要的时间、学生是否具备将错误与错误类型相关联的能力以及修正错误所花费的时间。
美国艾柏林基督大学的RaymondPettit等人[8]改善了编译器错误消息的准确度,使得学生更容易理解错误,并且更有效地更正错误;同时,他们关注学生连续提交相同编译错误的次数是否降低,希望以此证明他们方法的有效性。
2005年,英国肯特大学的MatthewC.Jadud[9]在面向对象编程的导论课中,关注了学生使用BlueJ编程环境开发Java语言时的编译出错情况,并给出最易犯的错误列表,用以改进之后版本的BlueJ开发环境。他们发现,在出现的1926个错误中,共会遇到42个不同的错误,其中5个错误的出错次数高达58%。
日本九州大学信息科学与电子工程系开发了一个教学系统,本科生在学习C语言时可以用它定位错误并找到解决方案。该教学系统有两类日志:C语言编程错误日志和BookLooper阅读日志。一旦错误发生,系统将分析BookLooper中最常被访问的页来提供相关材料;系统会为教师生成一份反馈,以帮助教师对材料进行补充。研究者对错误进行分类,将常见错误分成了26类,类别信息也连同错误日志一起被增加。
调错系统与错误定位紧密相关,文献[10]中提出教学生debugging的教学方法。
近几年,国内针对程序设计课程也展开了多种多样的改革,如将基于翻转课堂的创客式课堂教学模式应用在程序设计课程群中,提高学生的学习能力和创新能力[11];通过将任务项目驱动的教学模式引入程序设计课程教学中,提出以培养学生计算思维能力和程序设计能力为目标的教改思路与方法[12];对高级语言程序设计课程进行慕课建设[13-14]等。
在编程过程中,无论是逻辑错误,还是运行错误,又或者是概念理解错误,都与人类认知密切相关。根据人类错误理论[15],人类认知过程分为3个阶段:planningstage(计划阶段)、storage stage(存储阶段)和executionstage(执行阶段)。错误根据人类认知过程而分为3类:slips(键入错误)、lapses(判断错误)和mistakes(错误)。键入错误通常发生在执行阶段,是由于不正确地执行一个预定义动作而产生的;判断错误通常发生在存储阶段,是由于疏忽一个已存储的预定义动作而产生的;发生在执行阶段的mistakes(错误),是指设定的计划并不适合于达到期望的目标。
C语言程序设计学习过程中的错误有多种分类方法,常见的错误分类方法是将编程错误分为3类:编译错误、逻辑错误和运行时错误。编译错误是指违反构成语言规则的错误,通常包括未声明变量或函数错误、变量类型错误、标点及符号错误等,如缺少分号、缺少左(右)花括号、左右括号不匹配等;由于编译错误会妨碍程序执行,因此所有的编译错误都必须在程序运行之前被更正。逻辑错误是指运行结果不是期望的结果,但是也不会报错的错误,常见的逻辑错误包括未初始化变量、用赋值号代替逻辑相等符号进行逻辑判断、switch语句中缺少break语句、在不需要使用分号的地方使用分号等。运行时错误是指在程序运行时发生的错误,常见的运行时错误包括数组边界错误、内存溢出、用零做除数、未初始化指针错误、返回对局部内存的引用等。
我们根据问题求解过程的大致先后顺序,将错误产生的原因归为以下5类:①不进行自顶向下的问题分解,对于复杂问题直接进行代码编写;②代码的模块化设计不佳,模块之间的接口不清晰;③不遵循命名规则和通用的编程风格;④代码测试不完善;⑤不进行调试或存在不良调试习惯。我们分别对每类错误进行解释,并给出避免方法。
第1类错误是由于没有进行自顶向下的问题分解所产生的。对此,我们采取的避免措施包括以下3个方面:①要求学生学会自顶向下的问题分解方法,目的是建立“自顶向下、逐步求精”的思维方式,自顶向下的设计过程包括系统概要设计和子系统的逐步精化;②只有完成所有子系统的设计时,才能开始具体的编码实现工作,在此之前只能进行子系统的功能设计;③在编写具体代码时,要求先写主程序,主程序由系统概要设计时确定的所有功能子程序所构成,通常调用了各个功能子程序,编写完主程序之后再依据每个子程序的功能进行代码编写。
第2类错误是由于代码的模块化设计结构不清晰所造成的。避免措施包括:①学会从逻辑意义上进行模块化设计,而非简单物理意义上的代码分割;②必须学会定义和使用函数,定义一个函数包括函数的输入参数和返回值;③编写尽可能简单的函数,每个函数只完成一个功能,避免出现很长的函数,因为对于初学者而言,适当限定每个函数的长度有利于养成良好的编程习惯;④为了尽量避免或减少模块衔接部分产生的错误,要重点注意不同模块访问公共数据结构时可能出现的干扰,以及不同模块之间使用相同局部变量名所引发的冲突和错误。
第3类错误是由于命名规则和编程风格不好所导致的。避免措施包括:①变量命名要有意义,且命名风格尽量保持一致;②注意变量的命名长度;③编码尽量规范,注意缩进;④养成编写注释的习惯,以下位置一般需编写注释,如整个程序的开始、关键变量的含义、每个子程序模块的功能描述和输入输出参数说明;⑤编写程序的说明文档,如标注整个程序的文件结构、版本及日志文件、主要程序功能、输入数据集构成、可能的输出样例等。
第4类错误是由于代码测试不完整所造成的。这类错误可以通过单元测试和集成测试尽可能予以避免。①单元测试是指编写足够多的单元测试用例,对每个模块进行功能性和完整性测试;单元测试要注意测试用例的完整性,尽可能覆盖所有的执行分支,数据要覆盖所有可能的范围,如正数、负数、零等;特殊元素的测试,如零;注意数组边界情况的测试、指针访问空间的有效性、申请的临时空间是否及时回收等。②集成测试是指对组装在一起的多个模块进行测试,常用的集成测试方法包括非渐增式测试和渐增式测试。
5类错误的分类及避免方法如图1所示。
错误案例的搜集与分类须分别从学生、教师和教辅的角度进行考虑。
1)如何指导学生进行错误的搜集和分类?
(1)要求学生对自己在课堂上出现的疑问、犯的错误以及教师的解答进行记录,并每周汇总一次给教辅,同时将出现的错误与可能的知识点进行关联,在教师的指导下进行关联式学习,避免以后犯类似错误。
(2)要求学生对教师在课堂上的提问及展开的讨论进行记录和整理,包括提问的内容、相关代码、学生的回答、出现的错误、调试方法、调试演示过程、相关调试技巧和正确解答。
图15 类编程错误的避免手段
图2 编程错误与编程能力培养之间的关系
(3)要求学生对习题课上的疑问和解决方案进行记录和整理,包括所用机器的结构、体系结构、编程环境、调试选项等。
(4)对课后作业中出现的各种错误进行记录和整理,还包括开展的Project项目中出现的各种错误、不当的日志文档记录、bug调试错误记录等。
2)教师如何进行错误的搜集和分类?
③应坚持骨干工程与配套工程相匹配。骨干水利工程的良性运行,离不开配套工程的辅佐,田间配套工程是农田灌溉的“最后一公里”。应加强大中型灌区田间配套工程的建设,确保与骨干工程建设的同步和匹配,以保证工程整体效益的充分发挥。
(1)对错误问题的设计及课堂提问情况进行记录。教师根据记录的错误日志文件,进行错误案例设计,并将这些错误案例穿插在课堂讲解中,提问并记录学生解答情况,以检测教学效果。
(2)对课堂上学生的提问以及回答情况进行记录,同时将典型错误与相关知识点关联,采用联想式教学衍生出与此类似的新问题。
(3)对在线答疑时学生提问及回答情况进行记录。在线答疑与课堂提问有所不同,课堂上所提出的问题中,有一部分是学生在学习新概念时没能理解的问题,这类问题通常比较直接;在线答疑的问题有时并不直接,通常是学生经过反复思考或长时间编程、调试仍然未能解决的问题,这往往也是比较典型的错误,还有可能涉及其他相关知识,对这类问题的记录要注意保留学生的出错“现场”,同时注意跟踪学生今后是否还会犯类似的错误。
3)教辅如何辅助教师进行错误的搜集和分类?
(1)教辅协助教师对提交的错误进行分类和整理,包括按照章节知识点进行错误整理,以便教师在下一次教学中设计新的错误案例;对学生所犯错误的次数和频率进行分类,帮助教师设计错误案例。
(2)教辅协助教师对学生的学习程度进行分级,并对不同程度的学生错误进行归类,及时发布给学生,同时选择性地进行课堂讲解。
根据解决问题的过程步骤,我们将编程能力分为4级,从上到下依次是对一个问题的抽象能力、进行模块化分解的能力、进行子模块功能定义的能力和实际编写代码的能力。如图2所示,我们给出这4级编程能力与编程错误之间的关联。其中,右边一列代表5类错误,和图1中的5类错误相吻合;双向箭头代表这5类错误与4级编程能力之间的关联性;左边一列列出4级编程能力,用单项箭头表明问题求解过程中的时间段。
越往上层的编程能力的缺失或不足,所犯的错误越难纠正或者纠正的代价越大,因为这种错误对于代码的影响来说可能是根本的、致命的。这类编程能力的培养有时需要学习其他相关计算机课程,如数据结构、算法设计等,以培养学生的问题抽象能力和问题分解能力。目前的程序设计课程往往比较缺乏对问题分解能力的培养和锻炼,极大地影响了学生编写大型程序,且使得学生无法高效完成代码的合作编写。
越往下层的编程能力的缺失或不足,使得所犯的错误相对比较集中和明确,容易识别和纠正,这类错误通常与编程习惯的养成有关,规范性的东西较多,在教学过程中比较容易掌控。对于这类错误,目前教学中比较注重命名规则和编程风格的规范以及调试方法,但是往往容易忽略调试习惯的养成、高级调试技巧的掌握以及代码测试的作用,这也影响学生自我查错能力的锻炼以及编写大型软件时提高全面代码测试能力。
通过两个学期的实践,学生普遍反映自己编程能力的提高比较明显,具体体现在3方面:①问题分解能力得到提高,掌握了基本的问题分解方法和技巧,学会了如何将一个问题划分为多个子问题;②子模块功能定义能力提高,能够从逻辑意义上将一个问题划分成多个子模块,并用函数定义每一个子模块的功能,清楚函数的参数设置方法及参数作用域,学会了如何调用函数进行复杂问题求解;③程序查错及调试能力提高,绝大部分学生可以根据基本的编译错误提示快速纠正语法错误,大部分学生能够通过调试、跟踪代码的执行过程查找语义错误,部分学生掌握了通过断点设置方法和条件断点的设置查找运行时错误。
编程能力培养离不开编程错误案例的分析与避免方法,对此国内外程序设计课程教学研究者均有认同,并在错误识别、错误定位与分析、辅助工具等方面作出大量研究。编程错误产生的原因多种多样,我们从问题求解过程的角度发现错误产生的原因主要有5类,并将这5类编程错误与编程能力之间进行了关联,给出了一些错误避免方法和建议。
[1]优酷.奥巴马呼吁每个美国人都学习编程[EB/OL].[2017-08-02].http://v.youku.com/v_show/id_XNjQ2MzAxODky.html.
[2]编程一小时[EB/OL].[2017-08-02].https://code.org/learn.
[3]PanoffRM.Computationalthinkingforall:Thepowerandtheperil[C]//Proceedingsofthe45thAC MtechnicalSymposiumon ComputerScienceEducation(SIGCSE'14).NewYork:ACM,2014:1-2.
[4]凯尼格.C陷阱与缺陷[M].高巍,译.北京:人民邮电出版社,2008.
[5]薛非.品悟C:抛弃C程序设计中的谬误与恶习[M].北京:清华大学出版社,2012.
[6] Youngs E A. Human errors in programming[J]. International Journal of Man-Machine Studies, 1974, 6(3): 361-376.
[7] Alqadi B S, Maletic J I. An empirical study of debugging patterns among novices programmers[C]//Proceedings of the 2017 ACMSIGCSE Technical Symposium on Computer Science Education(SIGCSE’17). New York: ACM, 2017: 15-20.
[8] Pettit R, Homer J, Gee R. Do enhanced compiler error messages help students: Results inconclusive[C]//Proceedings of the 2017ACM SIGCSE Technical Symposium on Computer Science Education(SIGCSE’17). New York: ACM, 2017: 465-470.
[9] Jadud M C. A first look at novice compilation behavior using blueJ[J]. Computer Science Education, 2005, 15(1): 25-40.
[10] Lewis C M, Gregg C. How do you teach debugging: Resources and strategies for better student debugging[C]//Proceedings of the2016 ACM SIGCSE Technical Symposium on Computer Science Education(SIGCSE’16). New York: ACM, 2016: 706.
[11] 陈晋音, 俞山青, 毛国红, 等. 程序设计课程群的创客式课堂教学模式探究[J]. 计算机教育,2017(1): 80-83.
[12] 郭银章, 王丽芳. 基于项目任务驱动的C语言程序设计课程教学改革与实践[J]. 计算机教育, 2017(2): 41-44.
[13] 陈娟, 张长海, 邓春燕, 等. 高级语言程序设计课程的慕课建设与思考[J]. 计算机教育, 2017(1): 9-13.
[14] 郑莉. “C++语言程序设计”慕课的设计[J]. 计算机教育, 2015(23): 120-122.
[15] Reason J. Human error[M]. Cambridge: Cambridge University Press, 1990: 1-296.