设计模式学习笔记 - 设计原则与思想总结:1.总结回顾面向对象、设计原则、编程规范、重构技巧等知识点

概述

对前面的内容的回顾,温故而知新,包括:面向对象、设计原则、规范与重构三个模块的内容。

在这里插入图片描述


1.代码质量评判标准

如何评价代码质量的高低?

代码质量的评价具有很强的主观性,描述代码质量的词汇也有很多,比如可读性、可维护性、灵活、优雅、简洁。这些词汇是从不同的维度去评价代码质量的。它们之间有相互作用,并不是独立的,比如,代码的可读性好、可扩展性好就意味着代码的可维护性好。代码质量高低是一个综合各种因素得到的结论。我们并不能通过单一维度去评价一段代码的好坏。

最常用的评价标准有哪几个?

最常用到几个评判代码质量的标准有:可维护性、可读性、可扩展性、灵活性、简洁性、可复用性、可测试性。其中,可维护性、可读性、可扩展性又是提到最多、最重要的三个评价标准。

如何才能写出高质量的代码?

要写出高质量的代码,我们就需要掌握一些更加细化、更加能落地的编程方法论,这就包含面向对象设计思想、设计原则、设计模式、编码规范、重构技巧等。

面向对象

1.面向对象概述

现在,主流的编程范式或者编程风格有三种,它们分别是面向过程、面向对象和函数式编程。面向对象这种编程风格又是这其中最主流的。现在比较流行的编程语言大部分是面向对象编程语言。大部分项目也是基于面向对象编程风格开发的。面向对象编程因为其具有丰富的特性(封装、抽象、继承、多态),可以实现很多复杂的设计思路,是很多设计原则、设计模式编码实现的基础。

2.面向对象四大特性

封装也叫作信息隐藏和数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方法来访问内部信息或者数据。它需要编程语言提供权限访问控制语法来支持,例如 Java 中的 private、protected、public 关键字。封装特性存在的意义,一方面是保护数据不被随意修改,提高代码的可维护性;另一方面是仅暴露有效的必要接口,提高类的易用性。

如果说封装是讲的是如何隐藏信息,那抽象就是将如何隐藏方法的具体实现,让使用者只需要关心方法提供了哪些功能,不需要知道这些功能是如何实现的。抽象可以通过接口或者抽象类来实现。抽象存在的意义,一方面是修改实现类不需要改变定义;另一方面,它也是处理复杂系统的有效手段,能有效地过滤不必要关注的信息。

继承用来表示 is-a 的关系,分为两种模式:单根继承和多继承。单继承表示一个子类只能继承一个父类,多继承表示一个子类可以继承多个父类。为了实现继承这个特性,编程语言需要提供特殊的语法机制来支持。继承主要用来解决代码复用的问题。

多态是指子类替换父类,在实际的代码运行过程中,调用子类的方法实现。多态这种特性也需要编程语言提供特殊的语法机制来实现,比如继承、接口类、duck-typing。多态可以提高代码的可扩展性和复用性,是很多设计模式、设计原则、编程技巧大代码实现基础。

3.面向对象VS面向过程

面向对象相比面向过程编程的优势:

  • 对于大规模复杂程序的开发,程序处理流程并非单一的一条主线,而是错综复杂的网状结构。面向对象编程比起面向过程编程,更能应对这种复杂类型的程序开发。
  • 面向对象编程相比面向过程编程,具有更加丰富的特性(封装、抽象、继承、多态)。利用这些特性编写出来的代码,更加易扩展、易维护、易复用。
  • 从编程语言跟机器打交道方式的演进规律中,可以总结出:面向对象编程比起面向过程编程,更加人性化、更加高级、更加智能。

面向对象编程一般使用面向对象编程语言来进行,但是不用面向对象编程语言,我们照样可以进行面向对象编程。反过来讲,即便我们使用面向对象编程语言,写出来的代码也不一定是面向对象编程风格的,也可能是面向过程编程风格的。

面向对象和面向过程两种编程风格不是完全对立的。在用面向对象编程语言开发的软件中,面向过程风格的代码并不少见,甚至在一些标准的开发库(如 JDK、Apache Commons、Google Guava)中,也有很多面向过程风格的代码。

不管使用面向过程还是面向对象来写代码,最终的目的还是写出易维护、易读、易复用、易扩展的高质量代码。只要我们能避免面向过程编程风格的一些弊端,控制好它的副作用,在掌控范围内为我们所用,我们就大可不用避讳在面向对象编程中书写面向过程风格的代码。

