代码整洁之道

338 阅读11分钟

一、为什么需要写整洁的代码

我们只要有几年开发经验,就会经历过接手别人的项目因为某些糟糕代码而纠结过。可能被这种代码拖过后腿,造成进度延缓。有些团队在项目初期进展迅速,但到后面项目进度却越来越缓慢。对代码的每一次修改都会影响到其他地方的代码。如果这样的项目代码我不进行关注任其发展,最后这样的代码逐步的堆积越来越多,最终直到我们束手无策。

二、什么是整洁的代码

写代码犹如写文章。本书Bob大叔给出了最简单的答案。总的原则无非是KISS(Keep It Simple Stupid):让代码简单直接,让阅读者可以很容易地看出设计意图

三、如何写出整洁的代码

3.1 整洁代码

  • 设计函数、类、模块遵循单一权责原则(SRP)全神贯注于一件事
  • 应当有单元测试和验收测试。它使用有意义的命名,代码通过其字面表达含义。
  • 减少不必要的重复代码。
  • 让营地比你来时更干净。时时保持代码整洁。

3.1.1 名副其实

  • 变量名、函数、类等的名称的见名知其意,好的名字让人起来更开心
  • 使用与原意相符合的命名,例如:一组账号使用accountGroup,而别使用accountList,除非它真的是List类型
  • 做有意义的区分。比如一个copyStr函数的入参应该是copyStr(source string,destination string)不应是copyStr(str1 string,str2 string)
  • 命名的名称是可以读的出来的。
  • 使用的名称的是可搜索。例如:MAX_CLASS_PER_STUDENT设置每个班的最大的参数,这个参数搜索起来是非常容易的,但搜索其应的数值就会非常难。 起一个好名字不仅方便我们自己进行记忆和维护,也赋予我们代码具有表达能力。

3.1.2 函数

  • 一个函数应该只做一件事(高内聚),无副作用。要判断函数是否只做了一件事 ,就看它是否能再拆分出一个函数
  • 自顶向下阅读代码,如同是在阅读报刊文章。
  • 长而具有描述性的函数名称,好过描述性的长注释。
  • 函数参数尽可能的小于等于3。太多的参数代入太多的概念性不易理解
  • 函数要么做什么事,要么回答什么事,二者不可兼得。例如:Set(attribute string,value string) boolean。该函数设置某个指定属性,如果成功,就返回true,如果不存在那个属性,就返回false,这样就导致以下语句发生歧义:if(set("username","lpp"))它是在问username属性值是否之前已设置为lpp,还是在问username属性值是否成功设置为lpp,语义很难判断。set本身是动词,而语句表达式形容词。
  • 使用异常代替返回错误码,使代码更清晰。

我们写函数时,一开始都冗长而复杂,有太多缩进和嵌套循环、有过长的参数列表、名称随意、重复代码。不过我们需要打磨这些代码,分解函数、修改名称、消除重复等。就想写文章一样初稿,然后反复打磨。

3.1.3 注释

什么也比不上放置良好的注释来的有用。什么也不会比乱七八糟的注释更有本事搞乱一个模块,什么也不会比陈旧、提供错误信息的注释更有破坏性。

  • 别给糟糕的代码加注释----重写吧。
  • 用代码阐释。把力气花在写清楚明白的代码上,直接保证无需编写注释。
  • 好的注释:
  1. 提供信息的注释。例如:解释正则表达式
  2. 对意图的解释。例如:函数中对某个数据结构做出了特殊处理比如JDK1.8中HashMap中put操作对链表做了特殊操作解决循环引用问题。
  3. 阐释。注释将一些晦涩难懂的参数或返回值的意义翻译为某种可读形式。
  4. 警示。一些注释用于警示某些操作可能会出现某种后果。 5.TODO注释

3.1.4格式

1.代码格式很重要。代码格式关乎沟通,而沟通是专业开发者的头等大事。 2.向报纸格式学习代码编写。 3. 垂直距离

  • 变量声明尽可能靠近使用位置,本地变量应在函数顶部出现
    
  • 实体变量应在类的顶部声明
    
  • 相关函数放在一起
    
  • 函数的排列顺序保持其相互调用的顺序
    
  1. 水平位置
  • 一行代码尽量短,不超过100-120字符
    
  • 用空格将相关性弱的分开:加减法,赋值,乘法因子见无需空格
    
  • 声明和赋值不需要水平对齐
    
  • 缩进
    
  • 空循环容易忽略行末的分号,要括号包围空循环。
    

3.1.5 对象和数据结构

