代码重构 - 规范
事物的复杂程度在很大程度上是取决于其有序程度,减少无序能在一定程度上降低复杂度,这正是规范的价值所在。通过规范,把无序的混沌控制在一个合理的范围内,从而帮助我们减少认知成本,降低对事物认知的复杂度。
代码规范
代码格式
代码格式关系到代码的可读性,因此需要遵从一定的规范,包括缩进,水平对齐,注释格式等,关于代码格式,一个团队中最好是选定一种格式,因为一致性可以减少复杂度。
空行规范
空行是一个很小的细节,但不仅仅是一个细节问题。
老子的《道德经》中有提到 “三十辐共一毂,当其无,有车之用”,意思是指正是因为有了车轮毂和车轴之间的空白,车轮能够转起来,这正是“无“的价值。
空行在代码隔断方面作用也很大,例如spring中的BeanDefinitionVisitor,在包声明,导入声明和每个函数之间都有空行隔开,这种极其简单的规则极大地影响代码的视觉体验,每隔空白行都是一条线索,提示你下一组代码表示的是不同的概念和功能。
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);
}
}
}
}
如果删掉这些空白行,代码可读性就会变得很差。
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日志的打印,但是到了线上就会关闭,防止日志输出量特别大。
public void destroy() {
try {
log.debug("====关闭后台任务任务线程池====");
Threads.shutdownAndAwaitTermination(scheduledExecutorService);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
异常规范
异常规范主要包含异常处理和错误码设置两个方面。
一般建议在系统中将异常分为系统异常(SystemException)和业务异常(BizException)两种,不要在系统中胡乱地插入try-catch,会搞坏代码结构,将错误处理和正常流程混为一谈,严重影响代码的可读性。
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:未知系统错误)
如果业务应用的错误都用这种约定来描述和表达,那么只要大家遵守相同的规范,系统的可维护性和可理解性就会大大提升。
防止破窗
破窗效应是指环境中的不良现象如果被放任存在,就会诱使人们效仿,甚至变本加厉。
在软件开发过程中,破窗效应屡见不鲜,面对一个混乱的系统和一段杂乱无章的代码,后来人们往往会加入更多的垃圾代码,这也凸显了规范和架构的重要性。首先,我们要有一套规范,并且遵守,不去做那打破第一扇窗的人;其次,破窗要及时修复,不要让事情进一步恶化。整洁的代码需要每一个人的精心呵护,需要整个团队都具备一些工匠精神。