麻雀虽小,五脏俱全——由C语言小程序深入学习软件工程和设计开发规
苏思畅 - 原创作品转载请注明出处 - 《软件工程(C编码实践篇)》MOOC课程
http://mooc.study.163.com/course/USTC-1000002006
一、课程认识和学习心得
2017年春天,我有幸选修了孟宁老师主讲的高级软件工程课程。这门课从一开始就让我感受到了不一样的气息:线上学习和线下交流同步进行;由学生交流而不是老师讲课为主导的课堂;通过循序渐进的在线编程实验不断引入开发设计中的规范和理念。这些与我之前接触过的课程内容有着极大的差异,它们无不让我获得了一种新鲜感。
在线上部分的MOOC课程中,整个学期的教学内容通过一个看似简单的C语言Menu小程序前后贯通,通过不断地对之前已经写就的代码进行迭代,我们在为期七周的课程周期里学习了C语言编码规范、模块化通用化的结构设计、回调函数的合理使用、面向接口的程序设计等知识点,并进行了充分的实践。可以说,麻雀虽小五脏俱全,尽管这是一个只有三四个文件、数百行代码组成的小程序,但是其中蕴含的软件工程思想却与市面上成功的大项目大系统别无二致。
在线下部分的课程实验中,我们通过使用Go语言实现线上实验的内容,来初窥这门之前未曾接触过的语言的魅力。同时,也让我们能够抛开语言的束缚,将学到的代码设计思想贯通到其他语言的开发中去。
而在每周课堂上的日常交流中,我们以Google的软件工程过程为主题,分享交流了这家公司在实际生产工作中的实践规律,以了解软件工程理论在实际工作中的作用和影响。
可以说,这门课程的学习时间虽然短暂,但它传授给我的知识却让我终身受用。《资治通鉴》中有云:”夫事未有不生于微而成于著“,任何大项目都是从小程序开始积累的,只有在今日通过小程序深入了解了软件工程的理论,才能在日后创造大项目的成功。
二、线上实验总结
在为其七周的线上实验中,我一共进行了六次在线编程练习,并撰写了实验报告。在这里我将对所有的实验内容进行简要的总结。至于所有的实验成果,可以使用以下链接来进行访问和查阅。
实验一:写一个Hello World小程序
第一次实验会让你初步了解Linux下的一些基本的操作,尤其是使用gcc进行C语言程序开发的操作。此外在提交代码时需要进行基本的git操作,在撰写实验报告时建议使用Markdown语法来进行书写。这些能为之后的开发实践打下良好的基础。
实验二:命令行菜单小程序 v1.0
本次实验是后续实验的基础,之后的所有实验内容都要在本实验所做的菜单小程序基础上进行迭代。本次实验从功能实现上难度不大,建议在开发时养成良好的代码编写习惯,注意代码风格。
实验三:内部模块化的命令行菜单小程序 v2.0
本次实验要求对之前的程序进行模块化升级。过去在使用Java等面向对象语言时可以比较轻松地使用类来进行模块化设计,但在C语言中尚没有这么设计的经验。通过这次实验,可以学习到如何使用链表和函数指针来进行模块化设计,建立统一的调用接口。
实验四:用可重用链表模块来实现命令行菜单小程序 v2.5
本次实验在上次已经用链表实现模块化的基础之上,进一步将链表设计为可重用的模块。这就要求引入一些类似于面向对象语言中接口的概念了。接口定义了链表的基本操作,如添加删除和查找,而这些操作都与具体的业务逻辑无关。在需要使用链表时,才会将我们具体的功能函数放到链表之中,这样就使得链表更为通用,今日的工作可以为之后的实践提供便利。
实验五:用Callback增强链表模块来实现命令行菜单小程序 v2.8
回调函数(Callback)可以让下层代码使用上层代码,使得具体的功能细节可以延迟到上层代码运行是再做决定。同时,由于下层代码大多需要考虑通用性,不能引入具体的业务逻辑,回调函数正是一种延迟实现、让业务与结构分离的优秀方法。
实验七:将菜单小程序设计为可重用子系统
在通过之前的实验将我们以链表模块为基础的菜单小程序实现出来之后,我们便可以将其中的核心部分进行进一步的抽取,形成可重用的菜单小程序。使用者可以利用我们提供的接口,来进一步扩展菜单程序的功能,并将新编写的函数非常便捷地加入到菜单列表中。此外,本次实验还尝试引入了带参数的功能实现,以应对更多样的需求。
三、课程内容学习总结
通过了这门课程的完整学习,我对于软件工程和代码开发规范方面有了以下几点心得认识。
-
养成良好的代码风格
课程上提供的视频和文本资料详细阐述了代码开发时缩进、命名、注释等风格规范,对我产生了重大的影响。以前我也知道代码风格的重要性,却没有去认真了解一般规范是怎样的,只是按照书本样例或者IDE自动对齐来进行。经过老师点拨,我认真总结了实用的代码编写规范,并了解了采用这些规范的理由。现在我的代码风格非常优雅,学习的内容让我获益匪浅。 -
基本的模块化设计理念
这里列出的是代码设计中的一些常见方法:
KISS(keep it simple & stupid)要求一个函数或一个方法,只做一件事。扩展开来,在设计上,一个系统、一个子系统、一个模块、一个类等也只做一件事。
Using design to frame thecode(matching design with implementation)希望including pseuducode,在从设计到实现的过程中加入伪代码要好于直接将设计翻译成代码。
不要和陌生人说话原则(Law of Demeter)要求一个对象应当对其他对象有尽可能少的了解。
学会合理利用控制结构和数据结构来简化代码。
Debug版本中所有的参数都要验证是否正确;Release版本中从外部(用户或别的模块)传递进来的参数要验证正确性。 -
设计可重用的模块
模块设计要求达到高内聚低耦合的目标,方便重复使用。接口设计要体现通用性。
常见接口设计规范有:参数化上下文,生死相依原则,移除前置条件,简化后置条件等,另外还需编写开发者指南,供用户阅读使用。
这里要考虑一个接口通用的问题,并不是越通用越好,因为过于通用需要考虑很多情况,导致模块臃肿、效率低下,因此应该not too specific, not too general。 -
函数可重入性和线程安全的考虑
可重入函数指可以由多于一个任务并发使用,而不必担心数据错误的函数。相反,不可重入(non-reentrant)函数不能由超过一个任务所共享,除非能确保函数的互斥(或者使用信号量,或者在代码的关键部分禁用中断)。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。可重入函数要么使用本地变量,要么在使用全局变量时保护自己的数据。
线程安全指如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
函数的可重入性与线程安全之间存在以下的关系:可重入的函数不一定是线程安全的;不可重入的函数一定不是线程安全的;可重入的函数在多个线程中并发使用时是线程安全的,但不同的可重入函数(共享全局变量及静态变量)在多个线程中并发使用时会有线程安全问题(可能是线程安全的也可能不是线程安全的)。
四、写在最后
本课程的授课以其新颖的形式和内容的实用性对于我们之后的实习和工作都有很大的帮助。非常感谢孟宁老师的教导和与其他参与课程同学们的交流,这些都让我获益匪浅。由于课程只有短短七周,使得实践内容只能局限在一个C语言小程序的格局之内,很多的软件工程开发理念还没能够来得及去接触和好好消化,这不得不说是一种遗憾。想要弥补这一遗憾,可能要在未来的开发实践中自己去慢慢琢磨,细细品味了。