当前位置:网站首页>我们为什么需要软件工程——从一个简单的项目进行观察

我们为什么需要软件工程——从一个简单的项目进行观察

2020-11-07 20:15:00 dfqqq

 

一、编译和调试环境的准备

 

由于在刚下载vscode的时候就已经配置好环境了,这里大概描述一下流程,首先下载安装vscode中的C/C++插件,注意其中不包括编译和调试工具,只有一些代码提示的编辑功能。

 

 

之后安装windows版的GCC——MingW,并配置环境变量,最后将MingW的路径配置到vscode的launch.json文件中。这里我直接使用图形界面配置。

 

 

使用g++ --version查看是否安装成功。

 

 

由于menu这个项目已经有makefile文件来描述他的编译过程,我们直接在项目的路径中直接使用make指令来编译整个工程。

但是由于MingW自身的问题,我们需要将MingW/bin目录中的MingW-32-makefile.exe更名为make.exe,否则windows将无法识别make命令。

 

 

 

在编译完成后我们执行生成的test.exe文件,简单操作一下menu这个项目。

 

 

至此我们完成了环境的配置并简单运行了menu工程。

 

二、从软件工程的角度分析menu工程。

 

接下来我们逐个版本依次分析。

 

1、lab1:

任何软件一开始都只有很小的雏形,之后由开发人员不断在之前的基础上开发,最终达到在需求分析中定义的目标,

所以说几乎所有软件都是迭代出来的,在每一次迭代中增加新的功能或者修改其中的bug。这里的lab1就是整个工程刚开始的样子。

 

2、lab2:

在lab2中,该工程有了最初的功能,即识别help,quit以及其他未定义的指令。最关键的是,在整个文件的头部有了注释,说明了跟项目有关的一些信息。

 

 

3、lab3.1:

在lab3.1中,孟老师将命令进行了抽象,将命令定义成了一个结构体,把命令的名字、描述以及具体的动作组织在一起,同时将不同命令通过链表组织在了一起。这里体现了模块化的思想。

这其中需要注意的是:

  这里使用了函数指针,使得不同命令虽然操作不同但对外提供了相同的接口,这就让c语言虽然不具备面向对象的性质但依然完成了类似多态的思想。

同时,使用函数指针方便我们在后期拓展命令数组head[ ]的时候只需要添加自己的行为即可,不必去修改其他的代码。在一定程度上体现了软件工程的开闭原则,

即对扩展开放,对修改关闭。

 

 

这里还有一个小细节,在lab2中,对面命令的最大长度直接使用一个数字进行定义,而这里使用了宏定义,消灭了magic number,增强了代码的可读性同时便于修改。

下图为lab2中的定义方式:

          

一下为lab3.1中的定义方式:

 

 

 

 

4、lab3.2:

在lab3.2中,孟老师将在链表中查询以及展示所有命令这两个行为抽象成两个新的函数。目的是方便后期对于这两个行为的修改,

例如我们不使用链表存储命令,改用红黑树或者哈希表的时候,只需要修改FindCmd这个函数即可,而不需要修改main函数中的代码,实现了查询命令操作与整个流程控制的解耦。

还有一个好处就是我们可以在其他工程中复用这个函数,如果需要的话。这里体现了模块化的思想。

 

下图为FindCmd的定义和在main函数中的调用:

 

5、lab3.3:

在lab3.3中,孟老师将与链表的定义和相关的操作放在了单独的文件中,实现了链表操作与主程序控制过程的解耦。这里依然是模块化的思想,方便维护以及调试程序。

 

 6、lab4:

在这个版本中,孟老师引入了一个可重用的链表,将链表的各种操作这一可复用的部分抽象出来,方便在其他工程中继续使用。这部分是可重用接口的使用。

同时对链表的相关操作进行了简单的单元测试,确保该模块的正确性。这里值的注意的是,可复用链表的节点类型是tLinkTableNode*,而真实存储数据的节点类型是tDataNode*,

为了解决这个问题,孟老师在进行链表操作时将tDataNode*强制类型转换为tLinkTableNode*,使得将业务部分的数据对链表操作的屏蔽,同时也完成了业务对可重用接口的解耦。

下图是两个强转的例子:

 

 

 

7、lab5.1 & lab5.2

在这两个版本中,FindCmd函数加入了callback机制,即在函数传参时传入一个函数,然后可以在调用函数中使用这个函数。

使用callback的好处是,可以使得程序更加灵活增加可复用性,同时也能增强函数的功能。

 

 

在这个例子中,我们可以根据不同的搜索条件只修改SearchCondition函数中的代码即可达成目的,而不用修改SearchLinkTableNode函数。

如果这里没有使用callback,我们必须修改SearchLinkTableNode中的代码,一定程度上违背了开闭原则。

还有一个更能体现callback机制的好处的例子。在C++ Stl中内置了可以遍历Stl容器的函数for_each(_InIt _First, _InIt _Last, _Fn _Func)。

其中前两个参数是要遍历的起始迭代器和终止迭代器,第三个参数是在遍历时执行的动作,可以是一个函数。

因为在遍历的时候可以进行很多动作,例如输出容器,对容器求和等,如果不适用callback,Stl需要针对每一种遍历动作都进行相应的操作,

而在使用callback之后,Stl只需要编写遍历容器这一个动作即可,具体的动作可以通过回调机制由Stl的使用者自己定义并传入。

 

 

由于在lab5.1中进行命令比较的时候使用了全局变量,使得原本只用数据耦合的模块变为公共耦合。

于是在lab5.2版本中,将被用来比较的cmd参数化并将其类型置为void* ,在降低耦合程度的同时,完成了业务对可重用接口的屏蔽。

 

 

 

 

 

 

在5.x版本中还加入了makefile文件,方便进行整个工程的编译。

 

8、lab7.1 & lab7.2

在7.1中,孟老师对链表操作加入了锁机制,保证整个链表操作函数是可重入的,方便多线程软件对于他们的调用。

同时,还将之前的流程控制部分从主函数中抽象成ExecuteMenu函数,继续进行解耦。还将menu的定义和实现分离成.c和.h文件,将menu设计成独立的模块。

在7.2加入了readme.txt,对整个模块的功能和编译方式进行了说明。

 

三、总结:

 

通过这个例子,我对软件工程有了更深刻的认识。软件工程产生的背景就是软件的规模越来越大,变化越来越多,使得整个软件项目难以维护和管理。

而软件工程的出现就是为了解决这些难题,使得开发人员可以更从容的面对变化,提高软件模块的复用性。menu工程虽然代码不多,但在多个地方体现了抽象的思想,

这带给我们的启示是—— 在软件设计与实现时,更多考虑接口的抽象而不是具体的实现,并且考虑哪些部分是可能变化的,哪些部分是可以重复使用的。

 

版权声明
本文为[dfqqq]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/dfqqq/p/13942157.html