
防止代码变质的思考与方法.docx
6页1、 软件长期运营存在什么问题一个大规模的客户端软件的生命周期中,我们可以把它分为两个比较粗的时期一个是前期的搭建软件的时期,即从无到有的时期;第二个是搭建完成ZJh,进入的一个稳 定的运营时期第二个时期才是最关键的,在这个时期我们会持续的迭加需求,持续的优化功能,而且第二个时期也是代码在慢慢变质的时期在这个时期,你可能会发现:我们的软件慢慢出现模块耦合严重,牵一发而动全身;毎个版木都会涌现出老功能的BUG,你没动过的模块也会出BUG;或者改了一个小问 题了,带出来很多其他问题;缺乏扩展性,往老模块加新功能非常痛苫;程序的崩溃率越来越高;新员T接手老模块经常不能理解原来的设计思想而改坏;移植一个DLL到另一 个软件时,发现必须连带也移植十几个DLL木文将分享对于这些问题的思考与方法2. 软件的积木模型一个运营型的客户端软件,做出来就是为了长期运营,需要不断的迭加功能而不是做出来,两三年就重吗一次那么这样一个软件就像堆积木一样一个软件刚开始每了 两T行代码,感觉设计得非常好,模块化扩展性都非常好,性能也非常快,都能很好的面向运营耳了两三年ZJT;,就会出现像这种积木一样的结构,很容易崩塌。
所谓 , 形彖的说,可以看做是某个积木不稳定,要往里塞一塞那么整个开发过稈,就是一个不断迭代、不断优化、不断重构的过程对于我们这个积木模形,有什么办法不让一些木 条跑出來,这也是我们需要想的思路我们是不是可以先围四面墙,然后在墙里面再去塔积木?3. 导致代码变质的两大团队中总是会存在这样那样的问题,这些问题最终总是影响到我们的代码朝着不良的方向发展对于这些因索,我可以将它们抽象为两人类一类是人的因索:比如架构设 计不合理,需求没考虑清楚,项目进度斥力,沟通问题,缺少文档、培训,等等另一类是时间的因素:比如人员的变动,需求的长期迭加和变更,等等人的因素是由于人木7的索质或疏忽导致,时间因索是由于时间的长期推进导致,即使人的索质很高也必然会出现时间因索的问题4. 代码变乱的微观原因在上述两人类因索的长期作用下,最终会导致代码越来越乱如果从微观的角度来剖析,这跟依赖有着很人的关系代码的变乱,根木原因就是由于太多不良依赖或者模块 失去单一性所致我们来看一下依赖是如何产生的1).依赖的方式如下图所示,如果纟R件A依赖于B, B依赖于C, A也是隐含的依赖于C的组件A不能单独使用,必须同B和C 一起使用。
在现实的代码中,可能存在着非常长的依赖 链依赖的方式也可能是多种多样的,单向依赖、双向依赖、环状依赖或者一个依赖于多个下图也是一些示例,现实的代码中可能是由各种依赖方式组成的非常复杂的网状结 构2).依赖的变化在两大类因索的作用下,依赖会发生变化最常见的变化应该是依赖的箭头越来越多,网状结构变得越来越复杂如果没有增加新的组件,下图中左边的图往往会变成右边 的图起初设计好的很好的代码,可能是左边的样了,模块具冇很好的独立性和可移植性随着时间、需求、人的变化,很可能由开发人员很随总的…行代码,就变成了右边的 图,一条红线就出来了两个模块变成相互依赖,上面那个模块就不再有独立性和可移值性我们的代码从设计Z初到现在,小间经过了几年的时间,代码变得越来越乱很大的原因是因为这种红线的持续出现本来有很多独立性很好的模块,变成了错综复杂的网状 结构前更是没有引入新组件的情况,如果引入了新组件,必然会引入新的引赖,那么就要好好的去界定,引入的新组件是属于哪个丿zM的像下面第…个图,新引入的组件依赖 于原来两个组件是在最上层,第二个图新引入的组件是在中间层,第三个图新引入的组件被另外两个组件依赖在最底层。
引入新组件,英实应该做好充份的考虑,而不是让开发人员随意的引入需要充份思考引入的新组件应该放在哪一层面才是最合理的,才有利于以品的扩展和移植可能读者会遇到这种情况,一个功能编译没有问题,测试也没有问题,发布麻一两年也没有问题当我们要把这个功能移植出来的时候,才发现问题人了你想移植一个组 件到另一个软件时,必须连带也移植十几个组件5. 如何解决依赖1).组件网图要解决依赖,首先要发现哪些是不止确的依赖下图就是一个具有良好层次的依赖关系图,我们称Z为“组件网图蔦对于我们现实的软件中,我们非常需求这样一张图将報 个软件所有组件的依赖关系绘制出来,以便于我们发现其中的错误依赖进行解决g如果组件网图中存在错误的依赖关系,或者如果冇需求要求图中的组件h依赖于g,丿2该怎么办?可以通过下面的“分解适配”和“升级降级”的方法进行解决2) .分解适配(单一职责)分解适配是指将一个功能复杂的模块分解为多个具冇单一职责的模块,那么模块间的依赖关系也会变得单纯读者可以结合下面的案例理解这个方法3) .升级降级我们经常会做重构,对于上面那张纽件网图来说,重构就是将不合理的依赖断开,把更通川的逻辑抽出来放在底层,将不能用的逻辑放在上层。
重构H实就是不断升级和降 级的过程比如说我们前面的图,如果H依赖于G 了,那么可能考虑将G进行分解适配,将G分为G1和G2,将G2和H介并为一个新纟H•件这样就完成了一个分解适配和升 级降级的过程gi6.处理依赖的方法论1) .通用的模块不要依赖于不通用的模块我们进行以次划分,通常是通用的模块放到底以,不通用的模块放在上丿乩 不通用的模块依赖通用的模块是合情合理的反过来,如果通用的模块依赖于不通用的模块,那 么这个通用的模块也会变得不通用2) .之前的创建模块尽量不要依赖于后创建的模块根据时间轴以及产品的发展,较早开发的需求•般都是通用的或者是基础性质的需求,而麻开发的需求是业务型的需求为主根据这个性质,后开发的需求应该大部分依赖 于Z前的特性,比较少的情况是让Z前的需求依赖于个麻来的需求,当然•些需求变更可能会引发这个现象麻创建的模块虽然可以依赖Z前创建的模块,但是尽量不要去修 改原来创建的模块,如果出现这种情况,也要考虑•下这个修改是不是合理的3).需要进行微观分层(组件网图)口常开发中,需要冇•张组件网图展现在开发人员的面前,使得开发人员在能总识到哪些依赖是不M该出现的当然,在开发…个功能Z前,也应该进行微观空次的设计,Z麻再进行代码的编吗。
7. 增加功能三步法我们拿到-份需求,需要增加•个功能,应该怎么做?如果新功能与原先的模块有依赖的时候,如果是经验欠缺的同事,他们会怎么去做呢?会不会考虑说架构会不会合理?经 验欠缺的同事可能通常都不会这么考虑,他们只是集中于能不能把需求实现,而不是考虑这样用架构上合不合理团队就应该有规范去约朿经验欠缺的同事不去犯错误这里有•个增加功能的三步法供读者参考,这些方法可能不完善,读者可能有更好的方法,应该d找适合H己团队的解决办法1).不修改依赖,不修改或增加接口假设原来就冇两个模块,…个在上层i个在底空,如果需要新写…个功能,第-步需要先考虑的是,我能不能衣上丿z坞代码,不修改两个模块的依赖,不修改也不增加接I I,我的需求能不能满足假如说已经有现成的接II和现成的依赖,首先就要考虑能不能利用现成的接II来完成需求在没有规范约朿的情况下,可能很多时候这个模块改…下,那 个模块也改一下,就把需求做完了2).不修改依赖,但增加接口如果第•步不满足需求的情况下,我们才考虑第二步,不要修改依赖,但是修改接II,这个接I I可能就是…个比较通用的,而不是针对特定需求的,新增接II需要考虑扩展性和通用性。
很多场景只实到这•步都可能满足的3) .修改内部依赖如果第二步还不能满足需求,必然会导致模块的耦合,底层如果依赖于上层,就要重新考虑将组件依赖图进行一些调桀,就必须做一些重构,进行升级降级,完全耦介的两 个模块甚至可以合二为一8. 组件网图的自动化监控随时时间的推移,代码中的依赖越来越多,如何将代码依赖的变化有效的监控起來建议团队开发一个监控纽.件网图变化的T具,一H冇开发人员把依赖搞乱,丁•具就会发出邮 件进行报警一个依赖层次正常的组件网图,是不会出现环状依赖的我们可以将环状依赖作为代码变乱的一个客观依据所以组件网图工具可以做成只要发现环状依赖,就发 出邮件报告给开发人员进行重构组件网图工具应该每天夜里定期运行,找到当天新修改的代码中是否引出新的依赖和环状依赖,及时修改9. 让架构去保证开发人员不犯错防止代码变乱,我们可以进行各种培训提高开发人员的素质,开发前的设计评审,开发后的代码检视,或者是监控工具每天的检杳更重要的戒该是从架构上去保证开发人 员不会犯错误,就像前面提到的积木模型,先将四面墙围起来再进行积木的搭建我们怎么在架构上让开发人员方便的进行解耦?比如我们冇一个通川的界面,界面上会插入备种业务图标,我们不能让一个通用的界面去依赖于衿个具体的业务,所以应该 设计一套插入体系:在界而上留了一些位置,让业务插进来。
这就从架构上访止这种耦介,示续开发人员需要继续加图标,他不会在通用界面上去调用业务的接口获取图标,因 为现有机制很难这样做所以只要架构上设计考虑充份,是可以让后来的开发人员不要犯错误的。
