代码整洁之道总结

前言

什么是整洁的代码

出自《c++程序设计语言》作者说到

我喜欢优雅和高效的代码。代码逻辑应当直截了当,叫缺陷难以隐藏;尽量减少依赖关系,使之便于维护;依据某种分层战略完善错误处理代码;性能调至最优,省得引诱别人做没规矩的优化,搞出一堆混乱来。整洁的代码只做妤一件事

OTI公司创始人,Eclipse 战略教父,Dave Thomas说到:

整洁的代码应可由作者之外的开发者阅读和增补。它应通过所有单元测试和验收测试。它使用有意义的命名。它只提供一种非多种做一件事的途径。它只有尽量少的依赖关系、明确地定义和提供清晰、尽量少的AP,代码应通过其字面表达含义,因为不同的语言导致并非所有必需信息均可通过代码自身清晰表达

简单代码规则

  1. 能通过所有测试
  2. 没有重复代码
  3. 体现系统中的全部设计理念
  4. 包括尽量少的实体,比如类、方法、函数等

命名

名副其实

变量命名

int d; //已用时间天数

比如:上面这句代码名称没有什么实际含义,无法表达时间消逝和按照天来计算。​

int elapsedTimeInDay = 1;

以上的这段代码不需要注释却也可以让人很清楚明白的理解其含义 也就是可以见名知意。
下面这是一个代表课程的数组,但是我们array没有明确的意思。我们应该直接写成能够直接看明白的名称。

 String[] array=new String[]{"课程1","课程2"};
 String[] courseArray=new String[]{"课程1","课程2"};

避免误导

命名不能有歧义,并且避免太过于相似导致难以区分

例子1:

  • 比如上面那个课程集合他是一个数组 所以命名不应该是list
String[] courseList = new String[]{"课程1", "课程2"};// 容易误导
String accountList  = “”;

其中courseList、accountList不是一个List却命名成List,容易给人带来误导,直接写成"courseArray"或者真正用List容器来存放。

例子2:

  • 不使用区别较小的名称,ZYXControllerForEfficientHandlingOfStrings和ZYXControllerForEfficientStorageOfStrings难以辨别

例子3:

  • 不使用小写l、大写O作变量名,看起来像常量1、0

做有意义的区分

例子1:

添加数字的反例:
public static void copyChars(char[] a1, char[] a2){
        for(int i = 0; i < a1.length; i++){
            a2[i] = a1[i];
        }
}
有意义的:
public static void copyChars(char[] source, char[] target){
        for(int i = 0; i < source.length; i++){
            target[i] = source[i];
        }
}

BeanUtils.copyProperties(source,target)

例子2:

使用废话的反例:存在类名Product,ProductInfo,ProductData。名称不同,但意思无区别。废话都是冗余。要尽量让读者能鉴别不同之处方式来区分。

使用读的出来的名称

例如: (生成日期,年月日时分秒) 变量命名时 gennerationTimeStamp 就比genymdhms好的多,前者比后者读起来好得多,也更好理解。

使用可搜索的词汇

名称长短应与其作用域大小相对应。若变量或常量可能在代码中多处使用,则应赋其便于搜索的名称

避免使用编码

例子1:

PhoneNumber phoneString;//名称不加类型,类型变化时,名称并不跟着变化

例子2:

不必用m_前缀来标明成员变量
private String m_des;

避免映射思维

比如传统上惯用单字母名称做循环计数器。所以就不要给一些非计数器的变量命名为:i、j、k等

类名

类名 应该是名词或者名词短语,
避免使用Manager、Processor 类名不应该是动词
避免使用Data、Info这样的类名

方法名

方法名应当是动词或者动词短语。如postPayment、deletePage或save

每个概念对应一个词

避免实用get,find,fetch多个命名定义查询方法,可以对于查询同一使用getXXX

别用双关语

有时可能使用add并不合适,比例insert、append。add表示完整的新添加的含义。

添加有意义的语境

可以把相关的变量放到一个类中,使用这个类来表明语境、或者添加前缀来提供语境

 private String firstName;
    
 private String lastName;

 private String addrFirstName;

 private String addrLastName;

不添加无意义的语境

