面向系统能力培养的程序设计综合实践课程改革
2022-03-25陈振邦徐建军陈立前
陈振邦,徐建军,陈立前,沈 立
(国防科技大学 计算机学院,湖南长沙 410073)
0 引言
计算机程序设计能力是计算机相关本科专业培养的专业核心能力之一,也是计算思维能力的核心体现,如何使学生具备良好的程序设计能力是计算机专业人才培养需要解决的核心问题之一。程序设计能力的培养需要在基础理论知识学习的同时结合大量编程实践,以不断提升学生的软件系统构建能力。因此,很多高校在开设计算机程序设计、数据结构等课程后会再安排一门程序设计综合实践课,通过工程量相对较大的项目训练综合提升学生的程序设计能力[1-2]。
国防科技大学的程序设计综合实践课是计算机学院各专业的基础课,是计算机程序设计类课程的后续课程。课程的主要目的是通过一个较为真实的复杂工程问题,使学生完成系统的分析、设计和编程实现,全面梳理学生学习的程序设计、数据结构和算法知识,巩固和提高其程序设计和系统能力,锻炼学生独立编程、从零开始编程以及系统级编程的能力,同时锻炼其自学能力。该课程支撑计算机科学与技术、软件工程、网络工程等专业学生达成具备计算机系统设计开发能力和程序设计能力的毕业要求。
程序设计综合实践课程的目标定位、任务选择、组织实施、考核等对于教学效果有重要影响。一方面,课程任务需要能够综合复盘学生在之前所学的知识;另一方面,根据不同学校的学科背景和培养定位,课程任务需要能对学生进行针对性程序设计锻炼。在教学过程中,针对以上方面开展了3 年的改革和探索。
1 课程改革需求
具有系统观的计算机专业人才培养是当前关注的焦点[3-5]。系统能力是指具备计算系统层面的认知和设计能力,主要包括系统软硬件合理划分能力、不同系统层次的抽象和封装能力、整体系统性能的分析和调优能力、各个系统层面的错误调试和修复能力、程序的性能评估和优化能力、面向具体应用需求的系统构建能力等[3-5]。国防科技大学计算机专业对具备系统能力人才的需求由来已久,特别是在大系统(例如巨型机、CPU、操作系统等)的研制过程中,具备较强系统能力的人才非常关键。
系统能力要求学生对计算机系统的整机概念和层次结构具有较为深刻的理解和认识,对高级语言、指令集架构、编译器、链接器、操作系统、应用程序等之间的关系具有较为深入的掌握,并且能深刻理解和权衡时空开销、抽象和建模、并发和并行等概念,掌握现代计算机系统中最核心的技术和实现方法[3-5]。
国防科技大学计算机专业在程序设计课程中讲授的是C 和C++语言,程序设计综合实践课程则安排在大三。虽然之前的课程内容中安排了一定的程序设计时间,例如采用C/C++开发一个图形界面程序、采用Java 或Python 开发一个安卓应用或游戏等[6],但这些都是在应用层面的编程实践。虽然这些编程实践具备一定的工程量,可以锻炼学生的编程能力,但在学生系统能力、独立编程能力、自主探索能力的培养上仍然没有针对性,主要问题体现在以下几个方面:①由于过度依赖于开发环境,学生对于程序编译、链接、执行等方面的理解不深,同时在程序架构设计、优化、调试等方面的能力仍然较弱,具有全栈开发和优化能力的学生更是少见;②当前互联网和开源技术高速发展,代码资源非常丰富,在提升开发效率和软件设计能力的同时,一定程度上减弱了学生从零开始设计、开发程序的能力,导致部分学生只能修改程序但不能自主从零开发程序,降低了其自主创新能力;③学生在自主学习探索方面存在不足,之前课程的大部分作业或实验可以通过上网搜索找到标准答案,如何体现复杂工程问题无标准解的特点是一个问题,如何调动学生的课程学习积极性、激发其学习兴趣也是一个问题;④学生的独立编程能力存在不足,特别是在规模稍大的软件开发实验上,通常是多人一组,存在“摸鱼”现象;然而,一人一组的模式对课程的验收和过程控制也会带来挑战,如何较为公平和高效地对学生编写的代码作出评测是急需解决的问题。
2 课程设计
在上述教学需求的驱动下,以提升系统能力为目标,在程序设计综合实践课程方面开展了一系列改革,设计了基于汇编程序解释器的课程任务,以下分别从课程目标,任务设置、组织与要求及其他教学措施等方面阐述该门课程的设计及相应思考。
2.1 课程对象与目标
程序设计综合实践课程是计算机专业学生大三上学期的专业基础课,参与课程学习的为计算机科学与技术、软件工程、网络工程、信息安全、物联网等专业的学生,之前已经学习了计算机程序设计(C 和C++)、离散数学、数据结构、计算机原理等专业课。同时,所有学生会在同一个学期学习操作系统,部分学生会学习编译原理。该课程的主要目标是通过一个复杂工程问题锻炼并提升学生使用C/C++语言进行程序设计的能力,对一定规模软件系统进行分析、设计、编程实现以及测试、调试的能力,系统级设计、实现与调试能力,数据结构及算法设计能力,进而整体提升学生的系统能力。
2.2 课程任务
课程的基本任务是使用C/C++语言设计实现一个简单MIPS 指令集[7]汇编程序的解释器。解释器的输入为一个简单MIPS 指令集汇编程序及初始的内存文件(可选),输出为包含程序中输出指令与输出内容的文件。MIPS 指令集广泛用于计算机体系结构的教学中[7],选择该指令集也是考虑到学生在大二下学期刚学习完计算机原理课程,对指令集、汇编等相关概念比较熟悉。
课程使用的指令集为一个32 位的Load/Store 指令集,主要包括内存操作指令、I 型指令、R 型指令以及J 型指令4种,其中I 型指令为立即数指令,R 型指令为寄存器操作指令,J 型指令为跳转指令。指令集中有32 个32 位寄存器,其中第一个寄存器R0的值永远是0,并且禁止被修改。指令集中包含算术与位运算指令、移位指令、测试指令以及跳转指令。图1 给出了课程中使用的指令集语言文法(起始非终结符为P)及一个示例程序,其中IMM 和LABEL 分别为一个整数和一个标识符,为了简洁省略了其定义。
Fig.1 Syntax of a simple MIPS assembly language and an example program图1 简单MIPS 指令汇编语言语法与示例程序
指令集语言是一个类汇编的语言,例如图1 中的示例程序为计算0~100的和并输出。该课程对学生实现的解释器程序的输入与输出均作出了要求,要求通过命令行的-i、-m 和-o 输入参数分别指定输入的汇编源程序文件、初始内存状态文件以及输出文件,在提升学生对于程序命令行输入参数理解的同时,也有利于课程开展自动评测。因此,学生在熟悉指令语言的基础上需要实现以下基本功能:①汇编源程序文件、初始内存状态文件的读入和解析,同时还包括汇编指令及内存在解释器中的数据结构表示;②指令的解释执行,包括寄存器模拟、指令模拟执行以及信息文件输出。
课程任务的基本要求是解释器能够正确支持语言中所有指令的解释执行。同时,为了体现差异并进一步提升学生的算法设计及程序优化能力,课程设置了两个固定的高级优化任务:一是基于数据流和控制流的指令切片;二是基于并行分析的指令并发解释执行。此外,学生也可自由选择其他任务,例如浮点指令的支持、面向高级语言的MIPS 指令翻译、执行效率提升机制解释等。
课程要求程序编译和运行的操作系统平台为64 位的Ubuntu 14.04,编译器为GCC 4.8,并使用GNU Make 作为编译系统。要求学生自学Linux 平台的基本命令行操作、GCC编译器的基本使用以及GNU Make的使用方法,并亲自编译程序的Makefile 文件,以加深对程序编译、链接过程的理解。表1 给出了课程任务内容与系统能力要素培养之间的对应关系。
Table 1 Correspondence between the main tasks specified by the course and the elements of system development ability表1 课程指定主要任务与系统能力培养要素之间的对应关系
2.3 课程组织与评分规则
课程总计40 学时,在第一次课上给出课程任务需求并介绍简单MIPS 指令集汇编语言,然后将课程分为3 个阶段。表2 给出了每个阶段的主要任务、目标、时间安排和评分要求,同时要求每个学生独立完成课程任务。
Table 2 Three course stages of the curriculum表2 课程的3 个阶段
第一阶段只需要完成文件的读入及解析,因此该阶段要求将解析后的指令程序及内存信息按照固定格式输出到结果文件(-o 参数指定的文件)中,然后根据文件内容判断测试用例是否通过。第一阶段和第二阶段会给学生一些测试用例用于本地测试,这些测试用例会涵盖所有指令,同时在规模和程序功能上具有一定的差异性,但这些测试用例与每个阶段用于评测的测试用例没有交集。第三阶段的最后3 个测试用例是专门针对指定高级优化设计的,这些用例会保证如果没有进行指令切片或并行执行的优化,将不能在限定时间内得到结果。
课程基础成绩评分规则为:完成第三阶段的基本要求(通过第三阶段的前7 个测试用例和第二阶段的10 个测试用例)后的得分至少为65 分,否则视为课程挂科;然后对所有满足基本要求的作业,在第三阶段的10 个测试用例上根据性能(执行时间)进行排序,这个排序会保证最后3 个性能优化设计的测试用例权重较高,通过最后3 个任一测试用例的作业一定比3 个都没通过的作业排名靠前,最后3个测试用例的权重一样,前7 个测试用例的权重也一样;最后,在对通过基本要求的作业进行性能排名的基础上给出基础成绩,在95 分(排名第1)到65 分(最后1 名)之间根据排名均匀分布。在基础成绩之上,结合前两个阶段的历史扣分情况、作品创新性(如其他性能增强方法、浮点指令支持等)、分享活跃度、提交时间等方面综合给出学生的最终成绩。
2.4 其他教学措施
2.4.1 自动评测平台
根据课程的任务内容和评分规则,学生需要及时获取所开发的作品是否通过测试用例以及具体排名信息。为此,课程构建了自动化作品评测平台,学生可以在每个阶段于评测平台上传作品,该平台会自动编译、测试作品,然后将测试结果(例如哪些测试用例通过、哪些没有通过)反馈给学生,同时在第三阶段还可以实时给出学生作品的排名。自动化评测平台可有效提高课程评测效率,激发学生的学习热情。
2.4.2 指令模拟实现的缺陷定位辅助
解释器程序的调试比较困难,主要体现为程序中的缺陷难以定位。同时,由于第二、三阶段评测使用的测试用例与用于本地测试的测试用例不同,可能会存在本地测试用例通过但在评测平台不通过的问题。为此,笔者为本地调试提供了参考实现的可执行程序,用于指令模拟实现中的缺陷定位。缺陷定位的主要思路为通过差异测试定位可能导致的指令失效。参考实现可以将测试用例中指令程序每执行一条指令后的寄存器状态以及执行的指令输出,学生也可以在自己作品中实现相同格式的寄存器状态和指令输出,然后比较参考实现和自己作品的输出差异,第一个不一样的状态及指令就可能是导致缺陷的原因。此外,自动测试平台上也提供了相同的在线缺陷定位功能,为学生在无法获得测试用例情况下的缺陷定位提供一定辅助。
2.4.3 作品查重
为了杜绝抄袭现象,在每个阶段验收后会使用JPlag 工具[8]对所有作品进行查重。JPlag 工具是Java 实现的代码相似度评估工具,支持C、C++、Java 等语言程序的相似度分析。在前两个阶段,查重的规则是所有作品两两查重,然后每个作品的相似度是其与所有其他学生作品的相似度之和。查重结果会以相似度矩阵的形式公布给学生,老师通过作品相似度进行重点关注,在前面两个阶段被认定为抄袭的学生直接挂科,课程任务提前终止。在第三阶段,除了开展前面两个阶段的查重,还会与往年通过课程考核的所有作品进行查重。查重功能也集成到了自动评测平台。
2.4.4 课程资源与经验分享
课程先后使用确实(Trustie)平台和头歌(Educoder)平台作为在线课程平台,用于课程资源的发布和学科管理,包括发布课程任务的文档、胶片,管理学生信息、学生最终提交的设计实现文档等。此外,为了促进学生之间的交流,激发其学习热情,课程先后使用Trustie 平台和知士荟(LearnerHub)平台鼓励学生发贴交流程序开发过程中的经验和教训,并且会在最终课程成绩中综合考虑发帖情况。
2.4.5 文档与代码提交
设计开发文档的撰写能力也是软件开发能力的一部分。为了提升学生的文档撰写和文字表达能力,在第三阶段结束后,要求每个学生提交一个课程任务总结报告,在报告中详细描述课程任务开发过程中的架构和算法设计、实现细节、关键代码片段、课程收获和感悟等,最终课程成绩中会综合考虑文档质量。在代码方面,学生最终的代码作品以第三阶段在评测平台上提交的代码为准。
3 实践效果
从多个方面阐述课程3 年改革实践的效果。第一年,考虑到是课程改革初期,同时评测平台也是刚开始构建,需要评测服务器的性能,课程采取2 人1 组,一共50 个组99名学生(有一组是1 个学生);第二年,采取1 人1 组,一共110 名学生;第三年也是1 人1 组,一共80 名学生。
3.1 成绩趋势
图2 给出了3 年课程改革的成绩统计情况,平均成绩分别为75.4、72.1 和75.9 分,课程通过率分别为98%(49/50)、79%(87/110)、97.5%(78/80)。分析其原因,第1 年是2人一组,存在“摸鱼”现象(从学生后续的课程反馈中也印证了这一点);第2 年1 人1 组后,通过率和平均分有所下降;第3 年在1 人1 组的情况下,通过率和平均成绩回升到与第一年相当的水平。此外,第1 年有26 个组至少通过1个第三阶段中3 个针对程序优化的测试用例,有5 组3 个全部通过;第二年分别为22 和1;第三年分别为27 和7。这些统计结果证实了课程改革的成效。
3.2 代码规模
图3 给出了3 年来课程提交代码规模的统计信息,每年的平均代码规模均为750 左右。虽然平均代码行数不多,但都是从零开始编写,设计和调试的工作量比较大。通过平时学生反馈可知,大部分小组在完成课程任务的过程中都对代码进行了多次重构。同时,可以观察到第二年平均代码量相比第一年有所下降,原因与之前课程平均成绩下降的原因一致。第3 年的平均代码量相比前2 年有所提升,同时大于1 000 行代码作品的数量(19 个)和比例(23.7%)也最高,这些结果证实了课程改革的有效性。
Fig.2 Course grade statistics图2 课程成绩统计
Fig.3 Source code lines statistics图3 作品代码规模统计
3.3 提交次数
图4 给出了作品提交次数的统计结果,横轴为提交次数,纵轴为时间。第1 年和第2 年的平均提交次数接近,均为23 次左右,第3 年的平均提交次数为64 次。原因是第1年课程改革伊始,考虑到评测系统的稳定性以及服务器性能,作品评测设置的是每45min 一次,导致学生平均提交次数偏低。之后两年学生在上传作品后可以马上进行测试,但第二年由于是首次1 人1 组,课程要求对部分学生有一定难度,存在部分学生放弃的现象,提交小于5 次的有25组,因此整体拉低了平均提交次数,但有学生最高提交了303 次。提交次数整体趋势基本与课程成绩一致。
3.4 学生反馈
课程成绩公布以后,要求学生在评测平台上给出对课程的反馈与建议,部分学生没有反馈。图5 是根据学生的反馈信息人为分类后得到的结果,其中“无法判断”是指学生在反馈与建议中没有提及自身收获方面的情况,因此无法判断。3 年的课程改革过程中,学生明确表示有收获的比例分别为80.3%(37/46)、70.3%(61/87)、57.3%(43/75)。从2 人1 组切换到1 人1 组后,学生反馈有收获的比例降低,也符合预期。此外,3 年中每年都有1 组反馈没有收获,其中前2 年的原因是认为课程任务难度和工作量大,希望后续老师能提供更多帮助,第3 年的1 组是觉得任务偏简单。
Fig.4 Statistics of the number of submissions图4 作品提交次数统计
Fig.5 Student feedback statistics图5 学生反馈统计
在课程改革过程中,根据学生的反馈、经验分享以及平时的答疑情况,发现学生在计算机程序设计能力方面得到了较好锻炼,具体包括数据结构的设计与选择、C/C++程序中的内存操作、GCC 中编译优化的使用、文件读写与解析、优化算法设计、代码调试与优化等多个方面,这些都与系统能力培养直接相关。此外,学生普遍反映自身自主探索、独立编程能力得到了提升。在资源分享平台,学生自发分享的设计开发经验帖子有70 个,内容涉及到上述各个方面。这些经验分享在帮助后续学生开发程序的同时,也为课程任务的更新提出了挑战,一些面向高级功能需求的开发任务与编译原理(解析、面向高级语言的代码生成)、操作系统(基于多线程的并行执行)课程也形成了联动。此外,课程评测平台的功能和性能也在不断迭代优化,例如在第二年加入了自动缺陷定位功能,同时开发了Java 版本的解释器参考原型以及基于随机测试的汇编程序随机生成工具,用于辅助各个阶段的测试用例设计。
4 结语
针对计算机专业学生系统能力培养的问题与挑战,对程序设计综合实践课程进行了改革实践,设计了基于简单MIPS 指令集汇编程序解释器的课程任务以及基于3 个阶段的课程组织形式;开发了支撑课程开展的自动评测平台,通过自动评测、差异测试、自动查重、自动缺陷定位等手段,不断提升课程质量和实验效果,在学生系统能力培养方面取得了不错效果。
在后续深入推进教学改革的过程中,将从以下几个方面作出改进:①课程任务的进一步扩展和优化,包括引入其他经典的复杂工程问题或具有特定时代背景的问题,例如SAT 求解器和无人系统开发,同时设计更多指定优化任务;②进一步完善自动评测平台的性能,为其他课程提供支撑;③针对后期有部分学生通过在作品中注入无效代码以通过查重的现象,优化查重功能,提升查重精度。