代码质量评价及设计原则

代码质量评价及设计原则1 设计模式 2 代码的优化 代码质量

大家好,欢迎来到IT知识分享网。

1.评价代码质量的标准

1.1 可维护性

可维护性强的代码指的是:

 在不去破坏原有的代码设计以及不引入新的BUG的前提下,能够快速的修改或者新增代码.

不易维护的代码指的是:

在添加或者修改一些功能逻辑的时候,存在极大的引入新的BUG的风险,并且需要花费的时间也很长.

代码可维护性的评判标准比较模糊, 因为是否易维护是针对维护的人员来说的,不同水平的人对于同一份代码的维护能力是不同的. 所谓 ”难者不会 会者不难”. 对于同样的系统,熟悉它的资深工程师会觉得代码可维护性还可以,而新人则会因为能力不足、了解不够深入等原因觉得代码的可维护性不是很好.

1.2可读性

软件开发教父,Martin Fowler曾经说过一句话: “任何傻瓜都能够编写计算机能理解的代码,而优秀的程序员能够编写人类能理解的代码。” 这句话的意思非常容易理解,就是要求我们写出的代码是易读的、易理解的,因为代码的可读性会在很大程度上影响代码的可维护行性.

是否存在重复代码、过长函数、过大类、过于亲密的两个 classes等

1.3可扩展性

代码的可扩展性表示,我们在不修改或少量修改原有代码的情况下,通过扩展的方式添加新的功能代码。

可扩展性的背后其实就是: “对修改关闭,对扩展开放” 这条设计原则,后面我们会详细的讲解

1.4灵活性

“灵活” 是指在添加新代码的时候,已有代码能够不受影响,不产生冲突,不出现排斥,在保证自身不遭到破坏的前提下灵活地接纳新代码。

1.5 简洁性

我们要遵从KISS ( Keep It Simple Stupid) 原则,代码要尽可能的简单;但是思从深而行从简,真正的高手能云淡风轻地用最简单的方法解决最复杂的问题。这也是一个编程老手跟编程新手的本质区别之一。

代码的写法应当使别人理解它所需的时间最小化.

1.6可复用性

代码的可复用性可以简单地理解为,尽量减少重复代码的编写,复用已有的代码.

1.7可测试性

单元测试在一个完整的软件开发流程中是必不可少的、非常重要的一个环节。通常写单元测试并不难,但有的时候,有的代码和功能难以测试,导致写起测试来困难重重。所以写出的代码具有可测试性,具有很重要的作用。

代码可测试性的好坏,能从侧面上非常准确地反应代码质量的好坏

2.UML图

统一建模语言(Unified  Modeling Language,UML)是用来设计软件的可视化建模语言。它的特点是简单、统一、图形化、能表达软件设计中的动态与静态信息。

UML 从目标系统的不同角度出发,定义了用例图、类图、对象图、状态图、活动图、时序图、协作图、构件图、部署图等 9 种图。

这里我们只介绍类图.

我们要去研究一个设计模式的时候,是需要借助UML类图更加准确的描述所使用的设计模式,和设计模式下类与类之间的关系

2.1类图概述

类图(Class diagram)是显示了模型的静态结构,特别是模型中存在的类、类的内部结构以及它们与其他类的关系等。类图不显示暂时性的信息。类图是面向对象建模的主要组成部分。

2.1.1类图的作用

2.1.2类图表示法

UML类图中具体类、抽象类、接口和包有不同的表示方法。

2.1.3在UML类图中表示具体类

具体类在类图中用矩形框表示,矩形框分为三层:第一层是类名字。第二层是类的成员变量;第三层是类的方法。成员变量以及方法前的访问修饰符用符号来表示:

代码质量评价及设计原则

2.1.4在UML类图中表示抽象类

抽象类在UML类图中同样用矩形框表示,但是抽象类的类名以及抽象方法的名字都用斜体字表示,如图所示。

代码质量评价及设计原则

