本文在中科大软件学院孟宁老师的指导下完成,意在通过对小型程序的分析来帮助体会软件工程方法、思想。
本文的参考资料及资源来自:软件工程——码农的自我修养。
1. VS Code开发环境配置
1.1 VS Code 安装
有关VS Code的安装配置可以参考该资料:https://mp.weixin.qq.com/s/sU9Wh12JZqQyJfEBBafFAQ
在本文中,我们对此进行简单介绍。我们可以在VS Code官网下载,它是一款免费的轻量级IDE,通过扩展插件可以支持多种语言。
下载地址:https://code.visualstudio.com/#alt-downloads
1.2 C/C++编译器下载
在VS Code中,当我们通过加载扩展插件来实现支持时,它并不会包含语言的编译器与调试器。这里所说的编译器与调试器,对于C++而言,即为MinGw。
C/C++编译器则为MinGw: http://www.mingw.org/
下载完成并安装后,我们可以得到以下目录:
1.3 环境变量配置
通常情况下,在安装MinGw时,会建议或者要求我们将其添加到环境变量中。但如果我们忘记了配置,也可以通过以下方法手动进行配置。
首先,我们需要获取到MinGw在我们系统中的安装位置,如上所示,即为:C:\MinGw 。
之后,右键点击此电脑 -> 选择属性 -> 高级系统设置 -> 环境变量,在此处可以管理我们计算机的环境变量。我们找到下方系统变量中的Path选项:
通过编辑Path选项,新建添加新环境变量地址,填入上述安装位置。需要注意的是,我们此时需填入C:\MinGw\bin。
以上即为C/C++编译器的环境安装配置过程。对于其他语言,如Java、Python、Golang,我们均可以采用此种方法。
我们可以通过命令提示符查看MinGw的安装是否成功,使用gcc -v指令:
1.4 VS Code中配置C/C++开发环境
1.4.1 加载C/C++扩展
在VS Code左侧扩展选项中,我们搜索C++即可搜索到插件,点击Install后即可安装完成:
1.4.2 task.json与launch.json配置
VS Code通过 task.json 配置文件来获取编译程序的方式,通过修改task.json就可以利用VS Code进行gcc编译源码了。
一个参考的task.json文件配置如下:
{ "version": "2.0.0", "tasks": [ { "type": "shell", "label": "gcc build active file", "command": "gcc", "args": [ "-g", "${file}", "-o", "${fileDirname}/${fileBasenameNoExtension}"// ], "group": { "kind": "build", "isDefault": true } } ] }
关于task.json的格式,可以参考以下文档:https://go.microsoft.com/fwlink/?LinkId=733558
VS Code通过launch.json配置文件中的配置来调用调试器对程序进行调试。通过快捷键Ctrl+Shift+P,搜索launch.json,可以打开launch.json进行配置。
一个参考的launch.json的配置文件如下:
{ "version": "0.2.0", "configurations": [ { "name": "gcc build and debug active file", "type": "cppdbg", "request": "launch", "program": "${fileDirname}/${fileBasenameNoExtension}", "args": [], "stopAtEntry": true, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": false, "MIMode": "gdb", "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true } ], "preLaunchTask": "gcc build active file", "miDebuggerPath": "C:\\MinGW\\bin\\gdb.exe" // 此处为上述的MinGw安装地址 } ] }
关于launch.json文件的配置,可以参考以下文档:https://go.microsoft.com/fwlink/?linkid=830387
1.4.3 验证安装
可以看到,运行以下示例成功,说明环境配置成功!
2. 代码中的软件工程思想
2.1 代码注释风格
良好的注释风格,可以帮助代码阅读者、维护者等人员快速明确地了解到代码所实现的功能等,方便Debug。一个优秀的代码注释风格如下,这是孟宁老师所给出的案例,非常值得学习:
从中,可以清楚地看到,这个文件中的代码的文件名、作者、模块名、语言类型、运行环境、版本时间以及描述等,对我们理解代码十分有帮助。
2.2 模块化设计
2.2.1 模块化原理
模块化(Modularity)是指,在指软件系统设计时保持系统内各部分相对独立,以便每一个部分可以被独立地进行设计和开发。这样使得我们在开发过程中,每一个模块都执行自己单一的功能目标,可以独立于其他软件模块,这样也就方便了开发人员之间的配合。
模块化的基本原理是关注点的分离(Soc,Separation of Concerns),翻译成中文其实大概便是“分而治之”。
2.2.2 模块化程度
在实际的软件设计开发中,我们一般使用耦合度(Coupling)和内聚度(Cohesion)来作为软件模块化程度的高低,它们同样是判断一个软件设计优良程度的重要参考。
耦合度,是指一个软件中各个模块之间的依赖程度,从高到低在大致上可以分为紧密耦合(Tightly Coupled)、松散耦合(Loosely Coupled)。我们所追求的是松散耦合,它表明我们所设计的软件各个模块的依赖程度较低,是比较好的设计。
内聚度,是指一个软件模块内部各种元素之间互相依赖的程度。理想的内聚是功能内聚,也即一个模块只做一件事,只完成一个主要功能。
2.2.3 模块化设计:源码分析
在实现模块化设计时,我们可以将多种数据封装成结构体、将实现功能封装成一个文件,可以帮助我们简化代码:
在上图中,我们将数据结构以及函数声明放在一个linklist.h的头文件中,其实现放在linklist.c文件,需要调用时再在main函数中去调用,这样就实现了较低的耦合度,也方便我们作为程序员去debug。
2.3 软件模块接口
2.3.1 接口的定义
接口是通信双方共同遵守的一种规范,在软件系统内部一般的接口方式是通过一组API函数来约定软件模块之间的沟通方式。面向对象与面向过程的语言在对接口的是实现一般不相同。前者的接口是对象对外开放的一组属性和方法的集合;后者则是数据结构和操作这些数据结构的函数等。
2.3.2 接口的应用
上图借用了孟宁老师提供的工程源码文件中的某个源码文件。在这个文件中,可以看到其声明了许多接口,这些接口的实现会放在.c文件中。但不通过具体的实现代码,我们依然可以通过接口的名称、参数、返回值以及相关描述了解到大概的功能,这些接口的也可以被多次重复使用,提高了代码的复用性。
2.4 线程安全
2.4.1 线程的基本概念
线程(thread)是操作系统能够进行运算调度的最小单位。它包含在进程之中,是进程中的实际运作单位。一个线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
2.4.2 可重入与线程安全
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行读写操作,一般都需要考虑线程同步,否则就可能影响线程安全。
可重入(reentrant)函数可以被多个线程并发执行,而通常不会导致由于共享数据而导致结果错误;不可重入函数则在多个线程并发执行时,如果不能保持线程互斥,那么就会导致结果的错误。对于软件工程而言,我们应该以比较悲观的方式去评估函数:即可重入函数不一定是线程安全的,可能是线程安全的函数就不是线程安全的函数,不可重入函数一定不是线程安全的函数。
2.4.3 线程安全:代码分析
首先,对于线程安全,我们所关注的焦点一般在以下几个方面:
i. 所有的函数是不是都是可重入函数:分析函数有没有访问临界资源,若有则必须仔细分析其互斥的处理过程;
ii. 不同的可重入函数有没有可能同时进入临界区:读写互斥应该如何去考虑;
下面,同样地,通过孟宁老师所给出的代码,我们选择一段代码来加以解释:
在需要进入临界区时,我们对代码进行了加锁操作以确保其他函数无法使用临界区的资源,当我们退出临界区后及时解锁供以其他函数访问,最后销毁互斥锁,这就是一个典型的线程安全代码。
3. 总结
孟宁老师于课堂上讲解的各个知识点,使我对软件工程中的开发思想、方法有了更深一步地理解。同时,经过这篇博客的书写,我对这些知识的理解更上一层楼了,更为重要的是,动手能力也有所提高,在此深表感谢。
最后,再次强调,参考资料于此:软件工程——码农的自我修养