4.面向对象分析、设计与编程

面向对象分析(OOA)、面向对象设计(OOD)、面向对象编程(OOP),是面向对象开发的三个主要环节。

  • 面向对象分析就是要搞清楚做什么
  • 面向对象设计就是要搞清楚怎么做
  • 面向对象编程就是将分析和设计的结果翻译成代码的过程

需求分析的过程实际上是一个不断迭代优化的过程。我们不要视图一下就给出一个完美的解决方案,而是先给出一个粗糙的、基础的方案,有一个迭代的基础,然后再慢慢优化。这样一个思考过程能让我们摆脱无从下手的窘境。

面向对象设计和实现要做的事情就是把合适的代码放到合适的类中。至于到底选择哪种划分方法,判定的标准是让代码尽量满足 “松耦合、高内聚”、单一职责、对扩展开发对修改关闭等我们之前讲到的各种设计思想和原则,尽量地做到代码可复用、易读、易扩展、易维护。

面向对象分析的产出是详细的需求描述。面向对象设计的产出是类。在面向对象设计这一环节,我们将需求转化为具体的类的设计。这个环节的工作可以拆分为下面四个部分。

  • 划分职责进而识别出有哪些类。
    根据需求描述,我们把其中涉及的功能点,一个个罗列出来,然后再去看哪些功能点职责相近,操作同样的属性,可否归为同一个类。
  • 定义类的属性和方法。
    识别出需求描述中的动词,作为候选方法,再进一步过滤筛选出真正的方法。把功能点涉及的名词作为候选属性,然后再同样进行过滤筛选。
  • 定义类与类之间的交互关系
    UML 统一建模语言定义了六种类之间的关系。分别是:泛华、实现、关联、组合、聚合、依赖。我们从更加贴近编程的角度,对类与类之间的关系做了调整,保留了四个关系:泛华、实现、组合、依赖。
  • 将类组装起来并提供执行入口
    将所有的类组装在一起,提供一个执行入口。这个入口可能是一个 main() 函数,也可能是一个组合外部用的 API 接口。通过这个入口,可以出发整个代码跑起来。

5.接口VS抽象类

抽象类不允许被实例化,只能被继承。它可以包含属性和方法。方法既可以包含代码实现,也可以不包含代码实现。不包含代码实现的方法叫做抽象方法。子类继承父类必须实现抽象类中的所有抽象方法。

接口不能包含属性(Java 可以定义静态常量),只能申明方法,方法不能包含代码实现(Java8 以后可以有默认实现)。类实现接口的时候,必须实现接口中申明的所有方法。

抽象类是对成员变量和方法的抽象,是一种 is-a 的关系,是为了解决代码复用问题。接口仅仅是对方法的抽象,是一种 has-a 的关系,表示具有一组行为特性,是为了解决解耦问题,隔离接口和具体的实现,提高代码的扩展性。

什么实时使用抽象类?什么时候使用接口?

实际上,判断的标准很简单。如果要表示 is-a 的关系,并且是为了解决代码复用问题,就用抽象类。如果要表示一种 has-a 的关系,并且是为了解决抽象而非代码复用的问题,那我们就用接口。

6.基于接口而非实现编程

应用这条原则,可以将接口和实现相分离,封装不稳定的实现,暴露稳定的接口。上游系统面向接口而非实现编程,不依赖不稳定的实现细节,这样当实现发生变化的时候,上游系统的代码基本上不需要做改动,以此来降低耦合性,提高扩展性。

实际上,“基于接口而非实现编程” 这条原则的另一个表述方式是,“基于抽象而非实现编程”。后者的表述方式其实更能体现这条设计原则的设计初衷。在软件开发中,最大的挑战之一就是需求的不断变化,这也是考验代码设计好坏的一个标准。

越抽象、越顶层、越脱离具体某一实现的设计,越能提高代码的灵活性,越能应对未来的需求变化。好的代码设计,不能能应对当下的需求,而且在将来需求发生变化的时候,仍然能在不破坏原有代码设计的情况下灵活应对。而抽象就是提供代码扩展性、灵活性、可维护性最有效的手段之一。

7.多用组合少用继承

为什么不推荐使用继承

继承是面向对象的四大特性之一,用来表示 is-a 关系,可以解决代码复用的问题。虽然继承有诸多作用,但继承层次过深、过复杂,也会影响到代码的可维护性。在这种情况下,我们应该尽量少用,甚至不用继承。