名字中带有项目的缩写,这样完全没有必要。比如有一个名为“加油站豪华版”(Gas Station Deluxe)的项目, 在其中给每个类添加GSD前缀就不是什么好策略。

总结

  • 降低代码的模糊度,明确说明代码的用途
  • 做有意义的区分,只有意义不同时才使用不同的名字
  • 使用可搜索的名称:用宏定义数字比直接用数字好,避免使用单字母变量和数字常量
  • 避免成员变量使用类名前缀
  • 类名和对象名一般都是名词和名词短语
  • 方法名一般是动词和动词短语;get,set,is前缀
  • 添加有意义的语境:使用有意义的前缀
  • 命名要精确:不要添加无意义的语境

函数

短小

函数的第一规则是要短小。第二条规则是还要更短小。

只做一件事

函数应该只做一件事,做好这件事。

每个函数一个抽象层级

A方法中调用了B方法。B方法应该跟在A方法后面,如果A中调用了多个方法,这些方法也应该按照一定逻辑顺序排在A后面。

使用描述性的名称

长而具有描述性的名称,要比短而令人费解的名称好。长而具有描述性的名称,要比描述性的长注释好。使用某种命名约定,让函数名称中的多个单词容易阅读,然后使用这些单词给函数取个能说清其功用的名称

函数参数

参数数量

越少越好。 尽量不要有输出参数,而是将输出设置为返回值。 如果参数较多的时候可以考虑使用类进行封装。

最理想的参数量是零个,其实是一个,再次是二,应该尽量避免三个及以上的参数

标识函数

最好不要像函数传入布尔类型的值,这样做,方法签名立刻变得复杂起来,标识为true会这样做,标识为false会那样做。应该将函数一分为二。

分隔指令与询问

函数要么做什么事,要么回答做什么事,但两者不可得兼。函数应该修改某对象的状态,或者返回改对象的有关信息,两样常干常会导致混乱。

这是判断某个属性是否为为某个值,还是给属性设置某个值呢?

public boolen set(String attribute,String value)

修改方案,把指令分隔开来,防止混淆的发生

if (attributesExists("userName")){
  setAttribute("userName","aihe")
}

抽离try/catch代码块

try/catch会把程序弄的丑陋不堪,搞乱了代码结构,把错误与正常流程混为一谈。最好把tray和catch的主体部分抽离出来,另外形成函数。

如何写出这样的函数

写代码和写别的东西很像。在写论文或文章时,你先想什么就写什么,然后再打磨它。初稿也许粗陋无序,你就斟酌推敲,直至达到你心目中的样子。我写函数时,一开始都冗长而复杂。有太多缩进和嵌套循环。有过长的参数列表。名称是随意取的,也会有重复的代码。不过我会配上一套单元测试,覆盖每行丑陋的代码。然后我打磨这些代码,分解函数、修改名称、消除重复。我缩短和重新安置方法。有时我还拆散类。同时保持测试通过。
最后,遵循本章列出的规则,我组装好这些函数。我并不从一开始就按照规则写函数… 我想没人做得到。

注释

别给糟糕的代码写注释,重新写吧!

好注释

有些注释是必须的,也是有利的。来看看一些我认为值得写的注释。不过要记住,唯真正好的注释是你想办法不去写的注释。

  • 法律信息

比如:版权以及著作权的声明
提供信息的注释

/** Returns the number of key-value mappings in this map. 
If the map contains more than Integer.MAX_VALUE elements, 
returns Integer.MAX_VALUE.
Implementation Requirements:
**/
This implementation returns entrySet().size().
public int size() {
        return entrySet().size();
}
  • 对意图的注释
 public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }
  • 阐释

把某些晦涩难明的参数或返回值的意义翻译为某种可读形式

  • 警告

用于警告其他程序员会出现某种后果

  • TODO

TODO是一种程序员认为要做的,但由于某些原因还没做的工作。

  • 公共API

如果你在编写公共API,就该为它编写良好的javadoc

坏注释

  • 多余的注释

展示的简单函数,其头部位置的注释全属多余。都这段注释花的时间没准比读代码花的时间还要长

  • 循规式注释

每个函数都要注释,或者每个变量都要注释的规矩是愚蠢可笑

  • 日志式注释