2.1.5在UML类图中表示接口

接口在类图中也是用矩形框表示,但是与类的表示法不同的是,接口在类图中的第一层顶端用构造型 <<interface>>表示,下面是接口的名字,第二层是方法。

代码质量评价及设计原则

2.1.6在类图中表示关系

类和类、类和接口、接口和接口之间存在一定关系,UML类图中一般会有连线指明它们之间的关系。

关系共有六种类型 ,如下图:

代码质量评价及设计原则

2.1.6.1实现关系(implements)

实现关系是接口与实现类之间的关系。在这种关系中,类实现了接口,类中的操作实现了接口中所声明的所有的抽象操作。

在 UML 类图中,实现关系使用带空心三角箭头的虚线来表示,箭头从实现类指向接口。

例如,汽车和船实现了交通工具,其类图:

代码质量评价及设计原则

 

2.1.6.2泛化关系(extends)

泛化关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承关系。

在 UML 类图中,泛化关系用带空心三角箭头的实线来表示,箭头从子类指向父类。在代码实现时,使用面向对象的继承机制来实现泛化关系。

例如,Student 类和 Teacher 类都是 Person 类的子类,其类图如下图所示:

代码质量评价及设计原则

 

2.1.6.3关联关系

关联关系是对象之间的一种引用关系,用于表示一类对象与另一类对象之间的联系,如老师和学生、师傅和徒弟、丈夫和妻子等。关联关系是类与类之间最常用的一种关系,分为一般关联关系、聚合关系和组合关系。

我们先介绍一般关联关系, 一般关联关系又可以分为单向关联,双向关联,自关联。

2.1.6.3.1单向关联

在UML类图中单向关联用一个带箭头的实线表示。上图表示每个顾客都有一个地址,这通过让Customer类持有一个类型为Address的成员变量类实现。

代码质量评价及设计原则

2.1.6.3.2双向关联 

双向关联就是双方各自持有对方类型的成员变量。

在UML类图中,双向关联用一个不带箭头的直线表示。上图中在Customer类中维护一个List<Product>,表示一个顾客可以购买多个商品;在Product类中维护一个Customer类型的成员变量表示这个产品被哪个顾客所购买。

代码质量评价及设计原则

2.1.6.3.3自关联 

自关联在UML类图中用一个带有箭头且指向自身的线表示。上图的意思就是Node类包含类型为Node的成员变量,也就是“自己包含自己”。

代码质量评价及设计原则

2.1.6.4聚合关系

在 UML 类图中,聚合关系可以用带空心菱形的实线来表示,菱形指向整体。

聚合关系是关联关系的一种,表示一种弱的‘拥有’关系,体现的是A对象可以包含B对象,但是B对象不是A对象的一部分

在代码中: 比如A 类对象包含 B 类对象,B 类对象的生命周期可以不依赖 A 类对象的生命周期,也就是说可以单独销毁 A 类对象而不影响 B 对象

public class A{ private B b; public A(B b){ this.b = b; } } 

代码质量评价及设计原则

2.1.6.5组合关系

在 UML 类图中,组合关系用带实心菱形的实线来表示,菱形指向整体。

组合关系是一种强‘拥有’关系,体现了严格的部分和整体的关系,部分和整体的声明周期一样

在代码中: 比如A 类对象包含 B 类对象,B 类对象的生命周期依赖A 类对象的生命周期,B 类对象不可以单独存在

public class A{ private B b; public A(){ this.b = new B(); } } 

 代码质量评价及设计原则

2.1.6.6依赖关系

依赖关系是一种使用关系,它是对象之间耦合度最弱的一种关联方式,是临时性的关联。

在代码中,某个类的方法通过局部变量、方法的参数或者对静态方法的调用来访问另一个类(被依赖类)中的某些方法来完成一些职责。

在 UML 类图中,依赖关系使用带箭头的虚线来表示,箭头从使用类指向被依赖的类。

