前言

本文中会介绍一些笔者在编程中所使用过的一些思路,技巧等。这些设计思路,实现技巧能够或许能够帮助写出大家可读性高、扩展性强和规范性好的代码。笔者认为,好的代码不是故意使用高级语法编写来让别人看不懂;然而恰恰相反,好的代码应该是易读的,并且让其他人好接手的;能够以最小的改动代价,快速的扩展去响应新需求;好代码的头衔不是自封的,而是其他程序员所给予的。 文章可能会随时更新,毕竟笔者自身也还需要进行对编程持续的学习和实践。若文中有错误的概念和逻辑,希望大家及时纠正。


(目录)


1.代码块与需求功能点一一对应

例如当前需求有4个功能点,得有4个独立的代码块与之对应。这样做的好处是:当需求发生变化时,代码的修改也相对集中。


2.编写之前梳理清楚代码结构

即写之前确定好逻辑。逻辑确定了,那么代码的结构就能够相应的确定了(即串行的顺序)。

Tips: 画流程图是一个不错的习惯。


3.对相似逻辑的代码块进行抽象

抽象的实现有两种方式:

抽象成函数: 一般遇到相同/相似代码时,可以抽象出一个函数.相同的部分写在函数体中,不同的部分作为参数传入。

抽象成对象:如果业务需求过于复杂时,可以将需求拆分为一个整体对象和多个部分对象,部分对象依赖于整体对象。这样的好处在于能够避免单个对象中存在太多的不相关的逻辑;同时能够保证一个对象内的功能保持单一性,不会关联其他无用的功能。同时对象之间是弱耦合,便于代码的扩展和修改。


4.使用面向对象设计时需要遵循的原则

面向对象风格侧重于代码的组织形式:把数据和操作数据的函数组织在类中,提高内聚;对象之间通过调用开放的接口通讯,降低耦合。

使用面向对象设计时需要遵循的几个原则:

  1. 单一职责原则 单一职责原则的核心是解耦和增强内聚力;单一职责原则实际上是把一件事拆分成多个步骤,代码修改造成的影响范围很小。

    实现方式:可通过把原来放在一个函数中实现的多个步骤拆分成为多个更小的函数,每个函数只做一件事来实现;当数据源发生变化时,只需要改相关的代码即可。

  2. 开放封闭原则 开放指的是对扩展开放,封闭指的是对修改封闭.即尽量减少修改原有代码,但可通过扩展的方式来新增功能

    实现方式:可通过使用单一职责原则设计或依赖倒置原则设计来实现

  3. 依赖倒置原则 依赖倒置中的倒置,指的是依赖关系的倒置;之前是调用方依赖于被调用方。依赖倒置中的依赖指的是对象的依赖关系;之前调用方依赖的是实体。

    实现方式:通过使用抽象基类来固定/稳定住被调用方,调用方改为依赖于抽象基类。这样当每次需要新增功能时,只需要继承抽象基类新增一个功能类,并实现其中指定的方法即可;调用方并不需要修改任何代码。同时还可使用依赖注入的方式来设计调用方:被调用方的实例化是在外部,然后从外部传入到调用方内部来进行使用。

    注意:在设计抽象基类时,可使用接口隔离原则设计; 即按照功能需求可将不同的功能封装到多个抽象基类中;实现新功能类时候继承多个抽象基类即可。

  4. 接口隔离原则 调用方不应该依赖它不需要的接口;即封装类时应只把调用方需要的功能封装进去。依赖关系应当建立在最小接口上;即应当将功能拆分到封装到多个类中,实现时不应继承全部类,而是只继承需要的类即可。

    实现方式: 封装类时要考虑调用方需要哪些功能;暂时是不用上但后续可能会用到的功能可以封装到其他的类中;后续若需要使用继承该类即可。

  5. 合成复用原则 尽量使用对象组合,而不是继承来达到复用的目的。组合即类调用类,而非类继承类。调用实现的方式可以灵活运用;例如依赖注入等。合成采用另一种方式实现对象之间的关联,降低依赖关系。

    实现方式: 在类中实例化一个类对象,然后在需要的时候调用它。

  6. 最小惊讶原则 不管你的代码有“多好”,如果大部分人都对此感到吃惊,或许我们应该重新设计它。即如果有大部分人都认为某一段代码有问题,则应该考虑重新设计那段代码的逻辑。

  7. kiss原则 我们需要尽量让复杂的问题简明化、简单化。