组合相比继承有哪些优势?

继承的主要作用有三个:表示 is-a 关系、支持多态特性、代码复用。而这三个作用都可以通过组合、接口、委托三个技术手段来达成。此外,利用组合还能解决层次过深、过复杂的继承关系影响代码可维护性的问题。

如何判断该用组合还是继承?

尽管我们鼓励多用组合少用继承,但组合也并不是完美的,继承也并非一无是处。在实际的项目开发中,我们还是要根据具体的情况,来选择该用继承还是组合。

  • 如果类之间的结构稳定,层次比较浅,关系不复杂,我们就可以大胆地使用继承。
  • 反之,我们就尽量使用组合来替代继承。
  • 还有一些设计模式、特殊的应用常见,会固定使用组合或者继承。

8.贫血模型 VS 充血模型

我们平时做 WEB 项目的业务,大部分都是基于贫血模型的 MVC 三层架构,被称为传统开发模式。之所以称之为 “传统”,是相对于新兴的基于充血模型的 DDD 开发模式来说的。基于贫血模型的传统开发模式,是典型的面向过程的编程风格。相反,基于充血模型的 DDD 开发模式,是典型的面向对象的编程风格。

不过,DDD 也并非银弹。对于业务不复杂的系统开发来说,基于贫血模型的传统开发模式简单够用,基于充血模型的 DDD 开发模式有点大材小用,无法发挥作用。相反,对于业务负责的系统开发来说,基于充血模型的 DDD 开发模式,因为前期需要在设计上投入更多的时间和经历,来提高代码的复用性和可维护性,所以相比于基于贫血模型的开发模式,更加有优势。

基于充血模型的 DDD 开发模式跟基于贫血模型的传统开发模式相比,主要区别在 Service 层。在基于充血模型的开发模式下,我们将部分原来在 Service 类中的业务逻辑移动到了一个充血的 domain 领域模型中,让 Service 类的实现依赖这个 domain 类。不过,Service 类并不会完全移除,而是复杂一些不适合放在 domain 领域模型类中的功能。比如,负责与 Repository 层打交道、跨领域模型的业务聚合功能、幂等事务等非功能性的工作。

基于充血模型的 DDD 开发模式跟基于贫血模型的传统开发模式相比,Controller 层和 Repository 层的代码基本上相同。这是因为,Repository 层的 Entity 生命周期有限,Controller 层的 VO 只是单纯作为一种 DTO。两部分的业务逻辑都不会太复杂。业务逻辑主要集中在 Service 层。所以,Controller 层和 Repository 层继续沿用贫血模型的设计思路是没有问题的。

设计原则

1.SOLID 原则:SRP 单一职责原则

一个类只负责完成一个职责或者功能。单一职责原则通过避免设计大而全的类,避免将不相关的功能耦合在一起,来提高类的内聚性。同时,类职责单一,类依赖的和被依赖的其他类也会变少,减少了代码的耦合性,以此来实现代码的高内聚、松耦合。但是,如果拆分得过细,实际上会适得其反,反倒会降低内聚性,也会影响代码的可维护性。

不同的应用常见、不同阶段的需求背景、不同的业务层面,对同一个类的职责是否单一,可能会有不同的判定结果。实际上,一些侧面的判断指标更具有指导意义和可执行性,比如,出现下面这些情况就有可能说明类的设计不满足单一职责原则:

  • 类中的代码行数、函数或者属性过多;
  • 类依赖的其他类过多,或者依赖类的其他类过多;
  • 私有方法过多;
  • 比较难给类起一个合适的名字;
  • 类中大量的方法都是集中操作类中的几个属性。

2.SOLID 原则:OCP 开闭原则

如何理解 “对扩展开发、对修改关闭”?

添加一个新的功能,应该是通过在已有代码基础上扩展代码(新增模块、类、方法、属性等),而非修改代码已有代码(修改模块、类、方法、属性等)的方式来完成。关于定义,我们有两点要注意。

  • 第一点是,开闭原则并不是说完全杜绝修改,而是以最小的修改代码的代价来完成新功能的开发。
  • 第二点是,同样代码的改动,在粗代码粒度下,可能被认为是 “修改”;在细代码粒度下,可能有被认为 “扩展”;

如何做到 “对扩展开发、对修改关闭”?