下图所示是司机和汽车的关系图,司机驾驶汽车:

代码质量评价及设计原则

 3.六大设计原则 (SOLID)

3.1单一职责原则

单一职责原则,英文缩写SRP,全称 Single Responsibility Principle。

在<<架构整洁之道>>一书中 关于这个原则的英文描述是这样的:A class or module should have a single responsibility 。如果我们把它翻译成中文,那就是:一个类或者模块只负责完成一个职责(或者功能)。

通俗解释

单一职责原则的定义描述非常简单,也不难理解。一个类只负责完成一个职责或者功能

也就是说在类的设计中 我们不要设计大而全的类,而是要设计粒度小、功能单一的类.

比如 我们设计一个类里面既包含了用户的一些操作,又包含了支付的一些操作,那这个类的职责就不够单一,应该将该类进行拆分,拆分成多个功能更加单一的,粒度更细的类.

3.1.1场景示例

那么该如何判断一个类的职责是否单一 ?

>其实在软件设计中,要真正用好单一职责原则并不简单,因为遵循这一原则最关键的地方在于职责的划分,而职责的划分是根据需求定的,同一个类(接口)的设计,在不同的需求里面,可能职责的划分并不一样.

我们来看下面这个例子:

在一个社交媒体产品中,我们使用UserInfo去记录用户的信息,包括如下的属性.

代码质量评价及设计原则

请问上面的UserInfo类是否满足单一职责原则呢 ?

正确答案: 根据实际业务场景选择是否拆分

代码质量评价及设计原则 

总结: 不同的应用场景、不同阶段的需求背景下,对同一个类的职责是否单一的判定,可能都是不一样的,最好的方式就是,我们可以先写一个粗粒度的类,满足业务需求。随着业务的发展,如果粗粒度的类越来越庞大,代码越来越多,这个时候,我们就可以将这个粗粒度的类,拆分成几个更细粒度的类。这就是所谓的持续重构

3.1.2如何判断一个类的职责是否单一?

这里没有一个具体的金科玉律,但从实际代码开发经验上,有一些可执行性的侧面判断指标,可供参考:

 3.2开闭原则

开闭原则规定软件中的对象、类、模块和函数对扩展应该是开放的,但对于修改是封闭的。这意味着应该用抽象定义结构,用具体实现扩展细节,以此确保软件系统开发和维护过程的可靠性。

通俗解释

定义:对扩展开放,对修改关闭

3.2.1优点:

3.2.2场景示例

系统A与系统B之间进行数据传输使用的是427版本的协议,一年以后对427版本的协议进行了修正。

设计时应该考虑的数据传输协议的可变性,抽象出具有报文解译、编制、校验等所有版本协议使用的通用方法,调用方针对接口进行编程即可,如上述示例设计类图如下

代码质量评价及设计原则

 调用方依赖于报文接口,报文接口是稳定的,而不针对具体的427协议或427修正协议。利用接口多态技术,实现了开闭原则

3.2.3顶层设计思维

在写代码的时候后,我们要多花点时间往前多思考一下,这段代码未来可能有哪些需求变 更、如何设计代码结构,事先留好扩展点,以便在未来需求变更的时候,不需要改动代码整 体结构、做到最小代码改动的情况下,新的代码能够很灵活地插入到扩展点上,做到“对扩 展开放、对修改关闭”。

3.3里氏替换原则

如果S是T的子类型,对于S类型的任意对象,如果将他们看作是T类型的对象,则对象的行为也理应与期望的行为一致。

子类对象能够替换程序中父类对象出现的任何地方,并且保证原来程序的逻辑行为不变及正确性不被破坏。

3.3.1 通俗解释

什么是替换 ?

替换的前提是面向对象语言所支持的多态特性,同一个行为具有多个不同表现形式或形态的能力。

