代码中的软件工程
开篇
软件工程,这门计算机类专业的朋友想必都了解过,但其精髓、其重要思想常常浮现在优秀的开源项目里,大家想要进阶,去接触反而有点晦涩难懂了,有点可远观不可亵玩也的味道。本人学习了孟老师的这几节课程,也做了下其项目,略有点体会,就让我们一起揭开她的面纱吧!(参考资料见孟宁老师码云:https://gitee.com/mengning997/se/blob/master/README.md#%E4%BB%A3%E7%A0%81%E4%B8%AD%E7%9A%84%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B)
环境搭建
这次项目主要以VS Code + GCC为工具集,查看一个经典的C/C++项目——MENU(类似LInux的命令效果)
首先在Vscode下下载C/C++扩展
然后配置gcc环境,这里主要参考了两篇博文(见文末),最后成功配置好
软件工程一般原理简介
模块化设计
这里主要有三种模块
- 程序的入口test
- 菜单逻辑menu
- 菜单使用到的数据结构linktable
那么重点来了,模块化的优势在哪?别急着回答,先思考一下,然后带着你的想法继续看下去。先回想一下,传统开发的痛点在哪。
首先,如上所述,传统的开发方式需要等待所有脚本资源加载完成。这个问题最大的弊端就是页面要等待,因为资源加载是同步的。你的页面会出现短暂的空白期,引入的脚本越多,时间越长,如果某一脚本加载失败,也可能直接挂掉。模块化的代码则可以很好的处理这个问题。除了模块化支持的脚本必须加载进来以外,其他脚本都可以异步请求,不需要页面等待,可以加速渲染出页面。requirejs,sea.js等也会做好加载重试和模块缓存的处理,确保所有模块运行良好。所有资源加载的时间不会因为模块化而加速,但是模块化能加速渲染,这是优势1。当然webpack是特例,它和nodejs一样用 commonjs 规范,为了达到目的,全部脚本打包到一起再运行,看着和上面观点相悖,不过现在带宽足够,相对而言还是足够快的,也能减少多脚本加载出错的风险。接着上面的观点讲,抛开带宽速度来讲,既然网速够快,那模块化还有什么?不妨回想一下,传统开发时最烦的是什么?无非3点命名空间。早期为了避免命名冲突,大众做法是用一个变量作为命名空间做隔离,长期开发过程中没人能记住这个变量是否冲突,它的命名规范是什么,治标不治本。而模块化的出现消除了这点。一个模块内的命名随自己起,和外界不会冲突,对外的永远是你exports出来的内容。如果模块内出现命名冲突,这说明了你的命名水平太低…..好吧,是模块颗粒不够小,还可以继续分割出模块~代码重用。其实这点和传统开发并无两样,都是把可复用代码抽取出function(再通用点会抽象出类,也就是构造函数),独立文件。但模块化的好处同样可以规避命名空间的问题,不必设置变量污染到全局。一般模块化都有缓存机制,在二次调用时无需再解析,直接获取到缓存模块内容。按传统开发来处理,忽略以上问题,但也耐不住文件太多,引入和管理麻烦。除了amd规范需要依赖前置,我们还可以用cmd规范来写模块依赖,想用什么require什么,不用再一个个引入js,看着也舒服。而且现在的模块化工具基本都实现了多规范混搭,想怎么写就怎么写,只要注意组内规范就行。
此外就是 管理问题。小公司或个人开发,模块化能让自己思路更为清晰,降低代码耦合,优秀的模块能带来代码质量质的飞跃,标准的模块应该是 “分工明细,职责单一,不牵扯需求逻辑” ,它就应该是个万能的螺丝,不需要可以修改,哪里需要用哪里。而中型企业和大团队则很经常会遇到团队协作开发,除了会用svn/git等工具管理,各种需求有不同的人负责处理。模块化对团队开发会起到协同作用,公共 模块除了避免重复造轮子的痛苦外,也避免了逻辑混淆。
可重用接口
在一个系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对人员来讲就不那么重要了;而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。因此接口的设计好坏与泛化能力直接可以体现一个项目的优秀程度。
如下代码块,MENU程序也设计了好的接口。
//linktable.h文件 //LinktableNode结构体只保留了最基本的结点指针,具体的data数据并没有包含,面向抽象不依赖具体 typedef struct LinkTableNode { struct LinkTableNode * pNext; }tLinkTableNode; /* * LinkTable Type */ //而inktable这个结构体则在引入头文件的情况下,嵌入了LinkTableNode结构体,并且另外定义了数据头//尾指针以及互斥量,相当于实现了遍历功能和线程保护 typedef struct LinkTable { tLinkTableNode *pHead; tLinkTableNode *pTail; int SumOfNode; pthread_mutex_t mutex; }tLinkTable; /* * Create a LinkTable */ tLinkTable * CreateLinkTable(); /* * Delete a LinkTable */ int DeleteLinkTable(tLinkTable *pLinkTable); /* * Add a LinkTableNode to LinkTable */ int AddLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode); /* * Delete a LinkTableNode from LinkTable */ int DelLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode); /* * get LinkTableHead */ tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable); /* * get next LinkTableNode */ tLinkTableNode * GetNextLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode);
接口的可重用性也可以拿go来举例,例如m:=map[string]interface{} 这样就创建了一个string的key类型可以映射到任意的数据类型的value,而这个空接口是所有数据类型皆实现的,这样便大大提高了接口的可重用性,方便开发设计。
线程安全
线程安全在多线程环境中是一个很重要的议题,读读并发不影响安全性,读写并发则特别影响整个系统的可靠性,MENU程序也注意了这方面的保护,以删除操作为例
//linktable.c文件 int DeleteLinkTable(tLinkTable *pLinkTable) { if(pLinkTable == NULL) { return FAILURE; } while(pLinkTable->pHead != NULL) { tLinkTableNode * p = pLinkTable->pHead; //互斥量加锁 pthread_mutex_lock(&(pLinkTable->mutex)); pLinkTable->pHead = pLinkTable->pHead->pNext; pLinkTable->SumOfNode -= 1 ; pthread_mutex_unlock(&(pLinkTable->mutex)); //数据更新完成后释放锁 free(p); } pLinkTable->pHead = NULL; pLinkTable->pTail = NULL; pLinkTable->SumOfNode = 0; pthread_mutex_destroy(&(pLinkTable->mutex)); free(pLinkTable); return SUCCESS; }
保护线程安全常常加锁来处理,例如java中的synchronized和lock关键词实现线程保护。
结尾
那么,文章到这便结束了,软件工程的常见设计思想便简单介绍完了,希望各位能运用到项目中去。
https://www.cnblogs.com/bpf-1024/p/11597000.html
https://blog.csdn.net/zhanglu_1024/article/details/108678165