我们要时刻具备扩展意识、抽象意识、封装意识。在写代码时,我们要多花点时间思考下,这段代码未来可能有哪些需求变更,如何设计代码结构,事先留好扩展点,以便在未来需求变更时,在不改动代码整体结构、做到最小代码改动的情况下,将新的代码灵活地插入到扩展点上。

很多设计原则、设计思想、设计模式,都是以提高代码的扩展性为最终目的。特别是 23 种经典设计模式,大部分都是为了解决代码的扩展性问题而总结出来的,都是以开闭原则为指导原则的。最常用来提高代码可扩展性的方法有:多态、依赖注入、基于接口而非实现编程,以及大部分的设计模式(比如,装饰、策略、模板、职责链、状态)。

3.SOLID 原则:LSP 里氏替换原则

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

里氏替换原则是用来指导继承关系中子类该如何设计的一个原则。理解里氏替换原则,最核心的就是理解 “design by contract,按照协议来设计” 这几个字。父类定义了函数的 “约定”,子类可以改变函数的内部实现逻辑,但不能改变函数的原有 “约定” 包括:函数声明要实现的功能;对输入、输出、异常的约定;甚至包括注释中所罗列的任何特殊说明。

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

4.SOLID 原则:ISP 接口隔离原则

接口隔离原则的描述是:客户端不应该强迫依赖它不需要的接口。其中的 “客户端”,可以理解为接口的调用者。理解 “接口隔离原则” 的重点是理解其中的 “接口” 二字。这里有三种不同的理解。

如果把 “接口” 理解为一组集合,可以是某个微服务的接口,也可以是某个类库的接口等。如果部分接口只被部分调用者使用,我们就需要将这部分接口隔离出来,单独给这部分调用者使用,而不强迫其他调用者也依赖这部分不会被用到的接口。

如果把 “接口” 理解为单个 API 接口或函数,部分调用者只需要函数中的部分功能,那我们就需要把函数拆分成粒度更细的多个函数,让调用者只依赖它需要的那个细粒度函数。

如果把 “接口” 理解为 OOP 中的接口,也可以理解为面向对象编程语言中的接口语法。那接口的设计要尽量单一,不要让接口的实现类和调用者,依赖不需要的接口函数。

5.SOLID 原则:DIP 依赖倒置原则

**控制反转:**实际上,控制反转是一个比较笼统的设计思想,并不是一种具体的实现方法,一般用来指导框架层面的设计。这里所说的 “控制” 指的是对执行流程的控制,而 “反转” 指的是在没有使用框架之前,程序员自己控制整个程序的执行。在使用框架之后,整个程序的执行流程通过框架来控制。流程的控制权从程序员 “反转” 给了框架。

**依赖注入:**依赖注入和控制反转恰恰相反,它是一种具体的编程技巧。我们不通过 new 的方式在类内部创建依赖的对象,而是将依赖类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或 “注入”)给类来使用。

**依赖注入框架:**通过依赖注入框架提供的扩展点,简单配置一下所需要的类及其类之间的依赖关系,就可以实现由框架来自动创建对象、管理对象生命周期、依赖注入等原本需要程序员来做的事情。。

**依赖反转原则:**也叫做依赖倒置原则。这条原则跟控制反转有点类似,主要用来指导框架层面的设计。高层模块不依赖低层每块,它们依赖一个共同抽象。抽象不需要依赖具体实现细节,具体实现细节依赖抽象。

6.KISS、YAGNI 原则

KISS 原则的中文描述是:尽量保持简单。KISS 原则是保持代码可读性和可维护性的重要手段。KISS 原则中的 “简单” 并不是以代码行数来考量的。代码行数越少并不是代表代码越简单,我们还要考虑逻辑复杂度、实现难度、代码的可读性等。而且,本身就复杂的问题,用复杂的方法解决,也并不违背 KISS 原则。此外,同样的代码,在某个业务场景下不满足 KISS 原则,换一个应用场景可能就不满足了。

对于如何写出满足 KISS 原则的代码,总结了下面几条指导原则:

  • 不要使用同事坑你不懂的技术来实现代码;
  • 不要重复造轮子,善于使用已经有的工具类库;
  • 不要过度优化;

YAGNI 原则的英文全称是:You Ain’t Gonna Need It。直译就是:你不需要它。这条原则也算是万金油了。当用在软件开发中时,它的意思是:不要去设计用不到的功能,不要是编写当前用不到的代码。实际上,这条原则的核心思想是:不要过度设计

