本文在中科大软件学院孟宁老师的指导下完成,意在通过对小型程序的分析来帮助体会软件工程方法、思想。

本文的参考资料及资源来自:软件工程——码农的自我修养

 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/

    下载完成并安装后,我们可以得到以下目录:

    代码中的软件工程-LMLPHP

1.3 环境变量配置

    通常情况下,在安装MinGw时,会建议或者要求我们将其添加到环境变量中。但如果我们忘记了配置,也可以通过以下方法手动进行配置。

    首先,我们需要获取到MinGw在我们系统中的安装位置,如上所示,即为:C:\MinGw 。

    之后,右键点击此电脑 -> 选择属性 -> 高级系统设置 -> 环境变量,在此处可以管理我们计算机的环境变量。我们找到下方系统变量中的Path选项:

    代码中的软件工程-LMLPHP

    通过编辑Path选项,新建添加新环境变量地址,填入上述安装位置。需要注意的是,我们此时需填入C:\MinGw\bin。

    以上即为C/C++编译器的环境安装配置过程。对于其他语言,如Java、Python、Golang,我们均可以采用此种方法。

    我们可以通过命令提示符查看MinGw的安装是否成功,使用gcc -v指令:

    代码中的软件工程-LMLPHP

1.4 VS Code中配置C/C++开发环境

1.4.1 加载C/C++扩展

    在VS Code左侧扩展选项中,我们搜索C++即可搜索到插件,点击Install后即可安装完成:

    代码中的软件工程-LMLPHP

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 验证安装

    可以看到,运行以下示例成功,说明环境配置成功!

    代码中的软件工程-LMLPHP

    

2. 代码中的软件工程思想

2.1 代码注释风格

    良好的注释风格,可以帮助代码阅读者、维护者等人员快速明确地了解到代码所实现的功能等,方便Debug。一个优秀的代码注释风格如下,这是孟宁老师所给出的案例,非常值得学习:

    代码中的软件工程-LMLPHP

    从中,可以清楚地看到,这个文件中的代码的文件名、作者、模块名、语言类型、运行环境、版本时间以及描述等,对我们理解代码十分有帮助。

2.2 模块化设计

2.2.1 模块化原理

    模块化(Modularity)是指,在指软件系统设计时保持系统内各部分相对独立,以便每一个部分可以被独立地进行设计和开发。这样使得我们在开发过程中,每一个模块都执行自己单一的功能目标,可以独立于其他软件模块,这样也就方便了开发人员之间的配合。

    模块化的基本原理是关注点的分离(Soc,Separation of Concerns),翻译成中文其实大概便是“分而治之”。

2.2.2 模块化程度

    在实际的软件设计开发中,我们一般使用耦合度(Coupling)和内聚度(Cohesion)来作为软件模块化程度的高低,它们同样是判断一个软件设计优良程度的重要参考。

    耦合度,是指一个软件中各个模块之间的依赖程度,从高到低在大致上可以分为紧密耦合(Tightly Coupled)、松散耦合(Loosely Coupled)。我们所追求的是松散耦合,它表明我们所设计的软件各个模块的依赖程度较低,是比较好的设计。

代码中的软件工程-LMLPHP

     内聚度,是指一个软件模块内部各种元素之间互相依赖的程度。理想的内聚是功能内聚,也即一个模块只做一件事,只完成一个主要功能。

2.2.3 模块化设计:源码分析

    在实现模块化设计时,我们可以将多种数据封装成结构体、将实现功能封装成一个文件,可以帮助我们简化代码:

    代码中的软件工程-LMLPHP

    代码中的软件工程-LMLPHP

    代码中的软件工程-LMLPHP

    在上图中,我们将数据结构以及函数声明放在一个linklist.h的头文件中,其实现放在linklist.c文件,需要调用时再在main函数中去调用,这样就实现了较低的耦合度,也方便我们作为程序员去debug。

2.3 软件模块接口

2.3.1 接口的定义

    接口是通信双方共同遵守的一种规范,在软件系统内部一般的接口方式是通过一组API函数来约定软件模块之间的沟通方式。面向对象与面向过程的语言在对接口的是实现一般不相同。前者的接口是对象对外开放的一组属性和方法的集合;后者则是数据结构和操作这些数据结构的函数等。

2.3.2 接口的应用

    代码中的软件工程-LMLPHP

    上图借用了孟宁老师提供的工程源码文件中的某个源码文件。在这个文件中,可以看到其声明了许多接口,这些接口的实现会放在.c文件中。但不通过具体的实现代码,我们依然可以通过接口的名称、参数、返回值以及相关描述了解到大概的功能,这些接口的也可以被多次重复使用,提高了代码的复用性。

2.4 线程安全

2.4.1 线程的基本概念

    线程(thread)是操作系统能够进行运算调度的最小单位。它包含在进程之中,是进程中的实际运作单位。一个线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

2.4.2 可重入与线程安全

    线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行读写操作,一般都需要考虑线程同步,否则就可能影响线程安全。

    可重入(reentrant)函数可以被多个线程并发执行,而通常不会导致由于共享数据而导致结果错误;不可重入函数则在多个线程并发执行时,如果不能保持线程互斥,那么就会导致结果的错误。对于软件工程而言,我们应该以比较悲观的方式去评估函数:即可重入函数不一定是线程安全的,可能是线程安全的函数就不是线程安全的函数,不可重入函数一定不是线程安全的函数。

2.4.3 线程安全:代码分析

    首先,对于线程安全,我们所关注的焦点一般在以下几个方面:

      i. 所有的函数是不是都是可重入函数:分析函数有没有访问临界资源,若有则必须仔细分析其互斥的处理过程;

      ii. 不同的可重入函数有没有可能同时进入临界区:读写互斥应该如何去考虑;

    下面,同样地,通过孟宁老师所给出的代码,我们选择一段代码来加以解释:

    代码中的软件工程-LMLPHP

    在需要进入临界区时,我们对代码进行了加锁操作以确保其他函数无法使用临界区的资源,当我们退出临界区后及时解锁供以其他函数访问,最后销毁互斥锁,这就是一个典型的线程安全代码。

3. 总结

    孟宁老师于课堂上讲解的各个知识点,使我对软件工程中的开发思想、方法有了更深一步地理解。同时,经过这篇博客的书写,我对这些知识的理解更上一层楼了,更为重要的是,动手能力也有所提高,在此深表感谢。

    最后,再次强调,参考资料于此:软件工程——码农的自我修养

11-11 06:20