对系统可复用性的理解

什么是软件复用

软件复用(Software Reuse)是指将已有软件的各种有关知识用于建立新的软件,以缩减软件开发和维护的花费,是提高软件生产力和质量的一种重要技术。软件复用对程序员来说是再平常不过的事情,可能从学校里的编程作业开始,我们就在接触复用。早期的软件复用主要是代码级复用,被复用的知识专指程序。但其实领域知识、架构设计、技术文档等知识类的沉淀,同样适用上述定义,也属于软件复用的范畴。


为什么要复用

在项目交付的语境下谈复用,本质就是为了提升交付的效率,加快项目从客户的想法到实际交付到客户手里的时间,并减少投入的人力的资源成本。在项目交付中,最关键的约束就是成本和时间,如果脱离这两个因素,软件复用也许不那么紧要。但对我们做项目交付的而言,这两点恰恰是最敏感的。通过抽象完成代码或者功能的高内聚低耦合是提高复用的关键,将数据和操作分离,也会增加软件的复用程度。


复用等级划分

1. 代码复用:最简单直接的方式,也最常见,具体的形式有:类库、SDK、算法(在已有的成熟算法中选择一个比自己重新开发一个好的多)、工具类等。
2. 组件复用:在一个应用工程内,通过利用已有的组件去实现部分功能。如:中间件、开发框架、DB、ES、redis,以及在存储层之上构建出来的数据结构。
3. 应用复用:比组件复用更进一步,通过API方式去使用已有的服务,比如提供发短信能力的一个微服务。这个服务对消费方来说是一个黑盒,不需要对这个服务的源代码做修改。 
4. 产品复用:这里是指能够开箱即用,仅通过配置就能适配客户的个性化需求。通常是指SaaS类的产品,意味着很高的场景抽象能力,同时也和业务场景和定位有关。


代码和组件复用

代码复用是最常见的复用,包括目标代码和源代码的复用,指的是在同一个应用的多个模块中,或者是在多个应用下代码的复用。从复用关系上看:可以直接使用、继承(实现)使用、注入(机制)使用,配置使用。理想状况下,代码复用可以共享通用类、函数集合来实现;即便是在最差的情况下,代码复用也可以通过拷贝和修改源代码来实现。代码复用的最大好处在于它可以减少你的代码量,也就潜在地减小了开放和维护成本。坏处则在于你自己应用的能力范围就被约束住了,而且也增加了应用和被复用代码之间的耦合。在代码复用层面需要注意:

面向接口编程:"面向接口编程"是面向对象设计(OOD)的第一个基本原则。面向接口编程就是先把客户的业务逻辑线提取出来,作为接口,业务具体实现通过该接口的实现类来完成。当客户需求变化时,只需编写该业务逻辑的新的实现类,通过更改配置文件(例如Spring框架)中该接口的实现类就可以完成需求,不需要改写现有代码,减少对系统的影响。

优先使用对象组合而不是继承:"优先使用组合而不是继承"是面向对象设计(OOD)的第二个基本原则。继承是在程序开发的过程中重构得到的,而不是程序设计之初就使用继承,很多开发者滥用继承,结果可能造成后期的代码解决不了需求的变化。因此,优先使用组合而不是继承,是面向对象开发的一个重要经验。继承:继承的起源,来自于多个类中相同特征和行为的抽象。对象组合:对象组合要求被组合的对象具有良好的接口,并且通过从其他对象得到的引用在运行时运态定义,Spring的各种以来注入基本都是基于组合的形式。

将可变的部分和不可变的部分分离:"将可变的部分和不可变的部分分离"是面向对象设计(OOD)的第三个基本原则。如果使用继承的复用技术,我们可以在抽象基类中定义好不可变的部分,而由其子类去具体实现可变的部分,不可变的部分不需要重复定义,而且便于维护。如果使用对象组合的复用技术,我们可以定义好不可变的部分,而可变的部分可以由不同的组件实现,根据需要,在运行时动态配置。这样,我们就有更多的时间关注可变的部分。对于对象组合技术而言,每个组件只完成相对较小的功能,相互之间耦合比较松散,复用率较高,通过组合,就能获得新的功能。

代码复用的最佳实践:面向接口编程:使用接口定义模块或组件的功能,而不是具体的实现类。通过面向接口编程,可以降低模块之间的依赖性,提高代码的灵活性和可替换性。组合优先:在设计中,优先考虑使用对象组合来构建系统,而不是过度依赖类继承。对象组合更灵活,可以根据需要动态地组合不同的对象,而类继承在一定程度上限制了系统的扩展性。单一责任:确保每个模块或组件具有清晰的单一责任。每个模块应专注于完成特定的功能,并且在模块内部进行细分,避免功能的耦合和冗余。封装变化:识别系统中容易发生变化的部分,并将其封装起来。这样,在变化发生时,只需要修改变化的部分,而不影响其他部分的功能。松耦合&高内聚:模块之间应保持松耦合关系,降低它们之间的依赖性。同时,模块内部应该保持高内聚,即模块的各个部分相互关联并协同工作,完成特定的任务。可插拔性和可扩展性:通过合成复用原则,设计具有可插拔性和可扩展性的系统。模块之间的组合关系可以根据需要进行修改和替换,从而方便地扩展系统功能。避免过度设计:在应用合成复用原则时,要避免过度设计和过度抽象。只有在确实需要复用和组合的情况下才使用合成复用原则,避免不必要的复杂性和额外的开发成本。

代码复用常见的应用形式:设计原则、设计模式等

应用和产品复用