YAGNI 原则跟 KISS 原则并非一回事儿。KISS 原则讲的是 “如何做” 的问题(尽量保持简单),而 YAGNI 原则说的是 “要不要做” 的问题(当前不需要的就不要做)。

7.DRY原则

DRY 原则中文描述是:不要重复自己,将它应用在编程中,可以理解为:不要写重复的代码。

有三种代码重复的情况:实现逻辑重复、功能语义重复、代码执行重复。实现逻辑重复,但语义功能不重复的代码,并不违反 DRY 原则。实现逻辑不重复,但功能语义重复的代码,也算违反 DRY 原则。而执行代码重复的代码,也算违反 DRY 原则。

此外,我们还降到了提高代码复用性的一些手段,包括:减少代码耦合、满足单一职责原则、模块化、业务与非业务逻辑分离、通用代码下沉、继承、多态、封装、抽象、应用模板等设计模式。

复用意识也非常重要。在设计每个模块、类、函数的时候,要像设计一个外部 API 一样去思考它的复用性。

我们在第一次写代码的时候,如果当下没有复用的需求,而未来的复用需求也不是特别明确,并且开发可复用代码的成本比较高,那我们就不需要考虑代码的复用性。在之后开发新的功能的时候,发现可以复用之前写的这段代码,那我们就重构这段代码,让其变得可复用。

相比于代码的复用,DRY 原则适用性更强一些。我们可以不写可复用的代码,但一定不能写重复的代码、

8.LOD原则

如何理解 “高内聚、松耦合”?

“高内聚、松耦合” 是一个非常重要的设计思想,能够有效提高代码的可读性和可维护性,缩小功能改动导致的代码改动范围。“高内聚” 用来指导类与类之间依赖关系的设计。所谓高内聚,就是指相近的功能应该放到同一个类中,不相近的功能不要放到同一个类中。相近的功能往往会被同时修改,放到同一个类中,修改会比较集中。所谓 “松耦合” 指的是,在代码中,类与类之间的依赖关系简单清晰。即使两个类有依赖关系,一个代码的改动也不会或很少导致依赖类的代码改动。

如何理解 “迪米特法则”?

迪米特法则的描述为:不该有直接依赖关系的类,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。迪米特法则是希望较少类之间的耦合,让类越独立越好。每个类都应该少了解系统的其他部分。一旦发生变化,需要了解这一变化的类就会比较少。

规范与重构

1.重构概述

重构的目的:为什么重构(why)?

对于项目来说,重构可以保持代码质量持续处于一个可控状态,不至于腐化到无可救药的地步。对于个人而言,重构非常锻炼一个人的代码能力,并且是一件非常有成就感的事情。它是我们学习经典设计思想、原则、模式、编程规范等理论知识的练兵场。

重构的对象:重构什么(what)?

按照重构的规模,可以将重构分为大规模高层次的重构和小规模低层次的重构。

  • 大规模高层次重构包括对代码分层、模块化、解耦、梳理类之间的交互关系、抽象复用组件等等。这部分工作利用的更多的是比较抽象、比较顶层的设计思想、原则、模式。
  • 小规模低层次重构包括命名规范、注释、修正函数参数过多、消除超大类、提取重复代码等编程细节,主要是针对类、函数级别的重构。小规模低层次重构更多的是利用编码规范这一理论知识。

重构的时机:什么时候重构(when)?

一定要建立持续重构的意识,把重构作为开发不可少的部分融入到开发中,而不是等到代码出现很大问题的时候,再大刀阔斧地重构。

重构的方法:如何重构(how)?

大规模高层次重构难度比较大,需要有组织、有计划地进行,分阶段小步快跑,时刻保持代码处于一个可运行的状态。

小规模低层次重构,因为影响范围小,改动耗时短,所以,只要你愿意并且有时间,随时随地都可以去做。

2.单元测试

什么是单元测试?

单元测试是代码层面的测试,用于测试 “自己” 编写的代码的逻辑正确性。单元测试顾名思义是测试一个 “单元”,这个 “单元” 一般是类或函数,而不是系统或模块。

为什么要写单元测试?

单元测试能有效地发现代码中的 bug、代码设计上的问题。写单元测试的过程本身就是代码重构的过程。单元测试是对集成测试的有力补充,能帮助我们快速熟悉代码,是 TDD 可落地执行的折中方案。

