《Java语言程序设计》课程分析与总结(上)
自2003年以来,我承担了北京理工大学计算机学院《Java语言程序设计》这门课的教学任务,从2000级到2011级,十年弹指一挥间。
这十年中,软件技术的进步可以用上“飞一般”这样的形容词。相应地,我这门课程也在不断地变革,尤其是考核方式,由经历了以下过程:
闭卷考试(一张试卷定成绩)-->闭卷考试+课程设计-->开卷考试(可带书,不允许带电脑)+课程设计-->彻底的开放考试(可带书、可带电脑、可上网)+课程设计
典型试题及答案参见以下链接
2008级考试试题在这里(2010年)
2011级考试试题在这里(2013年)
之所以走这条路,是因为我日益形成了这样的一个观点:
以闭卷方式考程序设计,是最无用的考核方式,遗害无穷。
为什么这么说?
先来看看闭卷方式所带来的弊端。
1 它要求学生去机械记忆大量的知识。
以Java而言,你至少得记忆两部分内容:Java语法和JDK中的常见类的用法。语法还好说,JDK如此庞大,有几人能背下来?又能背下多少?
更重要的,这诱导了学生将高中养成的学习方法直接应用于大学课程学习,并且这种方法看上去还真的“有效”,其实是误人子弟。程序设计语言怎能这样学习?
扣心自问:我用Java/C#写了近十年程序,对Java/C#编程可谓熟悉,但又能记得住JDK和.NETFramework中的多少内容?我在实际开发时不也是需要查询互联网和官方文档吗?
另外,Java/C#等语言的编译器己相当完善,开发者看到的编译错误信息已经相当接近真实的错误原因,再加上Eclipse/Visual studio之类的IDE所提供的“智能更正”建议,语法问题早就不是程序设计语言学习的主要障碍。再让学生花大量时间去记忆这些语法规则,实无必要。
2 它绑住了出题者的手脚
由于必须能让大部份学生及格,因此考试内容必须严格地限制于书本,特别地,还应该给学生在考试前以“辅导”的名义“划定考试范围”,不然,大部分不及格,学生闹将起来,还真不好收场。
通常来说,教材的编写和出版要经历一个比较漫长的周期,而且一旦出版,至少保持数年的稳定是基本要求,但这对于计算机专业而言很要命,技术进步与知识更新极快,别说数年,一年过后,教材中的有些内容可能就过时了,还有些内容甚至可能被废弃。闭卷考试强制必须在这个“陈旧”的锅里炒冷饭,炒饭的人痛苦,吃饭的人也痛苦。
何苦呢?
所以近些年来,我基本上放弃了使用固定的教材,也放弃了闭卷考试这种形式,而代之以开放式的不断更新的“PPT+大量实例”的教学方式。
简单地说:我把Java编程相关的内容进行分解,每部分组成一个教学模块,每个知识点或编程特性,我都提供一个可以运行和修改的实例。这些实例不仅展示了特定的知识点或编程特性,实际上还是一些可以复用的代码模块,学生既可以通过运行、修改这些实例掌握特定的知识与技能,更能通过把这些代码模块进行组装,开发出新的程序。
由于放弃了课本而采用了模块化的PPT+实例,使得我可以很方便地年年更新教学资料,淘汰掉老的内容,补充新的内容。比如近几年我就把Java语法的教学课时进行大幅压缩(全留给学生自学),加入了正则表达式、Ant、Android程序设计等内容,同时增加对开发中非常重要的基础技术(比如多线程开发、对象集合、数据结构与算法)等内容的介绍。
我是一名教育工作者,并不算程序员,但我一直对软件开发有着深厚的兴趣,也写了多年的代码,因此在设计Java考试试题时,我力图让这些试题更接近真实的开发场景,并从中展示出我希望学生努力的方向。
下面就以去年年底设计的Java考试试题为例,简要地说说这些试题的设计构想。
2011级的Java课程成绩由两部分组成,卷面考试成绩占60分,课程设计占40分。
卷面考试允许学生上网甚至QQ直接沟通。
有些人可能会疑虑,这么干,不是鼓励学生相互抄袭吗?
这要看你怎么出题和怎么要求学生答题。
我出的题大多直接从开发实践中抽取,就算你百度,也只能找到部分答案,事实上,在考场上,仅2个小时,我很确信学生根本不可能有大多的时间去慢慢百度一个问题,敲代码都来不及,还有时间去QQ?
第一部分判断题,这是知识性的题型,我尽量选取一些比较有趣和重要的知识点,这些知识点我大都在课堂上讲过或现场演示过,来上课的学生应该会有这些印象,但那些没来上课的学生,做错就别怪我了。
这里我就学生逃课多说两句:
我在课堂上点名抽查学生的上课率,记忆里十多年来不超过五次,就这还大多是教务处要求的。我一直认为,如果你讲的课只会照本宣科,还用点名这种方式强制学生来听,实在是一种无能的表现。
我就从不勉强学生来听课。
我的态度是:
你来,我就尽量把一些“干货”教给你,当然,你学不学得会是另一回事,我至少保证自己有多少货就倒多少货。
学校又不是企业,师生之间没有竞争关系,也没有根本上的利益冲突,藏什么私?相反,学生日后越牛,老师脸上越有光彩,就越得意。
想想若干年退休之后可以和别人吹牛:XXX你知道吗?大牛吧!当年我还教过他呢,当时就发现此学生厉害……
个人虚荣心得到满足,爽啊!
只可惜两点:
1 本人属水平有限公司,能倒的货实在有限。
2 不少学生实在给耽误了,大一大二积累的开发经验少得可怜,我讲的那些从实践中得出的东西,能产生共鸣的机率很少。
尽管如此,我还是抱着这样的观点:
该讲什么还是要讲什么。即使学生听不懂,至少也可以留下个印象,等他们毕业走上工作岗位,再掌握起来就会快得多,毕竟:没吃过猪肉,至少也见过猪跑步嘛!
至于那些喜欢逃课的学生(我相信他们要逃绝不会只逃一门课),如果他们逃课把时间用到正道,我不但不反对,甚至还赞同,我认为这些是有主见的学生。但如果逃课躺在宿舍里睡觉玩游戏看大片天天做梦,大好光阴花在这上面,……,如果是我的孩子这样,我早一巴掌轮过去了!
事实上,我不是你老爸,你爱来不来,爱学不学,随便。我自己还一堆事呢,没哪个闲功夫管你!
最后再唠叨一句(这似乎是老师的通病——话多):每个人最终都会为自己的行为买单。
回到正题,再谈谈试卷。
第二部分是简答题,考查的面相当广。
试卷的第11题涉及到Java开发中的一个独特现象:100.1*2能得到正确的值,100.1*3就不行了。我估计那些“通过看书学编程”的学生99%是不知道会有这个运行结果的,我也不记得有哪本教材谈到过这个问题,这个现象是我在开发中遇到的,就把它弄到试卷上了。这题就看学生的分析能力了。
第12题是Java编译器在编译泛型代码时的“擦除”特性,我在课上讲过,PPT中也有。知道这个词是一回事,知道用是另一回事。有些同学此题空白,why? 不超过10行代码,你敲到Eclipse里试一试,不就看到提示了?
第13题是Android跨线程更新TextView的问题。我主要是看看有没有学生进行过稍微深入一点的Android开发实践。这道题回答得不好是意料中的。
第14题本意是送分题,我订的答题标准是:只要答案中有LinearyLayout或RelativeLayout之类词语出现,就给分。但实际答题情况让我无语了,有如此多的学生此题是空白。
我像个老和尚念经似的天天强调“动手动手”,总有些学生懒到一学期不打开电脑编写哪怕一个Android应用程序。好吧,偶承认败了,偶不当老和尚了,偶还俗。
第15题是程序性能优化的题目。关于Integer与int之间的区别与联系我在课上讲过很多,还在课程上展示过JDK的源码和反汇编出来的字节码指令。相信给学生的印象深刻,因此大多数学生都知道Integer比int慢的原因,但只有几个能发现并解释第二个现象(因为我课上没直接讲):为什么不管是Integer还是int,第二次、第三次代码运行的时间比第一次要少那么多?
第16题是程序架构设计的问题。对学生开发经验少的现状我心知肚明,因此对此题我从不报太大指望,学生知道用try/catch就行了。如果知道可以将JVM抛出的通用异常转换为特定的业务逻辑异常那就不错了(有一两学生这么干了),如果还能进一步谈到多层架构下的异常处理策略那就更好了(没有一个学生谈到这些)。
第三部分是编程题。
这部分题目我要检讨。我低估了把代码从电脑上抄到试卷上的工作量,所以100%的学生时间不够,最后一道题没有一个能做完的。
对于这个问题,我的调整策略是:
下一届学生我将不再要求他们提供完整版的源代码。你只需以文字、流程图甚至是手绘的示意图方式告诉我你的编程思路,再列出其中的关键代码就行了。
这可能会带来一定的副作用,比如某学生可能编程不错,但文字功夫太差,写的东西我看不懂。但我又想,表达能力也是一种很重要的能力,如果你写的程序文档连我这种具“专业知识背景”和“超强纠错能力”的老师都看不懂,那其他人就更看不懂了,扣你分也别叫屈!
抓紧时间学学语文很重要啊!
还有,练练字也很重要。有些学生试卷上那个脏啊,那个乱啊,我就不说了……
心情恶劣,多扣两分,实为人之常情。别怨我。
第17题要求一个方法向外界返回多个值,一个纯粹的编程技巧考查题,这题几年前考过。就有学生百度到了,直接Copy到卷子上,末了怕我扣分,又注明“这是通过百度从金老师的博客上Copy来的,不要算抄袭哟……”。
“连变量名都没改,原封不动Copy,原来这不是抄袭”——我靠,现代版孔乙已啊。
不过这几个学生还算诚实坦白,我心柔软,都没扣分。
第18题要求把一个数组转换为环型链表。很简单的题,但我没发现有同学在方法代码前部添加数据有效性检测的代码:比如传入的数组参数为null或为空的情况。看来下学期OOAD我得好好讲讲防卫型编程问题。
第19题是一道简单的多线程题。有些学生比较聪明,他们发现这三个工作任务内容高度一致,所以只写了一个Thread子类,给其添加一个begin和end字段指明循环的起始值和结束值。有些学生就比较机械了,三个线程就写了三个类,这三个类大量代码是一样的,就是循环起始值与终值不同,于是抄代码到卷子上抄得手都断了。
同学,对造成你抄代码抄得手断的情形我表示深切同情与自责,并保证以后不会再要求大家大规模地抄写代码。但你是不是也该动动脑筋写出聪明一点的代码呢?
第20题是一个比较综合的题目。前面也说了,由于我考虑不当,100%的学生没一个能答完全题的,因为没时间了。我相信如果给与足够的时间,应该会有些能干的学生完成这道题。
第一小问没什么好说,如果不用JDK 7中的现成组件,自己写一个递归遍历文件树的方法就行了。递归是一个程序员的基本功,递归都写不出来,你可以洗洗睡了,别干这行了。So easy.
第二小问排序。有些同学直接使用Collections.sort方法升序排序,再reverse()一下,它就降序排列了,直接使用JDK中相应组件而不是手写冒泡排序等方法。这是聪明的学生。
在实际开发中,当你需要实现某个功能时,先去查查JDK文档,看看有没有现成的,没有或现有组件不能满足需求,再自己动手。不要动不动重新发明轮子,事实证明,自己发明的轮子往往隐藏着诸多自己意识不到的BUG或问题,通常不如那些经过比较严格测试的JDK中的“轮子”可靠。
第三小问分组。这里的关键是你需要选择合适的数据集合类型。不知道是我写的问题描述有问题还是学生在理解上有问题,我明明在题目说明中写了“比如”两个字,N多学生却偏偏认为“只有”三个组——txt、exe和无扩展名。同学,你见过哪个操作系统中只有这三种类型扩展名的文件?那些不是这些扩展名的文件你放到哪去呢?直接忽略掉?
这道题中:组的个数是变的,每个组中的文件个数也是变的。
针对这个需求,你选择JDK中的哪些对象集合来构造出符合本题场景要求的数据结构?这是这道题的关键点。
第四小问线程同步。我的PPT中介绍了N种用于实现Java线程同步的组件与方法,还有N个例子,很多都可以用在这里。但没有一个学生能正确地完成这一小问的开发任务,我估计原因有二:
(1)时间不够了
(2)学生自己没编写过类似的线程同步程序。要现场马上写一个出来,这要求似乎还是有点高的。
好了,到此为止,试卷算是分析完了。
最后说说我的试题设计原则:
淡化语法,重点检测学生分析与解决问题的能力,尽可能地让分数高低与学生的开发能力高低相对应。引导学生注重实践,彻底杜绝死记硬背的学习方式。
下一部分将对本学期学生提交上来的课程设计大作业作点评,内容更为有意思。
敬请同学们关注。