5.对于基础逻辑的封装

如果代码中出现大量的if或for代码段,且这些代码段及其相似;则可以考虑将这些代码段内的逻辑封装成一个函数,同时将整个基础逻辑封装成一个函数,这个函数的入参为第一个封装的内部逻辑函数,即使用函数式编程来封装代码段

Tips: 若在封装外部基础逻辑函数时碰到了不适配所有内部函数时候,可以考虑先将所有内部函数的入参以及返回值进行汇总,然后将汇总的所有入参作为外部函数的入参,将汇总的所有返回值作为返回值外部函数的返回值即可。但如果封装的层数变多,那么外部函数的入参就会越多,此时可以使用少量的全局变量来解决该问题。


6.验证抽象是否正确的方法

可通过观察”当需求改变时,代码需要修改位置的数量”。 若抽象/封装后的代码修改的位置比为封装时的修改位置数量少时,则说明抽象/封装是有效的。


7.重构的顺序

开始重构时,切记重构的元素一定要从小到大! 重构时也应遵循从小到大的原则,依次解决重复的常量/变量、语句、代码块、函数、类、库……发现重复不能只浮于表面相同,得理解其背后的意义,只有后续需要一起变化的重复才是真正的重复。从小到大的重构顺序能帮助理解每一个重复的细节,而反之却容易导致忽略这些背后的细节。


8.优化if else结构

  1. 使用卫语句 即判断条件取非。

  2. 使用map结构(switch语句) 即将判断条件和处理操作做映射。

  3. 使用责任链设计模式 责任链设计模式的定义为:

    为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

    即将所有的判断模式组成一个链表。当一个判断无法处理时,则会顺着链表调用当前处理节点的下一个判断节点。

  4. 使用策略模式

  5. 使用枚举 用枚举类型的变量来解决。每一种判断都为枚举中的一组。

  6. 表驱动法 从表里查询信息来代替逻辑语句。


9.类和文件名使用名词

这个名词要有意义,比如 Data、Information 就意义不明显,不是好名字。

而且当名称过长时,应考虑当前方法、类中所封装的功能是否过于复杂了;能否将其细化拆分为多个方法或类对象。


10.函数使用动词或短语命名

比如 isReady hasName。


11.长名字 vs 无意义名字

在长名字和无意义名字中选择时,请选择长且有意义的名字。


12.命名法则

常见的有驼峰命名法(camelCase)和蛇形命名法(snake_case), 比如文件名使用蛇形是 file_name,驼峰式 fileName。


13.常量命名

定义常量名来代替魔法数字,如在 Java 中: java final int static FEMALE=0,MALE=1;


14.变量的定义和使用不要离得太远

一般不要超过 20 行,函数也类似.


15.开关参数的滥用

函数的形参中有一个是 boolean 类型,函数体根据该参数为 true 或者 false 执行不同的代码块。这种方式会导致重构的另一个大函数的形成,从而增加代码的复杂性。开关虽然实现起来很方便,但这也是其容易被滥用的原因。 重构方法是:

去掉这个开关参数,将函数拆分成两个函数。


16.函数内修改形参

对于一些编程语言,其传入的是形参的引用(指针),而非形参的等同值。因此,若在函数内部直接对参数进行修改,可能会影响到函数体外部的逻辑。 重构方法是:

将参数赋值给局部变量,对局部变量修改。


17.函数的形参数量

函数的参数最多有三个是合理的,超过三个就需要提高警惕了。 重构方法是:

1.根据逻辑拆分函数 2.引入参数对象(parameter object:构造参数类,将原来传递的参数作为类的属性,调用方传入该类的一个对象)


18.临时变量

某个临时变量被赋值超过一次,就意味着它们在函数中承担了一个以上的职责。每个变量应该只承担一个责任。 重构方法:

针对每次赋值,创造一个独立、对应的临时变量,


19.复杂的逻辑判断条件

重构方式:

将这块代码抽取出来,变成一个单独的判断函数


20.处理类中的错误

抛异常通常都会被作为类与外界(其使用者)交互的一种方式。所以直接抛出向上层抛出异常就可以了。


21.自定义异常类

这样做好处是上层只需要根据catch住对应的异常类即可,而不用再根据抓到的异常中的信息来判断当前异常的具体信息。而且还可以在自定义异常中定义同一的日志字符串输出格式,保证异常描述的统一性。同时,通过将异常的具体描述信息作为异常类对象的实例属性,这样还可以方便在代码中处理异常。