代码整洁之道总结_第1张图片

  • 废话注释
    在这里插入图片描述

  • 位置标记

private String zoo = "" //动物//
  • 括号后面的注释

代码整洁之道总结_第2张图片

  • 注释掉的代码

尽力不要注释代码 因为 其他人不敢删除注释的代码

  • HTML注释

现在不建议自己写html 标记代码

代码整洁之道总结_第3张图片

格式

向报纸学习

​源文件要像报纸一样,短小而精悍。名称简单且一目了然。源文件最顶部给出高层次的概念和算法,细节从上往下渐次展开,直至找到源文件中最底层的函数和细节

代码块之前的空格

关联性高的代码块应该与其他有一定的间隔,如隔一个空格行,这样逻辑更清晰,读起来更轻松。

代码之间的靠近

相互关联的代码应该放在一起形成一个代码块​

关系密切的概念

关系密切的概念应该相互靠近

  • 变量声明:局部变量应尽可能靠近其使用位置,循环中的变量应该在循环中声明; 全局变量应该在类顶部声明
  • 调用函数:被调用的函数应该放在调用函数的下面第一位置。
  • 概念相关:概念类似的代码应该放在一起。例如几个名字很像,功能类似的方法。

横向格式

尽力按照IDEA 分割线为标准

空格分隔

空格字符把彼此紧密相关的事物连在一起,也把相关性较弱的事物分隔开。例如:

private void measureLine(String line) {
        lineCount++;
        int lineSize = line.length();
        totalChars += lineSize;
        lineWithHistogram.addLine(lineSize, lineCount);
    }

IDEA 快捷键也可以自动实现改格式。

异常

  • 使用异常而非错误代码
public Result getUserDto(User user) {
        try {
            Logger.param("user", user).desc("获取用户基本信息开始").info();
            List<UserEntity> userEntity = userDao.getUserInfo(user);
            LogBuilder.buildDefault().param("userEntity", userEntity).desc("获取用户基本信息结束").info();
            return ResultUtil.success(user);
        } catch (Exception ex) {
            Logger.param("user", user).desc("获取用户基本信息出现异常").error(ex);
            return ExceptionUtil.serviceException();
        }
    }
  • 对于java中的异常使用try{}catch{}捕获时,要判断哪些代码可能出错只对那部分代码进行捕获,避免捕获太多代码影响性能。

  • 必须避免空指针异常​

正例:if(obj!=null){}
反例:try{obj.method()}catch{}
  • 异常捕获后要对异常进行处理
 try {
            if (!checkParamIsLegal(userDto)) {
                return null;
            }
            UserEntity userEntity = buildUserEntity(recommendRequest, questionDto);
            return StringEscapeUtils.unescapeJavaScript(JSONObject.toJSONString(userEntity));
        } catch (Exception e) {
            LogBuilder.buildDefault().desc("checkParamIsLegal is failed ")
                    .param("userEntity", userEntity)
                    .error(e);
            return null;
        }
  • 不要返回null

一个方法返回null,会给上层代码增加严重的null处理负担,而且一旦遗漏对null的处理,就会导致难以排查的错误,所以尽量不要在方法中返回一个null。

  • 不要传null

除非API要求你传null值进行业务流程区分,否则尽量不要往函数中传null参数,因为这很有可能导致被调用函数抛出难以排查的空指针异常

单元测试

保持测试整洁

  • 脏测试等同于——如果不是坏于的话——没测试。
  • 测试必须随生产代码的演进而修改。测试越脏,就越难修改
  • 测试代码和生产代码一样重要。它可不是二等公民。他需要被思考、被设计和被照料。它应该向生产代码一般保持整洁。

F.I.R.S.T

快速(Fast)

测试应该够快。测试应该能快速运行。测试运行缓慢,你就不会想要频繁地运行它。如果你不频繁运行测试,就不能尽早发现问题,也无法轻易修正,从而也不能轻而易举地清理代码。最终,代码就会腐坏。

独立(Independent)

测试应该相互独立。某个测试不应为下一个测试设定条件。每个测试应该可以独立运行,及以任何顺序运行。当测试互相依赖时,前一个没通过就会导致一连串的测试失败,使问题诊断变得困难,隐藏了后面的错误

可重复(Repeatable)