1.对象把数据隐藏于抽象之后,只提供操作数据的函数。 数据结构暴露其数据,没有提供有意义的函数。 2.The Law of Demeter:模块不应去了解它所操作的对象内部细节。 3.遵守常见基本设计原则:

  • 开闭原则:开闭原则就是说对扩展开放,对修改关闭,在程序需要进行扩展的时候,不能去修改原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类。
    
  • 依赖倒转原则:这个是开闭原则的基础,具体内容:直接接口编程(如集成sdk),依赖抽象而不依赖具体。
    
  • 接口隔离原则:这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。这是一个降低类之间耦合度的意思,从我们这儿可以看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。
    

3.1.6错误处理

1.使用异常而非返回错误码. 2. try-catch-finally, 记录log出错信息. 3. 不要返回null,不要传递null。

3.1.7 边界

使用第三方代码或者开源库的时候,避免直接在生产代码中验证功能,可以尝试编写对应的单元测试来熟悉其使用方法,理解提供的api接口,这样当开源库版本发生变化,可以快速根据之前编写的单元测试来验证功能是否会受到影响。对于开源库/第三方提供的接口,可以尝试再进行一层封装,使用方便自己理解的接口名,避免代码各处直接引用其提供的接口,这样以后即便替换为不同的开源库或者开源库接口发生变更,也可以做到改动最小。

3.1.8 单元测试

单元测试与生产代码并行甚至优先于生产代码诞生,这样保证项目完成之时有着覆盖各个方面的单元测试体系。不能因为是单元测试就双重标准判断,不去注重单元测试的整洁。一套好的单元测试可以让你无所顾忌的修改现有代码的框架,也是你进行大改动的底气,而同生产代码一样,整洁的单元测试是必不可少的,否则一旦成套的单元测试臃肿不堪难以维护,会导致单元测试无法跟上生产代码的更新速度,最终变为效率上的累赘,一旦抛弃又无法保证主体功能的稳定。

3.1.9 类

  1. 自顶向下原则:让程序读起来就像是一篇报纸文章。
  2. method可以是protected,以便于单元测试。
  3. SRP:类或模块应有且仅有一个加以修改的原因。类名应准确描述其职责。高内聚。
  4. 开放闭合原则OCP、依赖倒置原则DIP
  5. 变量名、方法名、类名都是给代码添加注释的一种手段。

3.1.10 迭代前进

  1. 紧耦合的代码难以编写单元测试。
  2. 单元测试消除了对清理代码会破坏代码的恐惧。
  3. 写出自己能理解的代码很容易,软件项目的主要成本在于长期维护。
  4. 代码应当清晰表达其作者的意图;测试代码可以通过实例起到文档作用。

3.1.11 系统

如何建造和管理一座城市,这是靠单人力是无法做到的,因为城市有各种组织管理,有人需要负责全局有人需要负责细节,因此城市能运行必须是抽象的等级和模块,让个人和其管理的组件即便在不了解全局时也能有效运转。软件系统亦是如此,软件系统应该将起始过程和起始过程之后的运行逻辑分离开来即将构造和使用分离开来,使得类之间的耦合度下降,方便后续修改。例如:

public class RegisterImpl{
    public void register(){
            if(service==null)
            UserService userSerivce = new UserService()
            service.register();
    }
}

这个用例我们的构造和使用没有分开,RegisterImpl即构造的了UserService又调用了register方法耦合性太强。 类似"分离构造与使用"可以使用类似工厂模式+面向接口编程,如:

            public class RegisterImpl {
                public void register() {
                    UserService userSerivce= UserServicetFactory.getUserService();
                    userSerivce.register();
                }
            }
            
            public class UserServicetFactory {
                public static UserService getUserService() {
                    UserService userSerivce= new UserSerivceImpl();
                    return userSerivce;
                }:
            }

3.1.12 IOC

 Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。它是一种强大的机制可以实现构造与使用。 IOC 的思想最核心的地方在于,资源不由使用资源的双方管理,而由不使用资源的第三方管理。

    第一,资源集中管理,实现资源的可配置和易管理

    第二,降低了使用资源双方的依赖程度,也就是我们说的耦合度

    其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。IoC很好的体现了面向对象设计法则之一好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找

3.1.13 AOP

AOP技术,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为 “Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低 模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为; 那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手 将这些剖开的切面复原,不留痕迹。

四、总结

软件的质量不但依赖于架构及项目管理,而且与代码的质量紧密相关,这一点无论是敏捷开发流派还是传统开发流派都不得不承认的事实。 本书提出一种观念,代码的质量与其整洁度成正比,干净的代码即在质量上较为可靠,也为后期维护升级奠定了良好的基础。作为软件行业的专家,作者给出了一系列整洁代码的实践,在本书中主要体现为一条条编码规则,并辅之一些正反用例。我们只要遵循其规则一定能写出整洁清晰的代码。

判断你的代码是否整洁的方法:

image.png

WTF就是 what the fuck的缩写, WTF/min就是每分钟说出 what the fuck的次数,简单来讲就是当某个人在看一份代码时每分钟爆粗口的次数。显然每分钟爆粗口的次数越多代码质量越差,反之越好