如何编写单元测试?

写单元测试就是针对代码设计覆盖各种输入、异常、边界条件的测试用例,并将其翻译成代码的过程。可以利用一些测试框架来简化测试代码的编写。对于单元测试,我们需要建立以下正确的认知:

  • 编写单元测试尽管繁琐,但并不是太耗时;
  • 可以稍微放低单元测试的质量要求;
  • 覆盖率作为衡量单元测试好坏的唯一标准是不合理的;
  • 写单元测试一般不需要了解代码的实现逻辑;
  • 单元测试框架无法测试多半是代码的可测试性不好。

单元测试为何难落地执行?

  • 一方面,写单元测试本身比较繁琐,技术挑战不大,很多程序员不愿意去写。
  • 另一方面,国内研发比较偏向 “快、糙、猛”,容易因为开发进度紧,导致单元测试的执行虎头蛇尾,最后,没有建立单元测试的正确认识,觉得可有可无,单靠督促很难执行得很好。

3.代码的可测试性

什么是代码的可测试性

粗略的讲,所谓的代码的可测试性,就是针对代码编写单元测试的难易程度。

对于一段代码,如果很难为其编写单元测试,或者单元测试写起来很费劲,需要依靠单元测试框架很高级的特性,那往往就意味着代码设计得不够合理,代码的可测试性不好。

编写可测试性代码的最有效手段

依赖注入是编写可测试性代码的最有效手段。通过依赖注入,我们在编写单元测试代码的时候,可以通过 mock 的方法将不可控的依赖变得可控,这也是我们在编写单元测试的过程中最有技术挑战的地方。除了 mock 方式,我们还可以利用二次封装来解决某些代码行为不可控的情况。

场景的 Anti-Patterns

典型的、常见的测试不友好的代码有下面这 5 种:

  • 代码中包含未决行为逻辑;
  • 滥用全局变量;
  • 滥用静态方法;
  • 使用复杂的继承关系;
  • 高度耦合的代码。

4.大型重构:解耦

“解耦” 为何如此重要?

过于复杂的代码往往在可读性、可维护性上都不友好。解耦,保证代码松耦合、高内聚,是控制代码复杂度的有效手段。如果代码松耦合、高内聚,也就意味着,代码结构清晰、分层、模块化合理、依赖关系简单、模块或类之间的耦合小,那代码整体的质量就不会差。

代码是否需要 “解耦” ?

间接的衡量标准有很多,比如:

  • 改动一个模块或类的代码,受影响的模块或类是否有很多;
  • 改动一个模块或类的代码,依赖的模块或者类是否需要改动;
  • 代码的可测试性是否好…

直接的衡量标准是把模块与模块之间,及其类与类之间的依赖关系画出来,根据依赖关系图的复杂性来判断是否需要解耦重构。

如何给代码 “解耦”?

给代码解耦的方法有:封装与抽象、中间层、模块化,以及一些其他的设计思想与原则,比如:单一职责原则、基于接口而非实现编程、依赖注入、多用组合少用继承、迪米特法则。当然还有一些设计模式,比如观察者模式。

5.小型重构:编码规范

前面讲了很多设计原则,后面还会讲到设计模式,利用好它们都可以有效地改善改代码的质量。但是,这些知识的合理应用非常依赖个人经验,有时候用不好会适得其反。但是编码规范正好相反,大部分都简单明了,在代码的细节方面,能立竿见影地改善质量。此外,前面也讲到,持续低层次小规模重构依赖的基本上都是这些编程规范,也是改善代码可读性的有效手段。

我总结罗列了 20 条编码规范,分为三个大类:命名与注释、代码风格、编程技巧。

命名与注释

  • 命名的关键是能准确的达意。对于不同作用域的命名,可以适当的选择不同的长度,作用域小的命名,比如临时变量等,可以适当的选择短一些的命名方式。此外,命名中也可以使用耳熟能详的缩写。
  • 借助类的信息来简化属性、函数的命名,利用函数的信息来简化函数参数的命名。
  • 命名要可读、可搜索。不要使用生僻的、不好读的英文单词来命名。此外,命名要符合项目的统一规范,也不要用那些反直觉的命名。
  • 接口有两种命名方式。一种是在接口中带前缀 “I”,另一种是在接口的实现类中带后缀 “Impl”。两种命名方式都可以,关键要在项目中统一。对于抽象类的命名,我们更倾向于带有前缀 “Abstract”。
  • 注释的目的就是让代码更容易看懂,只要符合这个 要求,你就可以写。总结以下的话,注释主要包含这样三个方面的内容:做什么、为什么、怎么做。对于一些复杂的类和接口,我们可能还需要写明“如何用”。
  • 注释本身有一定的维护成本,所以并非越多越好。类和函数一定要写注释,而且要写的尽可能全面详细些,而函数内部的注释会相对少一些,一般都是靠好的命名和提炼函数、解释性变量、总结性注释来做到代码易读。