捕捉领域差异:对于系统级复用来说,获取必要的领域变化是非常重要的。业务领域充满了变化,需要通过你的代码库进行定义和管理。如何应对产品线的变化是软件有效复用的核心问题。为了在你的领域内定义这些变化,你需要寻找相关的业务专家以及该领域的终端用户。贴近这些专家和用户,与他们一起工作,你才能更好的了解领域知识、各方面的趋势变化,以及这些变化是如何体现在用户故事中的。从客户的角度看,相对于他们的业务功能来说,这些变化是非常自然的。你不仅要花时间确保了解整体上的变化,同时还要认识到这些变化的子集真正意味着什么。

从务实的角度看软件复用,你会发现去定义某一问题领域的所有变化是不现实的。除非你能引入并把领域变化作为你的开发实践的一部分,否则你的迭代很难进行,工作软件也不会正常发布。从设计的视角来看有些问题能够帮助你。每次你看到用户故事时,你都应该问自己:

会引入哪些领域实体?
是否是第一次遇到这些领域实体?
是否在一个以上的产品或应用系统中用到这个故事?
在故事中业务领域的什么方面发生了变化?一些通用的变化往往会重现──经常使用某个或几个功能的那类用户,一个功能或一组功能的行为差异,有多少功能被绑定在一起作为业务标准,界面隐喻和主题,报表 / 图表功能。
这些用户故事集或用户故事的某些方面是否常常一起出现?


易于集成:系统级复用很容易被忽视的一个方面就是可复用资产的集成,包括应用系统、流程和服务。大多数团队聚焦于构建大型可复用的资产库──服务,对象,框架和领域特定语言库。虽然这是必要的,但对于成功的系统级复用来说,这是远远不够的。一个重要的组成部分就是易于集成。什么意思呢?具体来说就是:

评估需求并做决定是否现存的资产能够完全满足需求(或是需要修改),或者需要开发新功能
通过集成可复用资产来应对风险(满足服务水平协议(SLAs),解决方案复杂性等)
通过有效接口(有 java 接口?还是 web 服务?)来共享信息。
提供样例代码和集成设计模式
提供全面的错误代码列表和错误处理建议──如果服务出现了特殊错误,用户该怎么办?是否存在需要用户特别注意的业务错误或数据校验错误?
确保客户并不缺少可复用资源──基于服务的复用能力是必不可少的,多个用户可以同时调用同一服务。
通过测试集成提供帮助(提供测试数据,单元测试代码,以及测试响应时间 / 吞吐量等实用功能)
你可以建立一个服务目录,并寄希望它可以达到很高的复用程度,但大多数情况下你会失望的。伸出手去帮助你的客户,帮助他们成功。让评估、集成和测试变得更加容易。不要着急,但要确保你的团队走在正确的路上,而不是你试图向他们“兜售”复用的价值。


复用的争议

复用不是系统要追求的目标。很多人认为更多模块的「复用」,就可以做到像乐高一样快速搭建系统,但是很多复用并不是乐高,而是器官移植,不仅不能提高效率,要不断面对各种各样的排异反应,降低了速度和稳定性。很多创业公司快速发展时系统腐化的主要问题就出现在了强行复用上。强行复用的案例很常见,例如:上百个字段的数据表(例如订单表,数据表),不清楚哪个字段在哪个场景下有效,根据名词来设计系统,出现业务中台,例如订单中台,电商中台,各部门之间不断地扯皮,出来无数个小的微服务,然后搞个统一的业务编排调度层(所谓的 gateaway)来处理业务,可能还抽象 DSL;上线十分复杂,需要发布 N 各模块;调度模块是系统大单点,稳定性迭代速度都是问题

如果有一个好的设计,我们需要谨记《Hints for Computer System Design》中的「Use a good idea again instead of generalizing it. A specialized implementation of the idea may be much more effective than a general one.」那系统设计好的标准是什么?衡量的维度有优先级吗?:两个评价标准自治性和一致性

自治性:受其他模块的影响程度,观测模块的接口是否稳定。可以使用AutonomyMetrics 进行衡量
一致性:应该使用一个模块的地方使用一个模块的程度,也可以衡量是否过早,过度抽象了。可以使用 ConsistencyMetrics

在业务层次是否要从多个业务模块中抽象出一个底层模块,可以通过多个业务模块的这部分公共逻辑部分是否要一起进行改变进行判断,如果不是需要一起变化的就不要抽象出公共模块。
根据个人经验如果你在要从各个业务模块进行抽象出一个模块保持一致与保持在模块保持自治之间犹豫时,要毫不犹豫优先选择「自治」


总结

大部分情况下系统的核心复杂度不会减少, 只是转移;不会因为所有代码一个人写,会比分成两个人写更快,分工是应该且必须的。但是请注意的是
不要为了复用随意通用化,抽象化,复用不是目的。如果要通用化就拿ConsistencyMetrics 来判断是否是抽象合理的。
不要随意复用其他的模块,自治优先,用 AutonomyMetrics 衡量;如果要复用一定要确保共识和标准优先,形不成共识和标准就不用复用。

参考

https://developer.aliyun.com/article/1046494
https://blog.51cto.com/u_15127562/3944414
https://developer.jdcloud.com/article/2868
https://www.modb.pro/db/56668
https://z.itpub.net/article/detail/D7D8DBC2DB55ADDE05301E9B8AD4D349
https://www.cnblogs.com/wangtao1211/p/12486066.html
https://d1.amobbs.com/bbs_upload782111/files_35/ourdev_608272DMR8VS.pdf
https://zhuanlan.zhihu.com/p/508877002
https://blog.51cto.com/issaxl/971916
https://zhuanlan.zhihu.com/p/632206160
https://www.woshipm.com/pmd/5146370.html

你可能感兴趣的:(系统架构)