复用性风控:软件复用成本的量化管理(复用性是什么意思)
点击链接阅读原文,获取更多技术内容:复用性风控:软件复用成本的量化管理-阿里云开发者社区
本文分析「复用性」这一概念背后的风险及成因,借助笔者在业务安全和基础安全的经验,提出了一个在软件研发流程中,管理「复用性成本风险」的风险管理模型,希望能为读者在后续的技术决策和软件研发流程提供些许帮助。
作者 | 齐光
来源 | 阿里云开发者公众号
复用性(Reusability)是软件工程中一个被频频使用的术语,它一般作为产品的卖点被宣传,或者出现在技术设计文档之中。大部分看到这个概念的的受众只是将其作为一个积极的软件非功能属性去理解,但却忽略了其背后隐藏的风险。本文从另一个角度出发,去分析「复用性」这一概念背后的风险及成因,借助笔者在业务安全和基础安全的一点经验,提出了一个在软件研发流程中,管理「复用性成本风险」的风险管理模型。从模型出发,我们可以认识到实现复用时面临的各项挑战、开发认知谬误、复用成本的形式化定义方法等,希望这些输入能为读者在后续的技术决策和软件研发流程提供些许帮助。
本文主要分为三个部分:第一部分介绍复用性的定义以及不合理复用引入的主要技术债,第二部分分析复用性失效的原因;第三部分为复用性软件资产的构建方和使用方提供一个形式化的度量工具,该工具将奠定后续风险管理模型评估阶段定量分析的基础;第四部分提出一个用于管理「复用性风险」的模型,覆盖软件研发生命周期的全流程,通过事前评估、事中缓释和事后迭代三个环节最大程度地降低由于软件复用带来的软件开发和维护成本。
一、复用性的理想与现实
1.1 复用定义:从代码到系统
软件复用是解决软件质量和生产力问题的一种方法,它指的是在软件开发过程中重复使用相同或相似的软件元素。通过合理利用软件复用技术,我们可以提高开发效率,并且降低开发过程中的错误率。同时,软件复用还可以促进团队协作和知识共享,使得开发者们能够更好地利用彼此的经验和资源。因此,在当今快节奏的开发环境中,软件复用已经成为提高生产力和质量的关键策略之一。在过去几十年的时间里,很多编程语言的成功(Python、Java 等)和开源文化的蓬勃发展,都与复用密不可分。
软件复用可以在不同粒度上进行,包括代码和设计拷贝、源代码复用、设计和软件体系结构复用以及领域特定的软件体系结构复用等。早期的软件复用主要集中在代码级别,例如共享方法、抽象类、库、微服务和Docker镜像等。随着时间推移,其外延拓展至领域知识、开发经验、设计文档、需求分析和测试用例及数据等在不同阶段所产生的各种软件产品。在本文中,除非特别说明,复用性主要指的就是聚焦代码的复用,下文中的「组件复用」,不仅限于通常我们认知中的公共库,还包括代码方法、公共类、软件框架、可集成系统等软件开发中的可复用元素。
1.2 复用风险:复杂度和成本
诚然,通过组件的复用可以提高软件开发效率和质量,但复用不是银弹,复用也会有一些副作用:
1.兼容性/安全性/性能;
2.增加了系统间的依赖;
3.增加了开发和维护成本。
首先,兼容性/安全性/性能等这几类问题,是针对可复用组件的使用方来说的,一般来说,在决策是否复用之前就可以评估,其指标和过程也比较清晰,这里就不具体展开了。
其次,复用会增加系统依赖。依赖关系是软件的基本组成部分,无法消除,但软件设计的目标之一是尽可能消除依赖关系,并使依赖关系尽可能简单和明显。当我们引入外部组件进行复用时,软件组件之间的依赖关系会导致组件变更范围的扩大以及组件认知负荷的增加,前者是针对组件维护方而言的,即看似简单的变更需要在许多不同的地方修改代码,随着消费者数量的增长,在不同需求之间进行平衡变得越来越困难;后者是对于组件使用方而言的,即开发人员需要了解大量组件领域知识才能实现有效的组件复用。
比如,需要了解待使用接口中若干入参的设计意图、是否存在隐式依赖传递从而导致依赖冲突等。依赖的增加会为系统引入更多的复杂性,而我们知道,构建软件系统的核心挑战就是管理复杂性,复用组件只会在一定程度上转移复杂性,但并不能消除复杂性。因此,我们需要在「复用组件降低成本」和「复用组件引入依赖(复杂性)」之间取得平衡。
最后,复用会增加各项成本。包括开发的成本、变更的成本、集成的成本、领域知识迁移的成本。对于一个面向复用设计的组件来说,实现正确抽象和通用框架的设计和开发成本,比一次性的解决方案高得多,对于组件的后续维护者来说,这样的可复用框架和库通常也会带来陡峭的学习曲线(因为文档一般是缺失的),组件会逐渐走向腐化,最后不得不推倒重来。此外,对于可复用组件的使用方来说,其理解和集成组件的成本通常也是被忽略的,一些强推的业务层的「伪复用框架」给前台集成的同学带来了巨大的集成、学习和维护成本。
上述复用带来问题,有一些是可以规避的,如兼容性、性能、容量等的匹配度,有一些是无法避免的,如设计通用化组件的开发成本、不合理的抽象导致的代码腐化、不合理的复用导致的维护成本等。事实上,无论我们在技术上做多么精妙的设计,技术的创新永远滞后于系统的腐化速度。
为了最大程度的降低复用带来的风险,本文提出一套从类比于安全风险管理的「复用性风险」应对模型,从事前评估、事中缓释、事后迭代三个阶段出发,最大程度地降低我们在开发可复用组件、使用可复用组件中遇到的各类风险。需要说明的是,上面以及后面指的「复用性风险」,定义为「由于不合理的复用决策,导致依赖和复杂度膨胀过快,从而导致软件维护成本过高」的问题,除了成本风险外,由于复用组件的不合理使用或存在的缺陷而导致的兼容性、安全性、性能等方面的风险,其风险更为显著和易于治理,因此不是本文论述的重点。此外,复用开发过程中的开发目标偏移、迭代和发布计划的延期、人员短缺等风险,限于篇幅也不在这里展开。
第二部分首先会介绍导致「复用提升软件开发效率」这一原则失效的几类主要原因,第三部分会重点介绍用于评估复用性的若干工具,有了对复用性本质的认识后,再第四部分我们会简要介绍复用性风险管理模型。
二、复用性风险根因分析
2.1 现实挑战:正确和错误的抽象
我们复用组件的一个初衷,除了是为了提升研发效率之外,也是希望可复用组件可以将领域的复杂性隔离在一个我们永远看不到的地方,从而整体降低组件使用方的系统复杂度。因此,一个可复用的组件,无论其规模大小,其设计过程就是对某个领域高度抽象的过程。在设计组件时,向上面对当前或潜在的需求,需要我们做一定的前向通用设计,向下尽可能屏蔽掉组件的实现细节,抽象的结果直接决定了后续该组件可复用性程度的高低(可复用性的度量将在下一个章节详述)。但遗憾的是,良好的抽象能力对于大部分开发者来说是一个稀缺的产物,它需要对问题进行清晰的定义、简化和分解,同时识别和利用通用模式,将子问题的解法组合起来形成一个整体解决方案,依赖对设计模式、开源的库和框架、数据结构和算法以及大量生产项目的长期实践和思考。
在日常的代码中,我们不乏抽象,但大部分都是不合理的抽象。错误的抽象造成的危害甚于不抽象,比如常见的一个现象:对设计模式的适用范围知之甚少,仅仅为了炫技而滥用设计模式,导致代码的可读性和可维护性下降。
除了对抽象能力的要求外,很多时候需求紧迫度、开发资源、责任心以及组件所在领域职责的变更等因素,都会导致可复用组件从出生就带着「高成本」的原罪,其后续的使用成本和维护成本会急剧上升,这里就不一一展开了。
2.2 认知谬误:复用不是设计目标
一个对于复用性的认知谬误就是,把「不重复」等效为「复用」,这两个概念之间有相似之处,但还是有一些微妙的差别。「不重复」即我们所熟知的 DRY 原则(Don’t Repeat Yourself),其目标是通过减少重复建设从而避免承担副本不一致的维护成本,而 Reusability 是从所有代码中找到重复的部分,然后在复杂度可控的前提下,努力抽象出可复用的东西。一堆不重复的代码,并不代表存在可复用的组件。
复用只是实现不重复目标的一种手段,「不重复」才是我们设计软件系统时的目标,单纯追逐「复用性」很多时候会出现一些本末倒置的现象。如出现了一些接入成本非常高的自动测试框架、业务中台框架,一味追逐「(我)一次开发,(你)随处使用」,殊不知在使用方需要消耗大量的精力去内化框架设计者的设计初衷,面对十几个接入参数或配置文件一筹莫展。
举个例子,偶尔会看到我们在业务层代码中,部分同学会把简单的新增和修改逻辑抽象为一个方法,美其名曰「提供给接入层复用」,如下面的 insertOrUpdate 方法中,初看是复用了领域对象转换和用户对象是否存在的代码,符合 DRY 原则,但实际上却是混用了两个不同的业务语义,会给后续的维护带来较高的成本,如变更用户信息时,需要做更个性化的用户属性处理,这时候调整领域对象转换处的代码,将会影响新增逻辑。
更合理的实现是,将明显不同语义的代码进行拆分,虽然看上去存在一定程度上的代码重复,但其设计会更利于后续的功能迭代,也更符合代码的「单一职责」设计原则。
2.3 决策偏差:复用的决策权在哪
代码的复用更多的时候是软件开发者自发完成的,但我们无法忽视的一点是,如何集成、是否复用、如何复用、是否是同一个功能、使用什么粒度的复用,很多时候是由业务架构决定的,「康威定律」还是无法回避的。
比如,在一个新的场景里,产品要求把「PPT 上与其名字相同的一个功能」进行复用,以快速上线,虽然他们除了名字相同,其产品形态、业务流程、环境依赖等都不一样。最终强行「复用」的结果就是代码逻辑里出现了大量的分支判断,底层技术架构变得臃肿。由于对于领域的理解不同,出现这种情况在所难免。虽然很多时候软件复用的决策权并不在开发者这里,但出于技术情怀也好,责任心也罢,开发者有义务去做这种纠偏,最大程度地消除这种差异性。但需要认识到技术的作用在这里并不是决定性的,卓越的技术是复用成功的必要非充分条件。
2.4 工具缺失:如何计算复用成本
复用性度量,主要分为两个部分:
1.复用度:决定一个组件复用性高低水平的因素有哪些?
2.复用成本:组件集成方、组件所在的组织,决定实行复用策略后的 ROI 如何计算?
通过复用度和复用成本两个指标,我们可以进行一定程度上的复用性定量分析,做出更为长远的技术决策。比如,可以了解到一个复用性高的组件,其特征有哪些?引入一个新的第三方组件时,除了基础的功能性组件外,我还需要考虑哪些?相较于使用已经存在的组件,是否考虑重新造一个轮子?「复用」和「造轮子」间成本有多大?关于复用性的度量工具,第三部分将重点论述。
三、复用性的形式化度量
3.1 组件度量:可复用水平的评估
我们在设计一段代码/一个类/一个模块等可复用的组件时,一些可衡量的软件指标共同决定了组件的可复用性水平的高低。这些指标包括:可靠性(Reliability)、可读性(Understandability)、可维护性(Maintainability)、通用性(Generality)与可迁移性(Portability),如下图所示。每一个指标可由各类代码度量属性决定,如组件的可迁移性由「组件的独立性」和「耦合性」两个属性决定,大部分的度量属性都是可以通过形式化定义并计算出来。不同指标的决定因子及度量值(括号中)如下:
1.可靠性:性能(响应时间)、容错程度(恢复时间);
2.通用性:类泛化水平(子类实例个数或接口实现类个数);
3.可读性:内聚性(类之间耦合度)、复杂度(圈复杂度)、规模(代码行数)、文档水平(数量 完整度);
4.可维护性:易于修改、单测和回归测试(测试覆盖度)、组件的独立性(依赖数)、耦合性(类间耦合度);
5.可迁移性:组件的独立性(依赖数)、耦合性(类间耦合度)。
为了度量整个组件的的可复用性,有必要定义一个可复用性计算模型。该模型基于上图所示的复用性属性模型。主要的可复用性属性、影响这些属性的因素以及度量这些因素的量度之间的关系显示在这个模型中。理论上,软件组件的可复用性(用 Reusability 表示)可以用表达式来计算:
Reusability = w1*M w2*R w3*P w4*U w5*G
其中 w1 ~ w5 为不同指标的权重值,指标 M(Maintainability)、R(Reliability)、P(Portability)、U(Understandability)、G(Generality) 值进行归一化(0 … 1)后,乘以每个指标不同的权重值,通过计算得到最终的组件的可复用度。
在上面的分析过程中,存在部分度量无法进行定量分析的情况,但不同因子组合计算还是有意义的,我们可以拿这些指标去评估我们目前的系统,存在的问题的严重程度。当下次别人问我们为什么要复用组件A而不是组件B时,我们可以给出更令人信服的理由,而不仅仅是「我觉得」、「A比B好很多」等论述。
3.2 组织度量:复用的投入产出比
对组件的复用性有了一个感性认知后,更加一步地,让我们从经济的角度去思考复用性背后的成本问题。首先,我们先定义几个变量 RL、NUC、RCR、RCWR。
- RL(Reuse Level):可复用组件在应用中的比例,即 RL=复用的组件中代码行数/应用总的代码行数;
- NUC(Not Use Cost):应用开发过程中完全不使用可复用组件的成本,注意不包括后续的维护成本;
- RCR(Relative Cost of Reuse):复用既有的组件与重新造一个相似的轮子,这两者之间工作量的比值,一般在0.03~0.4之间,经验值为20%,即这意味着复用所花费的成本大约是编写新组件所投入的20%;
- RCWR(Relative Cost of Writing for Reuse):开发可复用的组件与开发一次性使用的模块,这两者之间工作量的比值,一般在1.0~2.2之间,经验值为 1.5,即这意味着编写可复用软件需要大约50%的额外成本。
内容剩余60%,完整内容可点击下方链接查看:复用性风控:软件复用成本的量化管理-阿里云开发者社区
阿里云开发者社区,千万开发者的选择。百万精品技术内容、千节免费系统课程、丰富的体验场景、活跃的社群活动、行业专家分享交流,尽在:阿里云开发者社区-云计算社区-阿里云
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。