简介:
首先感谢孟宁老师的教学指导。这篇文章主要基于孟宁老师上课的内容完成。
本文以VS Code 和GCC工具集MinGW为主要环境编译调试课程项目案例——命令行菜单小程序menu(https://github.com/mengning/menu),在阅读分析源代码后,结合代码分析其中的软件工程方法、规范或软件工程思想。
参考资料:
实验环境:
Windows10,VS Code,MinGW/GCC
目录:
一、完成C/C++编译和调试环境配置;
二、软件工程的艺术分析;
三、小结。
正文:
一、完成C/C++编译和调试环境配置:
1.VS code安装C/C++扩展插件
Visual Studio Code是一个轻量且强大的代码编辑器,支持Windows,OS X和Linux。内置JavaScript、TypeScript和Node.js支持,而且拥有丰富的插件生态系统,可通过安装插件来支持C++、C#、Python、PHP等其他语言。
由于案例中的命令行菜单小程序menu是用C语言开发的,所以我们先在VS code中安装C语言插件。如下图所示,在VS code左侧管理扩展图标,搜索C找到Microsoft C/C++扩展插件,点击Insatll安装即可。
2.下载编译器 Mingw-w64/GCC,配置环境变量
因为C/C++扩展插件中并不包含C++的编译器和调试器,需要我们自己下载。这里我们选择安装 GCC 的 Windows 版本 Mingw-w64/GCC 作为编译器。安装好MinGW并配置了环境变量之后,我们可以通过在cmd命令行运行gcc -v,如果看到版本号,说明安装成功。
3.在VS Code中配置编译路径。
接下来配置编译器路径,按快捷键Ctrl+Shift+P调出命令面板,输入C/C++,选择“Edit Configurations(UI)”进入配置。配置如下图所示,修改编译器路径。配置完成后,此时在侧边栏生成了.vscode文件夹,并且里面有一个c_cpp_properties.json的配置文件,记录了配置信息。
运行hello.c成功,可以看到左侧多了一个文件夹.vscode,里面包含launch.json和tasks.json两个文件,前者是vscode用于调试的配置文件,比如指定调试语言环境,指定调试类型等等;后者的作用是告诉vscode怎样编译程序,如配置编译器的命令(command),以及编译参数(args)等。
4.使用Makefile来构建工程
Makefile工程文件是在Unix类操作系统环境下非常常见,是用于工程项目组织的一种方式。很多IDE集成开发环境一般会自动生成一个类似的工程文件。Makefile使用起来非常灵活,可以像写Shell脚本一样手写,也可以使用autoconf和automake自动生成。
我们需要使用自动构建工具Makefile生成构建工程文件。在本次课程的项目中,老师已经将Makefile文件准备好了,我们只需要修改配置文件task.json和launch.json就能够编译工程了,修改后如下图所示:
5.到此环境配置完成,运行test.c成功!
二、软件工程的艺术分析;
1.模块化设计
介绍:
模块化设计,简单地说就是程序的编写不是开始就逐条录入计算机语句和指令,而是首先用主程序、子程序、子过程等框架把软件的主要结构和流程描述出来,并定义和调试好各个框架之间的输入、输出链接关系。逐步求精的结果是得到一系列以功能块为单位的算法描述。以功能块为单位进行程序设计,实现其求解算法的方法称为模块化。模块化的目的是为了降低程序复杂度,使程序设计、调试和维护等操作简单化。改变某个子功能只需相应改变相应模块即可。
软件设计中的模块化程度便成为了软件设计有多好的一个重要指标,一般我们使用耦合度(Coupling)和内聚度(Cohesion)来衡量软件模块化的程度。耦合度是指软件模块之间的依赖程度,一般可以分为紧密耦合(Tightly Coupled)、松散耦合(Loosely Coupled)和无耦合(Uncoupled)。一般在软件设计中我们追求松散耦合。内聚度是指一个软件模块内部各种元素之间互相依赖的紧密程度。理想的内聚是功能内聚,也就是一个软件模块只做一件事,只完成一个主要功能点或者一个软件特性(Feather)。
模块化设计在menu项目中的体现:
将数据结构和它的操作与菜单业务处理进行分离处理,尽管还是在同一个源代码文件中,但是已经在逻辑上做了切分,可以认为有了初步的模块化。进行了模块化设计之后我们往往将设计的模块与实现的源代码文件有个映射对应关系,因此我们需要将数据结构和它的操作独立放到单独的源代码文件中,这时就需要设计合适的接口,以便于模块之间互相调用。
menu项目的代码结构如下,其中linktable.h用来存放链表数据结构和它的操作,其他的业务逻辑层代码都在menu.c中,在menu.c中只需要导入 linktable.h 头文件,就可以用其中的数据结构快速构建具体的业务逻辑,而不需要从头定义底层代码。将数据结构和对数据结构的操作与业务逻辑分离开,这里就体现了模块化的思想。
2.可重用接口
介绍:
上面已经做了初步的模块化设计,但是分离出来的数据结构和它的操作还有很多菜单业务上的痕迹,我们要求这一个软件模块只做一件事,也就是功能内聚,那就要让它做好链表数据结构和对链表的操作,不应该涉及菜单业务功能上的东西;同样我们希望这一个软件模块与其他软件模块之间松散耦合,就需要定义简洁、清晰、明确的接口。这时进一步优化这个初步的模块化代码就需要设计合适的接口。
接口就是互相联系的双方共同遵守的一种协议规范,在我们软件系统内部一般的接口方式是通过定义一组API函数来约定软件模块之间的沟通方式。换句话说,接口具体定义了软件模块对系统的其他部分提供了怎样的服务,以及系统的其他部分如何访问所提供的服务。
在面向过程的编程中,接口一般定义了数据结构及操作这些数据结构的函数;而在面向对象的编程中,接口是对象对外开放(public)的一组属性和方法的集合。函数或方法具体包括名称、参数和返回值等。
接口规格是软件系统的开发者正确使用一个软件模块需要知道的所有信息,那么这个软件模块的接口规格定义就必须清晰明确地说明正确使用本软件模块的信息。
可重用接口在menu项目中的体现:
在menu项目中,我们可以通过Linktable模块的接口来展示接口的可重用性。在linktable.h中,SearchLinkTableNode函数的Condition参数是一个函数指针,利用callback函数参数为其他模块提供了一个更加通用的搜索链表的接口。
接下来我们在linktable.c中看下SearchLinkTableNode函数的具体实现,可以看到,这个函数没有用到任何业务逻辑层的数据,只是负责遍历链表,具体判断是否找到指定条件的节点交由Condition回调函数来负责。所以当其他模块中想按一定条件遍历链表时,只需要构建特定的Condition函数就能方便地来调用这个搜索函数,而无需对遍历链表的操作重复造轮子。接口使用callback方式,参数中使用condition函数和args参数使得接口变得更加通用,不需要再访问全局变量cmd,便可实现操作。
在menu.c中,我们可以看到这个可重用接口的具体使用。menu.c中先定义了一个SearchCondition函数,用来定义查找链表的条件,然后FindCmd函数调用了SearchLinkTableNode函数接口,而且将SearchCondition函数作为参数传入这个接口。
3.线程安全
介绍:
线程(thread)是操作系统能够进行运算调度的最小单位。它包含在进程之中,是进程中的实际运作单位。一个线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。一般默认一个进程中只包含一个线程。
可重入(reentrant)函数可以由多于一个任务并发使用,而不必担心数据错误。相反,不可重入(non-reentrant)函数不能由超过一个任务所共享,除非能确保函数的互斥(或者使用信号量,或者在代码的关键部分禁用中断)。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。可重入函数要么使用局部变量,要么在使用全局变量时保护自己的数据。
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
可重入的函数不一定是线程安全的,可能是线程安全的也可能不是线程安全的;可重入的函数在多个线程中并发使用时是线程安全的,但不同的可重入函数(共享全局变量及静态变量)在多个线程中并发使用时会有线程安全问题;不可重入的函数一定不是线程安全的。
线程安全在menu项目中的体现:
在menu项目代码中,为了避免多个线程操作同一个链表导致线程安全问题,在一些涉及线程安全的链表操作代码中加入了锁机制,如linktable.c文件对锁的操作,其中pthread_mutex_lock(&(pLinkTable->mutex))用于上锁,pthread_mutex_unlock(&(pLinkTable->mutex));释放锁,而pthread_mutex_destroy(&(pLinkTable->mutex));用于删除锁。在对链表中的元素进行操作时,现对链表加上互斥锁,这个时候其他线程就无法获取链表对象,无法对链表进行操作。当拥有锁的线程完成相应任务后对锁进行解锁操作,这个时候其他线程便可以访问并占有锁资源。当整个系统不在有人对链表对象有需求的时候就需要将锁资源destroy释放。这样做就保证了多线程环境下对链表操作的线程安全。如下图所示:
三、小结
软件工程是门艺术,对模块化设计、可重用接口、线程安全等议题的理解和运用能提高编程的艺术造诣。通过本次课程实践的学习,我对软件编程的成长轨迹、规范风格、技巧方法有了深刻的认识。
原文:https://www.cnblogs.com/xsero/p/13945605.html