以JDK的集合框架为例,List接口的定义为有序集合,List接口有多个派生类,比如大家耳熟能详的 ArrayList, LinkedList。那当某个方法参数或变量是 List接口类型时,既可以是 ArrayList的实现, 也可以是 LinkedList的实现,这就是替换。

什么是与期望行为一致的替换?

在不了解派生类的情况下,仅通过接口或基类的方法,即可清楚的知道方法的行为,而不管哪种派生类的实现,都与接口或基类方法的期望行为一致。

3.3.2场景示例

里氏替换原则要求我们在编码时使用基类或接口去定义对象变量,使用时可以由具体实现对象进行赋值,实现变化的多样性,完成代码对修改的封闭,扩展的开放。

比如在一个商城项目中, 定义结算接口Istrategy,该接口有三个具体实现类,分别为 PromotionalStrategy (满减活动,两百以上百八折)、RebateStrategy (打折活动)、 ReduceStrategy(返现活动)

代码质量评价及设计原则

public interface Istrategy { public double realPrice(double consumePrice); } public class PromotionalStrategy implements Istrategy { public double realPrice(double consumePrice) { if (consumePrice > 200) { return 200 + (consumePrice - 200) * 0.8; } else { return consumePrice; } } } public class RebateStrategy implements Istrategy { private final double rate; public RebateStrategy() { this.rate = 0.8; } public double realPrice(double consumePrice) { return consumePrice * this.rate; } } public class ReduceStrategy implements Istrategy { public double realPrice(double consumePrice) { if (consumePrice >= 1000) { return consumePrice - 200; } else { return consumePrice; } } } 

 调用方为Context,在此类中使用接口定义了一个对象。

代码质量评价及设计原则

public class Context { //使用基类定义对象变量 private Istrategy strategy; // 注入当前活动使用的具体对象 public void setStrategy(Istrategy strategy) { this.strategy = strategy; } // 计算并返回费用 public double cul(double consumePrice) { // 使用具体商品促销策略获得实际消费金额 double realPrice = this.strategy.realPrice(consumePrice); // 格式化保留小数点后1位,即:精确到角 BigDecimal bd = new BigDecimal(realPrice); bd = bd.setScale(1, BigDecimal.ROUND_DOWN); return bd.doubleValue(); } } 

Context 中代码使用接口定义对象变量,这个对象变量可以是实现了lStrategy接口的PromotionalStrategy、RebateStrategy 、 ReduceStrategy任意一个。

里氏代换原则与多态的区别 ?

虽然从定义描述和代码实现上 来看,多态和里式替换有点类似,但它们关注的角度是不一样的。多态是面向对象编程的一 大特性,也是面向对象编程语言的一种语法。它是一种代码实现的思路。而里式替换是一种 设计原则,用来指导继承关系中子类该如何设计,子类的设计要保证在替换父类的时候,不 改变原有程序的逻辑及不破坏原有程序的正确性。

里氏替换原则和依赖倒置原则,构成了面向接口编程的基础,正因为里氏替换原则,才使得程序呈现多样性。

3.4接口隔离原则

客户端不应该被迫依赖于它不使用的方法.

该原则还有另外一个定义:一个类对另一个类的依赖应该建立在最小的接口上

3.4.1通俗解释

上面两个定义的含义用一句话概括就是:要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。

接口隔离原则与单一职责原则的区别

接口隔离原则和单一职责都是为了提高类的内聚性、降低它们之间的耦合性,体现了封装的思想,但两者是不同的:

3.4.2 场景示例

微服务用户系统提供了一组跟用户相关的 API 给其他系统 使用,比如:注册、登录、获取用户信息等。

代码质量评价及设计原则

public interface UserService { boolean register(String cellphone, String password); boolean login(String cellphone, String password); UserInfo getUserInfoById(long id); UserInfo getUserInfoByCellphone(String cellphone); } public class UserServiceImpl implements UserService { //... }

需求: 后台管理系统要实现删除用户的功能,希望用户系统提供一个删除用户的接口,应该如何设计这个接口(假设这里我们不去考虑使用鉴权框架).

方案1: 直接在UserService接口中添加一个删除用户的接口

代码质量评价及设计原则

public interface UserService { boolean register(String cellphone, String password); boolean login(String cellphone, String password); UserInfo getUserInfoById(long id); UserInfo getUserInfoByCellphone(String cellphone); } public interface RestrictedUserService { boolean deleteUserByCellphone(String cellphone); boolean deleteUserById(long id); } public class UserServiceImpl implements UserService, RestrictedUserService { //... } 

遵循接口隔离原则的优势

3.5依赖倒置原则

依赖倒置原则(Dependence Inversion Principle,DIP)是指在设计代码架构时,高层模块不应该依赖于底层模块,二者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。

3.5.1通俗解释

依赖倒置原则是实现开闭原则的重要途径之一,它降低了客户与实现模块之间的耦合。

传统的自定向下的设计

代码质量评价及设计原则

依赖倒置原则

代码质量评价及设计原则

3.5.2场景示例

假设我们现在要组装一台电脑,需要的配件有 cpu,硬盘,内存条。只有这些配置都有了,计算机才能正常的运行。选择cpu有很多选择,如Intel,AMD等,硬盘可以选择希捷,西数等,内存条可以选择金士顿,海盗船等

代码质量评价及设计原则

希捷硬盘类(XiJieHardDisk):

public class XiJieHardDisk implements HardDisk { public void save(String data) { System.out.println("使用希捷硬盘存储数据" + data); } public String get() { System.out.println("使用希捷希捷硬盘取数据"); return "数据"; } } 

Intel处理器(IntelCpu):

public class IntelCpu implements Cpu { public void run() { System.out.println("使用Intel处理器"); } } 

金士顿内存条(KingstonMemory):

public class KingstonMemory implements Memory {     public void save() {         System.out.println("使用金士顿作为内存条");     } } 

