代码重构 - 规范

事物的复杂程度在很大程度上是取决于其有序程度,减少无序能在一定程度上降低复杂度,这正是规范的价值所在。通过规范,把无序的混沌控制在一个合理的范围内,从而帮助我们减少认知成本,降低对事物认知的复杂度。

代码规范

代码格式

代码格式关系到代码的可读性,因此需要遵从一定的规范,包括缩进,水平对齐,注释格式等,关于代码格式,一个团队中最好是选定一种格式,因为一致性可以减少复杂度。

空行规范

空行是一个很小的细节,但不仅仅是一个细节问题。

老子的《道德经》中有提到 "三十辐共一毂,当其无,有车之用",意思是指正是因为有了车轮毂和车轴之间的空白,车轮能够转起来,这正是"无"的价值。

空行在代码隔断方面作用也很大,例如spring中的BeanDefinitionVisitor,在包声明,导入声明和每个函数之间都有空行隔开,这种极其简单的规则极大地影响代码的视觉体验,每隔空白行都是一条线索,提示你下一组代码表示的是不同的概念和功能。

java 复制代码
package org.springframework.beans.factory.config;



import java.util.LinkedHashMap;

import java.util.LinkedHashSet;

import java.util.List;

import java.util.Map;

import java.util.Set;



import org.springframework.beans.MutablePropertyValues;

import org.springframework.beans.PropertyValue;

import org.springframework.lang.Nullable;

import org.springframework.util.Assert;

import org.springframework.util.ObjectUtils;

import org.springframework.util.StringValueResolver;



public class BeanDefinitionVisitor {



       @Nullable

       private StringValueResolver valueResolver;





       public BeanDefinitionVisitor(StringValueResolver valueResolver) {

              Assert.notNull(valueResolver, "StringValueResolver must not be null");

              this.valueResolver = valueResolver;

       }



       protected BeanDefinitionVisitor() {

       }





       public void visitBeanDefinition(BeanDefinition beanDefinition) {

              visitParentName(beanDefinition);

              visitBeanClassName(beanDefinition);

              visitFactoryBeanName(beanDefinition);

              visitFactoryMethodName(beanDefinition);

              visitScope(beanDefinition);

              if (beanDefinition.hasPropertyValues()) {

                     visitPropertyValues(beanDefinition.getPropertyValues());

              }

              if (beanDefinition.hasConstructorArgumentValues()) {

                     ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();

                     visitIndexedArgumentValues(cas.getIndexedArgumentValues());

                     visitGenericArgumentValues(cas.getGenericArgumentValues());

              }

       }



       protected void visitParentName(BeanDefinition beanDefinition) {

              String parentName = beanDefinition.getParentName();

              if (parentName != null) {

                     String resolvedName = resolveStringValue(parentName);

                     if (!parentName.equals(resolvedName)) {

                            beanDefinition.setParentName(resolvedName);

                     }

              }

       }

}

如果删掉这些空白行,代码可读性就会变得很差。

java 复制代码
package org.springframework.beans.factory.config;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringValueResolver;
public class BeanDefinitionVisitor {
       @Nullable
       private StringValueResolver valueResolver;
       public BeanDefinitionVisitor(StringValueResolver valueResolver) {
              Assert.notNull(valueResolver, "StringValueResolver must not be null");
              this.valueResolver = valueResolver;
       }
protected BeanDefinitionVisitor() {
}
public void visitBeanDefinition(BeanDefinition beanDefinition) {
              visitParentName(beanDefinition);
              visitBeanClassName(beanDefinition);
              visitFactoryBeanName(beanDefinition);
              visitFactoryMethodName(beanDefinition);
              visitScope(beanDefinition);
              if (beanDefinition.hasPropertyValues()) {
                     visitPropertyValues(beanDefinition.getPropertyValues());
              }
              if (beanDefinition.hasConstructorArgumentValues()) {
                     ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
                    visitIndexedArgumentValues(cas.getIndexedArgumentValues());
                    visitGenericArgumentValues(cas.getGenericArgumentValues());
              }
       }
       protected void visitParentName(BeanDefinition beanDefinition) {
              String parentName = beanDefinition.getParentName();
              if (parentName != null) {
                     String resolvedName = resolveStringValue(parentName);
                     if (!parentName.equals(resolvedName)) {
                            beanDefinition.setParentName(resolvedName);
                     }
              }
       }
}

在工作中可以使用idea中的代码格式化快捷键帮助我们快速格式化,但是工具毕竟是工具,有些功能逻辑还是需要开发者自己把握。一个简单的原则就是将概念相关的代码放在一起,相关性越强,彼此之间的距离就越近。

命名规范

上一章主要讲了如何命名及命名的意义,这里主要介绍一下基本的命名规范。

在Java中,有一下默认的命名约定:

类名采用大驼峰形式,取首字母大写的驼峰,例如Object,User,String等。

方法名采用小驼峰,取首字母小写的驼峰,一般为动宾结构,例如sleepThreeSeconds,

appendText。

常量名的字母全部大写,单词之间用下划线连接,例如TOTAL_COUNT,PAGE_SIZE等。

枚举类以Enum或Type结尾,枚举类成员名称全部大写,单词之间用下划线连接,例如SexEunm.MALE,SexEnum.FEMALE。

抽象类一般以Abstract开头,异常类使用Exception结尾,实现类以Impl结尾,测试类以需要测试的类名开头,Test结尾。