代码风格

代码风格没有对错之分,不同的编程语言风格都不太一样,只要能在团队、项目中统一即可,不过,最好能跟业务推荐的风格、开源项目的代码风格一致。所以,这里就不展开罗列了,你可以对照这自己属性的编程语言的代码风格,自己复习以下。

编程技巧

  • 将复杂的逻辑提炼拆分成函数和类;
  • 通过拆分多个函数的方式来处理参数过多的情况;
  • 通过参数封装为对象来处理参数过多的情况;
  • 函数中不要使用参数来做代码执行逻辑的控制;
  • 移除过深的嵌套层次,方法包括:去掉多余的 if 或 else 语句,使用 continue、break、return 关键字提前退出嵌套,调整执行顺序来减少嵌套,将部分嵌套逻辑抽象成函数;
  • 用字面常量取代魔法值;
  • 利用解释下变量来解释复杂表达式。

统一编码规范

除了细节的知识点外,最后,还有一条非常重要的,那就是,项目、团队,甚至公司,一定要制定统一的编码规范,并且通过 Code Review 督促执行,这对提供代码质量有立杆见影的效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2871731.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

瑞_Redis_短信登录_Redis代替session的业务流程

文章目录 项目介绍1 短信登录1.1 项目准备1.2 基于Session实现登录流程1.3 Redis代替session的业务流程1.3.1 设计key的结构1.3.2 设计key的具体细节1.3.3 整体访问流程1.3.4 代码实现 🙊 前言:本文章为瑞_系列专栏之《Redis》的实战篇的短信登录章节的R…

uni-app发布 h5 与ASP .NET MVC 后台 发布 到 IIS的同一端口 并配置跨域

iis 安装URL重写 选择对应的后台项目,进行url重写 编辑【模式】部分的内容的重写规则,我这里是h5中请求的前缀是api,大家可以根据自己的前缀进行修改。 编写【操作类型】为重写,并写重写url,按照图中设置即可。 uni…

R语言深度学习-6-模型优化与调试

本教程参考《RDeepLearningEssential》 这是本专栏的最后一篇文章,一路走来,大家应该都可以独立的建立一个自己的神经网络进行特征学习和预测了吧! 6.1 缺失值处理 在我们使用大量数据进行建模的时候,缺失值对模型表现的影响非常…

研究生总结

Note:本博客更多是关于自己的感悟,没有翻阅文件详细查证,如果存在错过,也请提出指正。 1. 半监督回归 相比于半监督分类,半监督回归相对冷门。回归和分类之间有着难以逾越的天谴,预测精度。分类中的类别是可数的&…

Flutter开发进阶之使用工具效率开发

Flutter开发进阶之使用工具效率开发 软件开发团队使用Flutter开发的原因通常是因为Flutter开发性能高、效率高、兼容性好、可拓展性高,作为软件PM来说主要考虑的是范围管理、进度管理、成本管理、资源管理、质量管理、风险管理和沟通管理等,可以看到Flu…

Zookeeper的ZAB协议原理详解

Zookeeper的ZAB协议原理详解 如何保证数据一致性。 Paxos, 吸收了主从。 zk 数据模型Watch机制 zab zookeeper原子广播协议。 ZAB概念 ZooKeeper是通过Zab协议来保证分布式事务的最终一致性。 Zab(ZooKeeper Atomic Broadcast,.ZooKeeper原子广播协议)支持…

Java SE 认识异常 (Java SE完结篇)

1. 异常的概念与体系结构 1.1 异常的概念 在我们的生活中,一个人如果表情痛苦,我们可能会问: 你是生病了吗? 需要我陪你去看医生吗? 程序也和人是一样的,均会发生一些"生病"的行为,比如: 数据格式不对, 数组越界,网络中断等, 我们把这种程序出现的"生病&qu…

Ps:文字工具

