编写可维护软件的不朽代码随想-7

Posted 小么me

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了编写可维护软件的不朽代码随想-7相关的知识,希望对你有一定的参考价值。

架构组件松耦合

有两种构建软件设计的方式:简单到明显没有缺陷;复杂到没有明显的缺陷。

原则

顶层组件之间应该做到松耦合

尽可能减少当前模块中需要暴露给其他组件中模块的相关代码

 

模块耦合度关注于单个模块对系统其他部分的暴露程度,组件耦合度关注的是一个组件中的模块,对其他组件中模块的暴露程度。

如果从组件层面来考虑,相同组件中模块之间的调用会被认为是一个内部调用,但是如果从模块层面来考虑,就变成了模块之间的耦合。

将组件级别的松耦合称为组件独立,与其相反的称为组件依赖。当发生组件依赖时,组件的内部实现过多地暴露给了依赖它们的其他组件,使得组件不能表现为一个个单独的个体,导致我们很难判断出对一个组件的改动会影响到哪些其他组件。

一个组件内发生的变化只在这个组件内部有效果时,系统更加容易维护。

能够提升可维护性的调用分为两种:

1. 内部调用是健康的,它们的内部逻辑对于外部是隐藏的。

2. 传出调用是健康的,把要做的任务代理给其他组件,创建了一个向外的依赖,将不同的关注点代理给其他组件是一件好事,代理可以在一个组件内部任何地方进行,不必受限于组件内模块的数量。

 

降低可维护性的调用包括:

1. 传入调用,通过提供一个接口,为其他组件提供功能。组件内的代码应尽可能地封装,避免来自其他组件的直接调用。

2. 透传代码,必须要避免。透传代码既接收传入调用,又同时代理给其他组件。其违反了信息隐藏的原则,将自己的代理(实现)暴露给自己的客户,就像你向一个公司的帮助中心咨询了一个问题,它却没有给出答案,而是直接将问题转给了另一个公司。你就不得不依赖于两个公司来获得答案。从代码角度来看,说明组件之间没有良好的划分职责,不仅难以追溯到请求的整个路径,也难以测试和修改。

透传是透明传输

比如这样的设计:

层与层之间设计的单向依赖,

用户接口——服务层——业务逻辑层——数据抽象层——数据库层

随着时间,对于依赖原则的违反,导致了互相混乱,用户接口可能直接查询到数据库层,或者,服务层跳过业务逻辑层,直接对数据操作。

低组件依赖可以分离维护职责,让测试变得更容易。

 

使用本原则:

抽象工厂模式,这是经常在实践中使用的,能够成功限制组件对外部暴露接口的设计模式。更多地依赖于约定,而更少依赖于实现细节。

依赖注入框架,也是个很好的方法做到架构组件松耦合。

抽象工厂模式在一个通用的 产品工厂 接口背后,隐藏了具体 产品 创建过程,产品通常不只有一种类型。以下例子:假设有一个名为PlatformServices的组件,实现了对某个云主机平台服务的管理功能,支持两个具体的云主机平台,Amazon AWS和Microsoft Azure,为了能够启动、停止云平台的服务器,以及预定存储空间,为一个云主机平台实现这个接口:

public interface ICloudServerFactory
    {
        ICloudServer LaunchComputeServer();
        ICloudServer LaunchDatabaseServer();
        ICloudStorage CreateCloudStorage(long sizeGB);
    }

    public class AWSCloudServerFactory : ICloudServerFactory
    {
        public ICloudStorage CreateCloudStorage(long sizeGB)
        {
            return new AWSCloudStorage(sizeGB);
        }

        public ICloudServer LaunchComputeServer()
        {
            return new AWSComputeServer();
        }

        public ICloudServer LaunchDatabaseServer()
        {
            return new AWSDatabaseServer();
        }
    }
    
    public class AzureCloudServerFactory : ICloudServerFactory
    {
        public ICloudStorage CreateCloudStorage(long sizeGB)
        {
            return new AzureCloudStorage(sizeGB);
        }

        public ICloudServer LaunchComputeServer()
        {
            return new AzureComputeServer();
        }

        public ICloudServer LaunchDatabaseServer()
        {
            return new AzureDatabaseServer();
        }
    }

    //这些工厂类会调用特定的AWS和Azure实现类,对服务器和存储返回各自通用的接口类型。PlatformServers组件之外的代码,可以按照如下方式使用接口模块ICloudServerFactory
    public class ApplicationLanucher
    {
        public static void Main(string[] args)
        {
            ICloudServerFactory factory;
            if(args[1].Equals("azure"))
            {
                factory = new AzureCloudServerFactory();
            }
            else
            {
                factory = new AWSCloudServerFactory();
            }
            ICloudServer computeServer = factory.LaunchComputeServer();
            ICloudServer databaseServer = factory.LaunchDatabaseServer();
            ICloudStorage cloudStorage = factory.CreateCloudStorage(1000000L);
        }
    }
View Code

这样,其他组件与PlatformServices之间就可以形成松耦合关系。

 

常见反对意见:

1. 因为组件之间是混乱的,所以不可能修复组件依赖

通常在维护时遇到的最明显问题就是各个组件互相纠缠在一起,应该从分析模块是否含有透传调用开始,因为它对可测试性以及准确预测功能方面影响最严重。将组件职责的边界划分清楚,也会提高组件的可分析度和可测试度,例如,含有大量传入调用的模块,说明它们承担了多种职责并且可以被拆分,当它们被拆分后,代码就变得更加易于分析和测试了。

2. 没时间修复组件依赖

我们应当解决对可维护性造成真正影响的问题,当团队发现组件依赖破坏测试性、分析性或者稳定性时,就应该解决它。可以通过测量紧耦合组件所造成的问题上升率,或者增加的维护成本,来巩固你做这件事的理由。

3. 透传代码来自于需求透传

有些架构设计了一个中间层,从一侧(用户界面)收集请求然后打包传给其他层的服务层,只要实现了松耦合,这种层本身不是问题,它应该能够明确区分传入请求和传出请求,该层中接收请求的模块要遵循以下原则:不应该处理请求本身;不应该知道去哪里及如何处理请求。如果满足了这两点,服务层中的接收模块就有一个传入请求和传出请求,而不是将请求传给接收组件中的某个具体模块。

 

SIG评估组件独立性

SIG将组件间的松耦合定义为 组件独立性,独立性按照模块级别进行测量,系统中的每个模块都应该属于一个组件,这里的模块是指代码单元最小集合,通常是一个文件。通过测量模块之间的调用(静态代码分析)来得到它们的依赖关系,将其划分为隐藏代码和接口代码。

隐藏代码由没有传入依赖的模块组成,这些模块只在自己的组件内部互相调用,也可能调用组件外部的其他模块。

接口代码由含有传入依赖的模块组成,包括传入调用和透传调用的模块代码。

SIG根据隐藏代码所占比例作为组件独立性的测试标准,还是分为4星,要到达4星标准,含有传入依赖的模块比例不能超过14.2%

 

以上是关于编写可维护软件的不朽代码随想-7的主要内容,如果未能解决你的问题,请参考以下文章

代码不朽

优秀设计之ETC:编写可维护的代码

随想2

2020/2/7-Python学习计划

查看发票组代码后的总结和有感

测试如何编写可维护的集成测试