测试应当可在任何环境中重复通过。你应该能够在生产环境、质检环境中运行测试,也能够在无网络的列车上用笔记本电脑运行测试。如果测试不能在任何环境中重复,你就总会有个解释失败的借口。当环境条件不具备时,你就会无法运行测试。

自足验证(Self-Validating)

测试应该有布尔值输出。无论是通过或失败,不应该通过查看日志文件来确定测试是否通过。不应该通过手工对比两个不同的文本文件来确认测试是否通过。如果测试不能自足验证,对失败的判断就会变得依赖主观,而运行测试也需要更长的手工操作时间

及时(Timely)

测试应及时编写。单元测试应该恰好在使其通过的生产代码之前编写。如果在编写生产代码之后编写测试,你就会发现生产代码难以测试。你可能会认为某些生产代码本身难以测试。你可能不会去设计可测试的代码。

代码重构

重构就是在不改变软件系统外部行为的前提下,改善它的内部结构

什么是重构

名词:
对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本
动词:
使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构

重构的意义

  • 能够保持良好的设计,不仅可以经历长期的版本迭代和不同开发者的洗礼,更能够强化原有设计
  • 能够将原有糟糕或者混乱的代码,逐渐改善为设计良好的代码,提高其可读性,更易于扩展
  • 能够去除重复代码,减少包大小,特别是对于SDK来说意义重大,更易于第三方接入

什么时候该开始重构

什么时候该开始重构?当闻到代码的“坏味道”,便可以开始重构。而具体的开始时间并没有完全的标准可以衡量,这完全取决于你自己,可以随时随地,或者是将来的某一个时间。重构不分大小,小到修改一个函数名字,也是很好的一个开始 。

如何判断代码 “坏味道”

  • 重复代码
  • 复杂的逻辑嵌套
  • 超长的函数
  • 超级类
  • 过长的参数列表

重构方法

通过使用 java8新语法

  • Lambda 表达式
(parameters) -> expression或(parameters) ->{statements; }
  • 方法引用
List names = new ArrayList();
        names.add("Google");
        names.add("Runoob");
        names.add("Taobao");
        names.add("Baidu");
        names.add("Sina");
        names.forEach(System.out::println);
  • 函数式接口

 private <R> R executeCommand(Function<Jedis, R> commond) {
        Jedis jedis = getJedis();
        try {
            if (jedis != null) {
                return commond.apply(jedis);
            }
        } catch (Exception e) {
            LOGGER.error("executeCommond failed : ", e);
        } finally {
            releaseJedis(jedis);
        }
        return null;
    }

 public String set(String key, String value, int time) {
        return executeCommand(jedis -> jedis.set(key, value, SetParams.setParams().ex(time)));
    }
  • stream
public static void main(String[] args) {
    List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
    List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
    
    List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
// 获取空字符串的数量
int count = (int) strings.parallelStream().filter(string -> string.isEmpty()).count();

}


设计模式

  • 创建型模式

代码整洁之道总结_第4张图片

  • 结构型模式

  • ⾏为模式


重构代码案例

目标:实现不同支付方式业务处理,微信、支付宝、QQ钱包

ifelse方案

public Result<Object> pay(PayBusiness payBusiness) {
        Result<Object> responseResult = new Result<>();
        if (Objects.equals(PayEnum.ALI_PAY.getPayCode(), payBusiness.getPayCode())) {
            buildAliResult(payBusiness, responseResult);
        } else if (Objects.equals(PayEnum.WX_PAY.getPayCode(), payBusiness.getPayCode())) {
            buildWxResult(payBusiness, responseResult);
        } else if (Objects.equals(PayEnum.QQ_PAY.getPayCode(), payBusiness.getPayCode())) {
            buildQqResult(payBusiness, responseResult);
        }
        return responseResult;
    }

策略+工厂重构

  • 类关系

代码整洁之道总结_第5张图片

参考

  • 《代码整洁之道》、《重学java 设计模式》、《重构改善既有代码的设计第2版》
  • https://refactoring.guru/design-patterns
  • https://www.runoob.com/design-pattern/design-pattern-tutorial.html
  • https://blog.csdn.net/Summer_Lyf/article/details/102975055

你可能感兴趣的:(代码规范,java)