工具箱里的文字工具组中包含了四种工具: 横排文字工具 Horizontal Type Tool 直排文字工具 Vertical Type Tool 横排文字蒙版工具 Horizontal Type Mask Tool 直排文字蒙版工具 Vertical Type Mask Tool 快捷键:T 横排文字蒙版工具和直排文字蒙版工具…

iOS--第二章block

第二章block blocks 概要Blocks模式Blocks语法Blocks类型变量截获自动变量值_block 说明符 Blocks的实现Block的实质截获自动变量值_block说明符Block存储域 blocks 概要 Blocks是c语言的扩展,block是一个带有自动变量值的匿名函数,它也是一个数据类型&…

Lua面向对象

封装 实现了New方法,相当于创建了一个表obj,并设置元表可以通过obj表去访问__index指向表中的数据 继承 通过大G表传入字符串创建表,

安卓国产百度网盘与国外云盘软件onedrive对比

我更愿意使用国外软件公司的产品,而不是使用国内百度等制作的流氓软件。使用这些国产软件让我不放心,他们占用我的设备大量空间,在我的设备上推送运行各种无用的垃圾功能。瞒着我,做一些我不知道的事情。 百度网盘安装包大小&…

golang常用库之-golang常用库之-ladon包 | 基于策略的访问控制

文章目录 golang常用库之-ladon包 | 基于策略的访问控制概念使用策略 条件 Conditions自定义conditionLadon Condition使用示例 持久化访问控制(Warden) 结合 Gin 开发一个简易 ACL 接口参考 golang常用库之-ladon包 | 基于策略的访问控制 https://github.com/ory/ladon Lado…

Unity中UGUI中的PSD导入工具的原理和作用

先说一下PSD导入工具的作用,比如在和美术同事合作开发一个背包UI业务系统时,美术做好效果图后,程序在UGUI中制作好界面,美术说这个图差了2像素,那个图位置不对差了1像素,另外一个图大小不对等等一系列零碎的…

从零自制docker-4-【PID Namespace MOUNT Namespace】

文章目录 PID namespace代码mountnamespace通俗理解代码 PID namespace 每个命名空间都有独立的PID空间,即每个命名空间的进程都由一开始分配。 新建立的进程内部进程ID为1 代码 package main import ("log""os/exec""os""sy…

MySQL_数据库图形化界面软件_00000_00001

目录 NavicatSQLyogDBeaverMySQL Workbench可能出现的问题 Navicat 官网地址: 英文:https://www.navicat.com 中文:https://www.navicat.com.cn SQLyog 官网地址: 英文:https://webyog.com DBeaver 官网地址&…

Spring MVC文件上传配置

版权声明 本文原创作者:谷哥的小弟作者博客地址:http://blog.csdn.net/lfdfhl 文件上传 Spring MVC文件上传基于Servlet 3.0实现;示例代码如下: Overrideprotected void customizeRegistration(ServletRegistration.Dynamic reg…

【开源鸿蒙】编译OpenHarmony轻量系统QEMU RISC-V版

文章目录 一、背景介绍二、准备OpenHarmony源代码三、准备hb命令3.1 安装hb命令3.2 检查hb命令 四、编译RISC-V架构的OpenHarmony轻量系统4.1 设置hb构建目标4.2 启动hb构建过程 五、问题解决5.1 hb set 报错问题解决 六、参考链接 开源鸿蒙坚果派,学习鸿蒙一起来&a…

AJAX概念和axios使用、URL、请求方法和数据提交、HTTP协议、接口、form-serialize插件

AJAX概念和axios使用 AJAX概念 AJAX就是使用XMLHttpRequest对象与服务器通信,它可以使用JSON、XML、HTML和text文本等格式发送和接收数据,AJAX最吸引人的就是它的异步特性,也就是说它可以在不重新刷新页面的情况下与服务器通信,…

GPU密集型计算性能优化的方法和技术

对GPU密集型计算进行性能优化的方法和技术多种多样。通过一些优化策略和技术需要综合考虑应用程序的具体需求、所使用的GPU硬件、以及编程模型和库的选择。通过不断地分析和调整,可以实现GPU计算性能的持续提升。以下是一些常用的优化策略和技术: 算法优…

uni-popup(实现自定义弹窗提示、交互)

一般提示框的样式,一般由设计稿而定,如果用uniapp的showmodel,那个并不能满足我们需要的自定义样式,所以最好的方式是我们自己封装一个!(想什么样就什么样)! 一、页面效果 二、使用…