包名一般全部小写,不同单词之间用 . 连接。

日志规范

日志的重要性常常被人忽略,写好日志可以帮我们大大减轻后期维护的压力。

一般情况,将日志分为四个级别,由高到低:Error,Warn,Info,Debug。

Error日志表示是无法回复的错误,需要立即处理,比如说数据库连接错误,IO错误,未知的系统错误等,对于这类日志,不仅仅要打出错误堆栈,还需要打出上下文的一些参数,或者是功能模块的一些信息,便于排查。

系统中普遍都会接入监控系统,Error级别的日志一般是需要人工介入的,所以不能滥用,否则会增加系统的维护成本。

Warn日志表示一些可预知的问题,比如说参数错误,权限异常等等。在监控中一般会配置在一定的时间区间内warn日志出现了多少次的阈值告警。这样可以及时跟进问题。

Info日志通常用来记录一些系统运行中的状态。在通过日志定位问题时,优先通过info去初步定位,但是切记,不要把info日志当成debug来使用,防止记录的数据过多,让开发者找不到关键的信息。

Debug日志表示开发过程中的一些调试信息。例如某些参数需要打印下来,就可以用这类日志。通常在开发和测试环境允许debug日志的打印,但是到了线上就会关闭,防止日志输出量特别大。

java 复制代码
public void destroy() {

        try {

            log.debug("====关闭后台任务任务线程池====");

            Threads.shutdownAndAwaitTermination(scheduledExecutorService);

        } catch (Exception e) {

            log.error(e.getMessage(), e);

        }



}
异常规范

异常规范主要包含异常处理和错误码设置两个方面。

一般建议在系统中将异常分为系统异常(SystemException)和业务异常(BizException)两种,不要在系统中胡乱地插入try-catch,会搞坏代码结构,将错误处理和正常流程混为一谈,严重影响代码的可读性。

java 复制代码
    try{

            Response res = process(request);

        }catch (BizException e){

            log.error("BizException with error code:{},msg:{}",e.getErrorCode(),e.getErrorMsg());

        }catch (SystemException ex){

            log.error("System error {},{}",ex.getMessage(),ex);

        }catch (Exception ex){

            log.error("System error {},{}",ex.getMessage(),ex);

        }

错误码非常重要,需要统一的约定,对于错误码的管理,在后续的系统维护中会显得很轻松。

这里主要介绍两种错误码的约定方式:编号错误码和显性化错误码。

编号错误码好处是编码风格固定,给人一种正式感,缺点就是必须要配合文档才能理解错误码的含义。例如Oracle中共有两千多种错误码,从ORA-000001~ORA-02149,每一个错误码都有对应的错误解释。

ORA-00001:违反唯一约束条件

ORA-00018:超过最大会话数

ORA-00024:单一进程模式下不允许从多个线程注册

...

需要注意的是,一定要预留足够的码号,例如淘宝开放平台所用的3位数就显得很拘谨,其支撑的错误数最多不能超过三位数,超过后,为了向后兼容,只能通过子错误码的方式进行处理。

显性化的错误更具有灵活性,适合敏捷开发。例如,将错误码定义成3个部分:类型+场景+标识,每个部分之间使用下划线隔开,我们可以做个约定:P代表参数异常,B代表业务异常,S代表系统异常,那么错误码的设置就可以如下:

参数异常:P_xx_xx(P_Customer_NameIsNull:客户姓名不能为空)

业务异常:B_xx_xx(B_Customer_NameAlreadyExist:客户姓名已存在)

系统异常:S_xx_xx(S_Unknow_Error:未知系统错误)

如果业务应用的错误都用这种约定来描述和表达,那么只要大家遵守相同的规范,系统的可维护性和可理解性就会大大提升。

防止破窗

破窗效应是指环境中的不良现象如果被放任存在,就会诱使人们效仿,甚至变本加厉。

在软件开发过程中,破窗效应屡见不鲜,面对一个混乱的系统和一段杂乱无章的代码,后来人们往往会加入更多的垃圾代码,这也凸显了规范和架构的重要性。首先,我们要有一套规范,并且遵守,不去做那打破第一扇窗的人;其次,破窗要及时修复,不要让事情进一步恶化。整洁的代码需要每一个人的精心呵护,需要整个团队都具备一些工匠精神。

相关推荐
fangxiang200815 分钟前
spring boot 集成 knife4j
java·spring boot
C++小厨神24 分钟前
Bash语言的计算机基础
开发语言·后端·golang
BinaryBardC27 分钟前
Bash语言的软件工程
开发语言·后端·golang
飞yu流星33 分钟前
C++ 函数 模板
开发语言·c++·算法
没有名字的鬼38 分钟前
C_字符数组存储汉字字符串及其索引
c语言·开发语言·数据结构
王先生技术栈1 小时前
思维导图,Android版本实现
java·前端
生如夏花℡1 小时前
JAVA学习记录3
java·学习·idea
专注于开发微信小程序打工人1 小时前
庐山派k230使用串口通信发送数据驱动四个轮子并且实现摄像头画面识别目标检测功能
开发语言·python
土豆凌凌七1 小时前
GO:sync.Map
开发语言·后端·golang
{⌐■_■}1 小时前
【gRPC】对称与非对称加解密和单向TLS与双向TLS讲解与go案例
java·servlet·golang