 电脑(Computer):

public class Computer { private XiJieHardDisk hardDisk; private IntelCpu cpu; private KingstonMemory memory; public IntelCpu getCpu() { return cpu; } public void setCpu(IntelCpu cpu) { this.cpu = cpu; } public KingstonMemory getMemory() { return memory; } public void setMemory(KingstonMemory memory) { this.memory = memory; } public XiJieHardDisk getHardDisk() { return hardDisk; } public void setHardDisk(XiJieHardDisk hardDisk) { this.hardDisk = hardDisk; } public void run() { System.out.println("计算机工作"); cpu.run(); memory.save(); String data = hardDisk.get(); System.out.println("从硬盘中获取的数据为:" + data); } } 

测试类用来组装电脑。

public class TestComputer {     public static void main(String[] args) {         Computer computer = new Computer();         computer.setHardDisk(new XiJieHardDisk());         computer.setCpu(new IntelCpu());         computer.setMemory(new KingstonMemory());         computer.run();     } }

上面代码可以看到已经组装了一台电脑,但是似乎组装的电脑的cpu只能是Intel的,内存条只能是金士顿的,硬盘只能是希捷的,这对用户肯定是不友好的,用户有了机箱肯定是想按照自己的喜好,选择自己喜欢的配件。

根据依赖倒转原则进行改进:

代码我们需要修改Computer类,让Computer类依赖抽象(各个配件的接口),而不是依赖于各个组件具体的实现类。

类图如下:

 

代码质量评价及设计原则 

电脑(Computer):

public class Computer { private HardDisk hardDisk; private Cpu cpu; private Memory memory; //getter/setter...... public void run() { System.out.println("计算机工作"); } } 

3.5.3关于依赖倒置、依赖注入、控制反转这三者之间的区别与联系

1 )  依赖倒置原则

依赖倒置是一种通用的软件设计原则, 主要用来指导框架层面的设计。

高层模块不依赖低层模块,它们共同依赖同一个抽象。抽象不要依赖具体实现细节,具体实现细节依赖抽象。

2 ) 控制反转

控制反转与依赖倒置有一些相似, 它也是一种框架设计常用的模式,但并不是具体的方法。

3 ) 依赖注入

依赖注入是实现控制反转的一个手段,它是一种具体的编码技巧。

3.6迪米特法则

迪米特法则(LoD:Law of Demeter)又叫最少知识原则(LKP:Least Knowledge Principle ),指的是一个类/模块对其他的类/模块有越少的了解越好。简言之:talk only to your immediate friends(只跟你最亲密的朋友交谈),不跟陌生人说话。

3.6.1通俗解释

大部分设计原则和思想都非常抽象,有各种各样的解读,要想灵活地应用到 实际的开发中,需要有实战经验的积累。迪米特法则也不例外。

简单来说迪米特法则想要表达的思想就是:  不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。

如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。

3.6.2场景示例

我们一起来看下面这个例子:

明星由于全身心投入艺术,所以许多日常事务由经纪人负责处理,如和粉丝的见面会,和媒体公司的业务洽淡等。这里的经纪人是明星的朋友,而粉丝和媒体公司是陌生人,所以适合使用迪米特法则。

代码质量评价及设计原则

迪米特法则的独特之处在于它简洁而准确的定义,它允许在编写代码时直接应用,几乎自动地应用了适当的封装、高内聚和低耦合。

但是,过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低。所以,在釆用迪米特法则时需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰。

明星类(Star)

public class Star { private String name; public Star(String name) { this.name=name; } public String getName() { return name; } } 

粉丝类(Fans)

public class Fans { private String name; public Fans(String name) { this.name=name; } public String getName() { return name; } } 

媒体公司类(Company)

public class Company { private String name; public Company(String name) { this.name=name; } public String getName() { return name; } } 

经纪人类(Agent)

public class Agent { private Star star; private Fans fans; private Company company; public void setStar(Star star) { this.star = star; } public void setFans(Fans fans) { this.fans = fans; } public void setCompany(Company company) { this.company = company; } public void meeting() { System.out.println(fans.getName() + "与明星" + star.getName() + "见面了。"); } public void business() { System.out.println(company.getName() + "与明星" + star.getName() + "洽淡业务。"); } } 

4.设计原则总结

我们之前给的大家介绍了评判代码质量的标准,比如可读性、可复用性、可扩展性等等,这是从代码的整体质量的角度来评判.

而设计原则就是我们要使用到的更加具体的对于代码进行评判的标准,比如, 我们说这段代码的可扩展性比较差,主要原因是违背了开闭原则。

我们所学习的SOLID 原则它包含了:

4.1重点关注三个常用的原则:

1 ) 单一职责原则

单一职责原则是类职责划分的重要参考依据,是保证代码”高内聚“的有效手段,是我们在进行面向对象设计时的主要指导原则。

单一职责原则的难点在于,对代码职责是否足够单一的判定。这要根据具体的场景来具体分析。同一个类的设计,在不同的场景下,对职责是否单一的判定,可能是不同的。

2 ) 开闭原则

开闭原则是保证代码可扩展性的重要指导原则,是对代码扩展性的具体解读。很多设计模式诞生的初衷都是为了提高代码的扩展性,都是以满足开闭原则为设计目的的。

开闭原则是所有设计模式的最核心目标,也是最难实现的目标,但是所有的软件设计模式都应该以开闭原则当作标准,才能使软件更加的稳定和健壮。

3 ) 依赖倒置原则

依赖倒置原则主要用来指导框架层面的设计。高层模块不依赖低层模块,它们共同依赖同一个抽象。

依赖倒置原则其实也是实现开闭原则的重要途径之一,它降低了类之间的耦合,提高了系统的稳定性和可维护性,同时这样的代码一般更易读,且便于传承。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/118010.html

(0)
上一篇 2025-11-16 14:33
下一篇 2025-11-16 15:00

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信