论软件⼯程
论软件⼯程
昨天⼀位同学问我对软件⼯程的体会?当时愣住了⼀下,不知道该怎么回答,感觉话到嘴边却不知道怎么表达,因为这个话题⽐较沉重,不知道该怎么⽤简短的语⾔来进⾏描述,要说可能⼀天都说不完,可能是⾃⼰的表达能⼒有限,语⽂没学好(⾼考语⽂不到80分),当时只是给出了:“这叫我怎么回答”这样的答案。随后在我的脑海⾥⼀直回忆⾃⼰开发经历,想从中到⼀些信息。第⼀次听到软件⼯程是⼤三的时候有⼀门课程叫做软件⼯程,我依稀记得⾥⾯⼏个关键词语:髙聚类.低耦合、可扩展性、可⾏性、可维护性,同时还包括⼀些软件开发过程。下⾯将结合这⼏点以及我的经历来谈谈我眼中的软件⼯程,同时也对⾃⼰进⾏⼀次总结。
髙聚类.低耦合
这个词是⾃从我上过软件⼯程的课之后⼀直没忘记过,并且⼀直影响这我着设计开发过程。什么叫髙聚类?⼀句话就可以概要:物以类聚,⼈以分。什么意思?就是将相同属性的事物抽离出来,从⽽简化对这⼀类事物的控制,以⽅便管理。那什么叫低耦合?上⾯说了将相同属性的事物抽离出来以达到分组,那么低耦合就是降低各个分组之间的关系,这样可以达到结构清晰,管理灵活。上⾯都是解释字⾯上的意思,完全没有扯到⼀点关于软件⽅⾯的东西,那么这两个词放在软件设计⾥⾯是怎么体现的呢?
这个可以分为宏观和微观,宏观上⾯,⼀句话:模块化,层次化。模块化和层次化是髙聚类和低耦合在软件设计宏观上⾯的体现,具体的⼜可分为纵向业务划分,和横向架构划分(这也称为⼆维架构,之前还听过蔡学镛⽼师说的这个就更加的抽象了)。将相同业务抽离到⼀个模块⾥⾯,将整个业务线切分成多个层次,上层只能依赖下层,下层不能依赖上层的图结构。从⽽使得整个软件在结构上层次分明,业务线清晰。这⾥举个例⼦:⽐如我现在要做⼀个电商⽹站,⾸先要做的第⼀件事情不是探讨技术上⾯是否有难点,⽽是探讨整个⽹站业务⽅向,从⽽确定整个业务模型,对⾥⾯进⾏建模,分析⾥⾯包含的哪些领域模型。先不谈复杂的电商,就说⼀个初期的C2C⽹站,⾸先它的业务⽅向是⼀个个⼈买家交易平台,⼀个⽤户即可以扮演买家也可以扮演卖家,确定这个之后,我们再对⾥⾯进⾏建模,从⽽可以进⾏模块划分。上⾯的⽹站可以简单划分为商品模块,订单模块,⽤户模块等等,那么这个模块就是在软件架构⾥⾯的髙聚类,那么低耦合怎么体现呢?模块划分之后需要确定模块在整个系统⾥⾯的定位,从⽽可以确定它们之间的依赖关系,以达到确定它们所处的层级。下⾯通过⼀个图来描述上⾯三个模块的层次关系:
⽤户模块依赖订单和商品模块,订单模块依赖商品模块,那么可以确定的是商品模块是在整个系统正处于底层模块,⽽⽤户属于上层模块,那么订单模块不能反向去依赖⽤户模块,商品模块也不能反向依赖订单和⽤户模块,这就是在架构设计上的分层,这样做的好处就是系统的结构清晰,⽽且对功能的分层降级可以简化业务的复杂度,并且可以达到对局部的优化不会对其他模块的影响,只要当前对
外暴露的接⼝定义不变。那么这样分层之后,各个模块之间通过什么来关联呢?通过底层模块向上次模块之间暴露它内部的接⼝,⽐如商品暴露查询商品的接⼝,那么⽤户就可以查询⽤户所上架的商品,订单包含哪些商品,以及订单暴露它的接⼝给外部,那么⽤户就可以查询某个⽤户所拥有的订单。⾄于模块之间通过什么⽅式通信?如果是项⽬前期这些模块都放在⼀个⼯程⾥⾯,这个就可以直接引⽤各个模块的接⼝实现,如果项⽬发展到最后,需要将各个模块独⽴出来,单独部署,那么就需要⼀个远程通信的⽅案,这样⾯有很多成熟的⽅案,⽐如⽐较通⽤的Webservice,性能⽐较⾼的RPC(这⼀块有很多⼯具,⽐如最近听到的跨语⾔的,⽐如阿⾥系的dubbo和HSF,facebook的thrift),这就是软件⼯程⾥⾯的低耦合。
上⾯说的是软件设计宏观上的髙聚类.低耦合,那么细化到具体代码实现上⾯来说,这个就是在实现某个逻辑的时候需要带着这个思维去看待你的代码,这就是微观的髙聚类.低耦合。在开发过程中我们⼀直提倡提⾼代码的重⽤性,这就是髙聚类,同时也要保持代码的结构清晰,这就是低耦合。我在写代码过程中有⼀个洁癖,就是如果写⼀段业务逻辑,类似的逻辑出现了3次以上,那么我就和得了强迫症⼀样,把这⼀块抽离出来,通过对外暴露变量,以达到具体业务的需求。在Java⾥⾯提供接⼝和抽象的功能,⽽合适使⽤抽象也是⼀种髙聚类的体现,我们⼀般把⼀些公⽤的,模板的业务逻辑放在抽象类⾥⾯,这样就避免了其⼦类的复杂度,⼦类只需要关注它⾃⼰需要体现的业务,⽽⼀些通⽤的抽象⽗类提供了实现,这是⼀种在代码的重⽤度⽅⾯的髙聚类。在具体细节中其他地⽅也体现了髙聚类特
性,⽐如我们总喜欢把对某⼀类操作放在⼀个类⾥⾯或者⼀个包⾥⾯,⽐如对⽂件的操作,我们可以放在⼀个FileUtil,这样做是为了⽅便管理整个项⽬对⽂件的操作,⽽不是东⼀个,西⼀个,如果某⼀天业务要求我们对操作⽂件的时候加上某些东西,这个时候如果所有的操作都放在FileUtil⾥⾯,⾯对这样的变更,我们是如此的得⼼应⼿,否则那你去⼀个⼀个的⽂件⾥⾯对⽂件的操作吧!这也是⼀种⾼聚类的体现。那么低耦合怎么体现的呢?⽐如我们写代码过程中经常采取MVC模型,将数据展⽰层,业务层,以及数据存储层分离开来,这就是⼀种低耦合的体现,各个层次分⼯明确,⽐如我优化数据存储层,那么上层的业务层基本⽆感知,这种现象就表明你的耦合度很低,⽽不是⼀切东西都杂糅在⼀起。
可扩展性
如果软件开发设计⼀直遵循上⾯⾼聚类.低耦合,那么⼀个软件的可扩展性理论上是没有什么问题的。这⾥的可扩展性是只在软件⽣命周期⾥⾯能够适应和满⾜业务的发展需求,在原有的架构设计上将新的业务可以实现平滑的嵌⼊,或者可以实现插件式将⼀个业务直接嵌⼊当前架构⾥⾯,如果⼀个系统真正实现了插件化,那么它的扩展性应该是相当好的。还是拿上⾯C2C的电商⽹站来举例,之前只是满⾜⼀个简单的C2C的交易平台,可能随着业务的发展,为了能够吸引更多的客户,需要推出抢购业务。如何更好的将这个抢购业务嵌⼊到之前的C2C系统⾥⾯,这就要看之前系统的架构师的设计是否合理,如果为了满⾜这个抢购业务,把整个项⽬都进⾏了重构,那么之间的架构是很不合理的,如果
能够对之前架构进⾏很⼩的调整就能把这个抢购业务上线就说明之前的架构设计是很合理。当然这个过程除了分析抢购业务流程,也要分析当前系统的架构,通过业务的优化和架构关键因素的分析以达到最佳的实现⽅式。针对上⾯画出模块依赖关系,可以⼤致分为两个途径去实现这个抢购业务。
第⼀、如果当前⽹站业务量很⼤,如果上了抢购,能够刺激⽤户参与度,可能需要单独建⽴⼀个抢购模块,这样就可以当抢购很⽕热或者可能抢购业务因为流量太⼤,导致它的业务整体瘫痪了,那么普通正常的业务依然能够运⾏,因为你已经将整个抢购业务独⽴出去了,它只是会通过调⽤⽤户模块,商品模块和订单模块接⼝来执⾏相关的业务逻辑,同时也可以单独对这个模块进⾏优化,⽐如分析抢购模块是⼀个持续时间很短,数据⽐较集中,对某个数据的修改⽐较集中的操作,可以将数据的整体操作放在缓存(这是⼤部分电商对抢购业务常⽤的⽅式),那么这样就可以降低对数据库I/O的压⼒,并且提⾼抢购模块的系统吞吐量。这种实现⽅式可以降低系统的耦合度,也使得抢购业务以及整个系统的扩展性上⾯有所提⾼,但是对系统维护来说增加了⼀项⼯作以及对开发⼯作量来说有所增加了,因为⼀个新的模块创建毕竟也是重新开始的。
第⼆、如果当前⽹站流量还不是很⼤,只是想通过抢购来获取更多的⽤户,那么可以将抢购业务嵌⼊到原有的架构模块⾥⾯,⽽⽆需通过新建模块来达到⽬的。以我以前参与过的项⽬来说说这种⽅式的实现⽅案,由于我之前负责的⼀个电商⽹站流量不是很⼤,为了提供⽤户的参与度以及获取更多的⽤户,需要上线抢购业务,但是⼜不想能够快速上线,所以在商品模块对商品添加了⼀个抢购属性,并
且设定该商品的抢购时间区间,那么这样就可以简单的将抢购业务简单的嵌⼊到整个系统⾥⾯。当然这个前提是你的业务量不是很⼤,不然上线⼀个抢购业务可能会将你整个系统弄得瘫痪,那个时候你再将抢购独⽴出来,那就是⽆数⽇夜的加班熬夜了。
可⾏性
这个是体现上⾯所有⼯作做的是否有价值,如果你的架构很好,你的架构完全具备了髙聚类.低耦合,也具备了可扩展性,但是完全是异想天开,不具备可⾏性,这只能说这是⼀种YY的架构。我认为的可⾏性可以分为:在满⾜上⾯髙聚类.低耦合,可扩展性的条件下,能够让业务在这个架构上跑起来,以及整个系统是⼀种⾼效率的执⾏,整个系统在部署上可以轻松实现⽔平扩展以⽀撑后续的业务发展。那么将结合这三点来说说可⾏性。
业务跑起来
这个应该是⼀个系统设计的最基本的要求,因为这是设计的初衷。所有的架构设计应该是不能脱离业务去异想天开,合适围绕业务,站在业务的⾓度去设计当前系统,当然这⾥需要设计者对业务有深⼊的理解,⽽且和业务⽅的理解达成⼀致,这样设计的软件最后才能通过UAT。这就好⽐写作⽂,要紧贴标题,不然和跑⽕车⼀样,跑题跑到⼗万⼋千⾥,这样的作⽂,你把李⽩,杜甫哥俩搬上来也是⽩搭。
⾼效
这个也可以通过宏观和微观去看待,宏观⽅⾯各个模块之间的通信协议,⽐如webservice虽然可以跨语⾔,但是它的性能确实不敢恭维,或许RPC更⾼效点。同时也可以通过梳理简化业务流程提⾼整个系统的执⾏效率,这就好⽐寻求⼀条最短路径⼀样,减少业务的流程对提⾼系统执⾏效率⽐任何⽅法都见效,因为它优化的不是⼀次通信采取什么协议,某段实现采⽤什么算法,⽽是对整个业务线进⾏抽象。那么微观的优化就涉及到代码的具体实现了,⽐如⼀段业务逻辑的实现,避免没必要的循环,采⽤更⾼效的算法,减少⼀个变量的定义,减少内存的使⽤等等这些都是在具体细节⽅⾯的让系统更加的⾼效执⾏。
部署的⽔平扩展
要说这个就要说到分布式集部署了,⼀谈到这个或许真的谈半天都说不完。围绕这个,在软件开发过程的每个细节都有很多内容。这⾥我只是简单的描述⼀下,⼀个系统要达到可集部署的要求,⼀定要达到系统的⽆状态性,什么叫⽆状态?就是不能依托单个服务实体状态来⽀撑业务的执⾏,⽐如通过将⼀⽤户信息缓存到服务器内存,⽐如你将任务放在本地的内存中,这些地⽅都是集部署的障碍点。
举⼀个简单的例⼦:在标准的J2EE⾥⾯或者是标准的web开发,每次⽤户的访问都会有Session的概
念,如果你的Session是依托单个服务实体,那么该系统要进⾏集部署,这个⽤户信息同步就是⼀个问题,因为你进⾏集部署,⽤户的请求具体是落到哪个服务器上,完全是有前端的负载均衡器(nginx,apache或者F5)决定的,如果⽤户登录是在A服务器上执⾏的,你将⽤户登录态存储在A服务器的session ⾥⾯,那么下次⽤户的请求可能落到B服务器上,由于你将session放在了A服务器上,⽽B服务器没有⽤户登录信息,这个时候⽤户可能就不能完成⼀次请求,这对⽤户来说不能理解,因为他请求的就是你们系统,他并不知道你具体是把他的请求落在你们哪个服务器上。针对这个问题,业界有很多解决办法,有依托容器的session同步,有通过第三⽅缓存服务实现session共享或者CAS的单点登录⽅案。个⼈⽐较偏向通过第三⽅缓存服务实现session共享⽅案,因为通过容器来实现session同步,那么就对系统跨平台增加了⼀种限制条件,只能在具体的容器下⾯才能运⾏。这⾥就简单描述⼀下session共享怎么来做到的,session共享是将⽤户的登录信息不存储在具体的服务器上⾯,⽽是存储在所有服务器都能访问的第三⽅缓存⾥⾯(redis或者memcache,当然数据库也可以只是性能稍微不能接受),这样依赖所有的服务器均从第三⽅缓存⾥⾯获取⽤户的登录信息,这样⽤户的登录请求不管是落到哪台服务器上,所有的服务器都能感知到,这样不管你后续的请求落在哪台服务器上,服务器都能获取你的登录态。⽤户登录态是系统集部署⾸先需要解决的问题。
上⾯从三点描述了软件的可⾏性,但是⼀个系统,可以说⼀个⼤型系统可⾏性远远不知从这三点来分析和优化。针对不同地⽅有不同的优化⼿段:
1. 针对提⾼⽤户访问速度,可以采⽤CDN和反向代理,将我们的数据放在离⽤户更近的地⽅
2. 针对页⾯渲染速度,可以通过对JS,CSS以及HTML页⾯压缩或者提⾼页⾯加载的并发数来进⾏优化
3. 针对提⾼系统的吞吐量,可以在部分环节采取异步的MQ⽅式进⾏业务的执⾏
4. 针对降低数据库的压⼒,可以通过两级缓存(本地缓存和分布式缓存)的⽅式来优化整个数据的查询
5. 针对提⾼系统的处理能⼒可以进⾏并发处理,从⽽提⾼系统的执⾏效率
6. 针对提⾼每台服务器的使⽤率,可以使⽤负载均衡,使得服务器资源利⽤充分
7. 针对提⾼系统的容灾能⼒,系统需要⼀定的冗余,⼀套系统需要具备⼀个后备环境,这⾥要说的东西也很多,举个简单的场景,当切
到后备环境,应该先进⾏缓存预热,否则可能切到新的环境,由于缓存没有数据,导致缓存的命中率很低,导致巨⼤压⼒全部落到了数据库,会导致整个数据库瞬间爆棚。
8. 针对提⾼数据库的性能,需要进⾏数据库的分布数部署,⼀主⼀从或者⼀主多从甚⾄多主多从进⾏数据库端的优化
上⾯聊了关于系统可⾏性⽅⾯的讨论,上⾯只是将我知道的⽅案进⾏了⼀次总结,但不⼀定全部都实践过。要知道能够将上⾯都实践过的只要⼤型公司才具备这样的机会(⽐如像BAT这样的公司才会有很多这样的机会,但不是所有⼈都能参与进去),所以只是有感⽽发。
可维护性
上⾯探讨的⼤部分是软件架构和开发设计过程的问题,⽽⼀个系统的发展不能是⼀个⼈或者⼀个团队⼀直去开发和维护,那么要使得⼀个系统能够在不同⼈的⼿⾥运作的更好,这需要从系统初期就规定开发规范,开发流程以及开发⽂档的整理和搜集。使得整个软件在后续的交接过程中能够平缓的过渡,⽽不需要过多的“进⼊状态”的过渡期。⼀个系统的可维护性是⼩到⼀个普通开发,再到开发主管,⼤到⾸席架构需要在⼯作过程中去关注的事情,只有⼀直关注这件事情,整个系统的质量才能经得住时间的考验。这就是我理解的可维护性。
软件开发流程
其实这⾥最能体现软件⼯程的思想,上⾯⼤部分都是软件设计架构⽅⾯。⼀个软件开发过程⼀般遵循⼀下⼏个流程:
1. 产品确定业务需求产出PRD⽂件
2. 产品,开发,测试以及交互进⾏需求评审
3. 通过之后那么开发,测试以及交互同时进⾏产出设计⽂档,测试⽤例以及UX
开发网站需要什么软件4. 接下来是对上⾯三个进⾏评审设计评审,⽤例评审,UX交互评审
5. 进⾏完之后那么接下来就是开发阶段
6. 开发完毕体测,进⼊系统冒烟测试
7. 系统功能测试
8. 系统回归测试
9. 系统UAT测试
10. 系统部署上线
有些公司在第10步分为两部分,分为预发布和线上,这是为了提⾼系统上线的整体质量,预发布环境
的条件是数据以及环境和线上⼀样,只是系统是⽤最新分⽀的代码运⾏,部署预发布之后将会进⾏预发布验证,如果验证不通过则打回给开发修复,再次进⼊第7。
有些⼈认为软件⼯程其实就是这些流程化的东西,如果这么认为那理解就有点太过于局限于⼯程这个概念了。其实软件⼯程的⽬的就是使得软件开发的过程以及系统后续迭代开发能够像当前硬件发展⼀样,通过提供统⼀的接⼝,只需要将⾃⼰喜欢的配置组合在⼀起就能得到满⾜⾃⼰需求的硬件设备。那么要使得软件开发成为这样的模式,必然会涉及到开发每个细节,以及开发的整体过程当中,不仅仅是流程化的东西,⽽是系统的架构模式,代码的实现风格,以及系统上线部署维护⽅⾯,所以软件⼯程是⼀个⽐较⼤的话题,可能随着⼯作经验的累计有不同的理解,或者不同的岗位有不同的理解。可能今天我理解的软件⼯程是这样的,过了⼀到两年,可能会认为我现在的认识是错误的。我现在对软件⼯程的理解是开发过程所设计到的⼀切事物都是软件⼯程的⼀部分,开发过程中⼀切产出都是软件⼯程的⽬的。
最后我举个例⼦,也是前⼏天以为⽹友向我咨询过的问题,他现在有⼀个系统需要改造,他系统主要做的事情就是接受第三⽅通知,然后再将结果通知给另⼀个第三⽅系统。下⾯划出简单的⼀个系统拓扑图:
整个通信过程是第三⽅系统A将处理结果告知转发系统,然后转发系统将结果告知第三⽅系统B当作任
务放⼊本地⼀个队列中,然后有⼀个线程专门处理这个队列的任务将结果告知第三⽅系统B。这种架构存在什么缺陷呢?我下⾯列举⼀下:
1. 存在单点过障,如果流量增加单台转发系统可能根本处理不过来整个业务
2. 存在集部署的障碍点,转发系统是将任务放在本地的内存⾥⾯,如果集部署分担压⼒,那么在本机的任务其他服务器根本⽆法感
知导致⽆法分担压⼒
3. 没有很好的模块划分,将接受系统A的和转发系统B的代码全部杂糅在⼀起,导致后⾯对系统进⾏扩展很难
4. 存在数据丢失场景,如果系统上线新版本,服务重新部署,那么缓存在内存⾥⾯的任务如果挤压很多⼀时处理不完,那么重启必然导
致内存⾥⾯的任务数据丢失,导致不能再通知第三⽅系统B
针对上⾯⼏个问题我提出了下⾯的解决⽅案,先看看改进后的拓扑图:
将原来系统拆分成两个模块,⼀个是接受处理结果模块和通知处理结果模块,然后这两个模块之间通过异步的MQ来进⾏通信,现在的执⾏流程是:第三⽅系统A将处理结果发送给转发系统的前置Nginx,Nginx将请求通过负载均衡到后端具体服务器上,某台服务器即受到处理请求之后,将结果受理并执⾏相关业务,然后将通知任务放⼊MQ的消息队列中,这个时候可以⽴马响应结果给第三⽅系统A,这个时候第三⽅系统A通知处理结果就处理完毕,最后再就是通知处理结果模块由于监听了MQ的消息队列,那么如果有任务消息过来,将会接收到,此时将结果发送给第三⽅系统B。这个时候接受处理结构模块的吞吐量增加了,处理能⼒也增加了,因为将任务不再是缓存在本地,⽽是放
⼊MQ中,那么它是⽆状态的,可以随意⽔平扩展,从⽽可以应对⾼并发的业务处理。⽽通知处理结果模块由于是监听MQ中的消息队列,业务压⼒上来并不会给它带来压⼒,它依然按照它的节奏在处理任务,如果想让处理速度快⼀点,可以在集中多部署⼏台机器来解决,这样使得整个系统的⽔平扩展⽅⾯得到了很⼤的提⾼,也使得整体系统的处理效率以及系统结构得到⼀定的优化。
可能有⼈会说,我这样把⼀个简单的系统拆分成这么多模块,把整个项⽬弄得复杂了,需要维护和管理这么多模块,同时还加了⼀个MQ的维护。当然这么拆分的前提条件是你的业务量上来了,或者为以后做准备,如果你现在的系统⼀天才处理不到⼀万个请求,我也建议没必要这么搞。但是如果作为⼀个可以持续发展的项⽬,前期很好个规划可以很好的避免后⾯的⼤规模重构。上⾯这种⽅案对于实时性要求很⾼的系统也不是很适合,因为MQ是异步的,所以存在⼀定的延迟,所以对于允许有⼀定延迟
的业务可以采⽤MQ来提⾼整条业务的响应速度,以达到业务处理的吞吐量,当然这⾥所指的延迟并不是延迟⼏个⼩时,这种延迟⼀般不会超过⼀分钟。
在我们现实⽣活中这这⾥模式也很多,⽐如我们刷银⾏卡,然后我们⼿机⽴马会收到银⾏⼀个短信通知我们有⼀笔消费,⼀般从你刷卡到你收到短信不会过太久就会收到短信,这个过程总就涉及到异步处理。它这⾥的流程⼀般是这样的:⼀般你刷卡处理的后台系统是银联(当今中国线下清结算⽼⼤,线上你们都应该知道),银联通知对应银⾏对某张卡进⾏扣款(⾥⾯具体的操作我也不太清楚,⼀般做法是银联通知对应银⾏将钱转到银联的⼀个账号上,然后商户和银联进⾏结算),银⾏将扣款结果通知给银联,这个时候银⾏也会将⼀条短信存储在⼀个类似消息中⼼中,并有专门的系统处理这些消息去通知⽤户,银联在返回给POS机,最后POS机出票,这个时候⼿机也会收到⼀条银⾏短信。在这个过程中银⾏⽣成短信并不是⽴马去发送,⽽是放⼊消息中⼼(这个类似于⼀个MQ),让其他系统异步去做,为什么要这么做呢?因为并不是业务主线内容,如果扣款和放在⼀起同步去做,那么会导致整个业务响应很慢,也可能由于短信发送失败导致整个业务失败,因为谁能保证百分之百的成功,⽽且谁能保证百分之百的快速响应?所以将这种附加在主业务分⽀上的业务通过异步执⾏来提⾼整条业务的成功率和处理效率,这也是企业级系统开发常⽤的⼿段。
以上是我所认识的软件⼯程,也不知道最后表达了什么思想,只是总结了⼀下到如今我对软件开发的⼀个认识。以上也是我主观的认识,并不具备参考意义,还望不要误导⼤家。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。