【代码质量】-阿里巴巴java开发手册(代码质量提升神器)学习笔记

前言:《阿里巴巴 Java 开发手册》是阿里巴巴集团技术团队的集体智慧结晶和经验总结,有了这些前人总结的经验,可以帮助我们写出高质量的代码,同时可以减少Bug数量,少踩坑,提高代码的可读性和易维护性。本篇仅作为学习笔记,总结和提炼一些阿里巴巴java开发手册中需要遵守的规则,同时也是为了加深印象,手打了一遍规约,通过此番学习,感觉收获颇多,非常值得一学。


目录

1.编程规约

1.1命名风格

1.2常量定义

1.3代码格式

1.4OOP规约

1.5集合处理

1.6并发处理

1.7控制语句

1.8注释规约

1.9其它

2.异常日志

2.1异常处理

2.2日志规约

3.单元测试

4.安全规约

5.MSQL数据库

5.1建表规约

5.2索引规约

5.3SQL语句

5.4ORM映射

6.工程结构

6.1应用分层

6.2二方库依赖

7.设计规约

附录:专有名词解释


1.编程规约

1.1命名风格

①【强制】在代码中不要用下划线或美元作为命名的起始或结束符号,例如:_name,$name,name_,name$_等。

②【强制】代码中严禁使用拼音与中英文混合命名,更不允许直接使用中文命名,例如DaZhePromotion (打折),一定要使用拼音,也是在国际通用名的情况下,比如地名:HangZhou,专用名:Alibaba等。

③ 【强制】类名使用大驼峰命名,例如:BaseController,DO / BO / DTO / VO / AO / PO / UID 等除外。

④【强制】方法,参数,局部变量,成员变量都统一使用小驼峰命名,例如:salePrice,getUserInfo()。

⑤【强制】常量命名全部使用大写,单词之间用下划线_隔开,力求语义清晰,不要怕长,例如MAX_COUNT。

⑥【强制】抽象类命名要以Abstract或Base开头;异常类命名要以Exception结尾,测试类要以类名开始,以Test结尾。

⑦【强制】类型与中括号紧挨相连表示数组,例如:int[] arrayDemo.

⑧【强制】在POJO类中,布尔值变量不要加is,否则可能引起一些RPC框架解析异常,反例:isDeleted。

⑨【强制】包名统一使用小写单数命名,单词之间使用点.分隔符隔开。例如:com.alibaba.ai.util,一般推荐采用公司域名后缀+公司名缩写+所在BU+具体的封装对象类型进行命名。

⑩【强制】杜绝完全不规范的缩写,避免望文不能知义。反例:AbstractClass写成AbsClass,Condition写成condi。

⑪【推荐】为了达到代码自释义的效果,尽量使用完整的单词组合来命名,例如JDK的原子更新类名:         AtomicReferenceFieldUpdater,避免使用int a这种形式的命名。

⑫【推荐】如果模块,接口,类,方法中使用了设计模式,应该在命名时体现出来,例如:LoginProxy,ResourceObserver。

⑬【推荐】接口中的方法和属性尽量不要加任何修饰符,接口尽量避免定义变量,一定要定义要定义与接口相关的变量,反例:

public abstract void f()。

⑭【推荐】接口和实现类的命名有两套规则,接口实现类的命名结尾推荐使用Impl。

⑮【参考】枚举命名结尾尽量以Enum结尾,枚举中的成员名称统一使用大写,单词直接用下划线隔开。

⑯【参考】各层命名规约:

A) Service/Dao层方法命名规约:

 1)获取单个对象的方法用get作前缀。

 2)获取多个对象的方法用list作前缀,以对象的复数形式结尾,例如:listObjects。

 3)获取统计值的方法用count作前缀。

 4)插入的方法用save/insert作前缀。

 5)删除的方法用remove/delete作前缀。

 6)修改的方法用update作前缀。

B)领域模型命名规范

 1)数据对象:xxxDO,xxx即为数据库表名。

 2)数据传输对象:xxxDTO,xxx为业务领域相关名称。

 3)展示对象:xxxVO,xxx一般为网页名称。

 4)POJO是DO/VO/BO/DTO的统称,禁止命名成xxxPOJO。

1.2常量定义

①【强制】不允许任何魔法值(未经定义的常量)出现在代码中。反例:

 String key = "id#taobao_" + tradeId; map.put(key,value);

②【强制】在long或Long赋值时,以大写L结尾,避免使用小写l,容易与数字1混淆。

③【推荐】不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。正例:缓存相关常量放在CacheConsts类 下,系统配置相关常量放在ConfigConsts下。

④【推荐】常量的复用层次共有5层:跨应用共享常量,应用内共享常量,子工程内共享常量,包内共享常量,类内共享常量。

 1)跨应用共享常量:放置在二方库中,通常是client.jar中constant目录下。

 2)应用内共享常量:放置在一方库中,通常是子模块中的constant目录下。

 3)子工程内部共享常量:即在当前子工程的constant目录下。

 4)包内共享常量:即在当前包下单独的constant目录下。

 5)类内部共享常量:直接在类内部使用private static final xxx来定义。

⑤【推荐】如果变量值仅在一个固定范围内变化,推荐使用枚举来定义。例如:

如果存在名称之外的延伸属性就可以使用枚举来定义,例如下面例子中的数字就是延伸属性,可以表示一年中的第几个季节。

public enum SeasonEnum {     
    SPRING(1), 
    SUMMER(2), 
    AUTUMN(3),
    WINTER(4);     
    private int seq;     
    SeasonEnum(int seq){         
        this.seq = seq;     
    } 
} 

1.3代码格式

①【强制】大括号的使用约定,如果大括号中内容为空,可以简洁地写成{}即可,如果大括号中是非空代码,则:

 1)左大括号前不换行。

 2)左大括号后换行。

 3)右大括号前换行。

 4)右大括号后有else等代码则不换行,表示终止的右大括号后必须换行。

②【强制】左小括号和字符之间不出现空格;同样右小括号和字符之间也不出现空格,但左大括号前需要空格。

③【强制】if/do/while/switch等保留字与括号之间都必须加空格。

④【强制】任何二目、三目运算符前后都必须加一个空格。

⑤【强制】采用四个空格作为缩进,不能使用Tab进行缩进,如果一定要用Tab进行缩进,必须在编辑器里设置Tab填充4个空格。

⑥【强制】注释的双斜线与注释内容之间有且仅有一个空格。

⑦【强制】单行字符数限制不超过120个,超过需要换行,换行需要遵循以下原则:

 1)第二行相对第一行缩进4个空格,从第三行开始,不需要再继续缩进。

 2)运算符与下文一起换行。

 3)方法调用的点.符号与下文一起换行。

 4)方法调用中多个参数需要换行时,在逗号之后换行。

 5)在括号前不要换行。反例如图:

⑧【强制】方法多个参数之间,多个参数逗号之后必须有一个空格。例如:function(arg1, arg2, arg3...)

⑨【强制】IDE的Text file encoding设置为UTF-8;IDE的中文间换行符采用Unix格式,不要使用windows格式。

⑩【推荐】单个方法的总行数不超过80行。

⑪【推荐】没必要增加若干空格来使某一行字符与上一行对应位置的字符对齐。

⑫【推荐】不同逻辑,不同语义,不同业务的代码之间,插入一行空行来进行分隔,提升代码可读性。

1.4OOP规约

①【强制】避免通过一个类的对象引用来访问此类的静态变量或静态方法,无谓增加编译器的解析成本,直接用类名来访问即可,这条在较新版本的IDEA里已经没啥用了,因为通过引用调用时编译器已经自动过滤掉了这些静态变量和方法。

②【强制】所有的覆写方法必须加@Override注解,可以避免覆盖失败,编译时期就能发现。

③【强制】相同类型,相同业务含义时才能使用可变参数,避免使用Object。

④【强制】外部正在用的接口或二方库依赖的接口,不允许修改方法签名,以免影响接口的调用方。接口过时必须加@Deprecated注解,并清楚地说明新的接口或服务是什么。

⑤【强制】不能使用过时的类或方法。

⑥【强制】Object的equals方法容易抛空指针,应使用常量或有确定值的对象来调用equals方法,当然更推荐JDK1.7后提供的Objects.equals()方法。

⑦【强制】所有的包装类之间的比较全部使用equals进行比较,而非==。

⑧关于基本数据类型与包装数据类型的使用标准如下:

 1)【强制】所有的POJO类属性必须使用包装数据类型。

 2)【强制】RPC方法的参数和返回值必须使用包装数据类型。

 3)【推荐】所有的局部变量使用基本数据类型。

⑨【强制】定义DO/VO/DO/DTO等POJO类时,不要设定任何属性的默认值。

⑩【强制】序列化新增属性时,请不要修改SerialVersionUID字段的值,避免反序列化失败,如果完全不兼容升级,可以修改SerialVersionUID的值,抛出序列化运行异常。

⑪【强制】构造方法里禁止加入任何业务逻辑,如果有初始化操作时,请放在init方法中。

⑫【强制】Pojo类必须覆写toString方法,如果继承自某个类,需要在前面加上super.toString();

⑬【强制】禁止在Pojo类中同时存在属性xxx的isXxx和getXxx方法,以免框架在调用时不能确定哪一个是被优先调用的。

⑭【推荐】当使用索引访问String.split方法得到的数组时,需要对最后一个分隔符后的有无内容做判断,如果最后一个分隔符后没内容,否则可能导致抛出IndexOutOfBoundsException的风险。

⑮【推荐】当一个类具有多个构造方法或多个同名方法时,这些方法应按顺序放置在一起,便于阅读和维护,此规则优先于第⑯条规则。

⑯【推荐】类内部定义方法的顺序依次是:公有方法或保护方法>私有方法>getter/setter方法。

⑰【推荐】setter方法中,参数名称与类成员名称要保持一致,this.成员名=参数名,在getter/setter方法中不要增加业务业务逻辑,以免增加问题的排查难度。

⑱【推荐】循环体内的字符串连接使用StringBuilder的append方法进行连接,最后调用toString返回,避免使用String相加这种形式浪费系统资源。

⑲【推荐】final可以修饰类,成员变量,方法,以及本地变量,下列情况使用fInal关键字:

 1)不允许被继承的类,如String。

 2)不允许修改引用的域对象。

 3)不允许被重写的方法,如POJO类的setter方法。

 4)不允许运行过程中重新复制的局部变量。

 5)避免上下文重复使用一个变量,使用final可以强制重新定义一个变量,方便更好地进行重构。

⑳【推荐】慎用Object.clone克隆对象,Object提供的clone方法默认是浅拷贝实现,如果要深拷贝需要覆写clone方法遍历实现。

※【推荐】类成员与访问方法控制从严:

 1)如果不允许外部直接通过new来创建对象,那么构造方法必须private。

 2)工具类不允许有Public或default构造方法。

 3)类非static成员变量如果要与子类共享时,必须加protected修饰。

 4)类static及非static成员变量并且仅在本类使用,必须加private修饰。

 5)若是static成员变量,考虑是否使用final。

 6)类成员方法只供类内部调用,必须是private。

 7)类成员方法只对继承类公开,那么限制为protected。

这样做的理由倒不是为了安全,即便是private修饰的方法,也可以通过反射暴力破解,这样做主要是为了在代码重构等场景下,能够减少误操作。一个private修饰的方法我可以在该类中轻易地对其进行改造/删除,但是public修饰的方法,在我没搞清楚外部对其的调用关系,是不敢对其进行随意修改的。

1.5集合处理

①【强制】关于hashCode和equals方法必须遵循以下几点:

 1)只要重写equals,就必须重写hashCode.

 2) 因为Set存储的是不重复对象,判断对象是否重复依据的是hashCode和equals方法,所以Set存储的对象必须重写这两个方法。

 3)Map的键也必须重写这两个方法。

②【强制】ArrayList的subList的结果不能强转为ArrayList,否则会抛出ClassCastException异常,因为subList返回的对象并非ArrayList,而是ArrayList的内部类(视图)subList,对subList的任何操作都会最终反映到原List上。

③【强制】在subList的场景要高度注意对原集合元素的增加或删除都会导致子列表的遍历/增加/删除产生ConcurrentModificationException。

④【强制】使用集合转数组的方法,必须使用集合的toArray(T[] array),传入的是类型完全一样的数组,大小就是list.size()。

⑤【强制】使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合的相关方法,add/remove/clear方法均会抛出unspportedOperationException,因为该方法仅是适配器模式的体现,底层调用的还是原数组。

⑥【强制】泛型通配符<? extends T>来接收返回数据,此写法的数据不能使用add()方法,而<? super T>不能使用get方法。

这里可能不太好记,记住PECS原则就好了:即producer extends,Consumer super。

频繁往外读取内容的,有返回值,是生产者,适合用<? extends T>。第二、经常往里插入的,是消费者,适合用<? super T>。 

⑦【强制】不要在for循环里对集合元素做移除操作,一定要移除,请使用Iterator迭代器进行操作,否则会抛出ConcurrentModifyException,在并发场景下,需要对Iterator进行加锁或者考虑使用并发容器。

⑧【强制】在Jdk1.7+,Comparator实现类要满足下面三个条件,不然ArrayList.sort和Collections.sort会报 IllegalArgumentException。条件如下:

 1) x,y 的比较结果和 y,x 的比较结果相反。  

 2) x>y,y>z,则 x>z。  

 3) x=y,则 x,z 比较结果和 y,z 比较结果相同。 

⑨【推荐】在JDK1.7+,定义集合或泛型时使用Diamond语法,即<>来指代前面已经指定的类型。

⑩【推荐】集合在初始化时需要指定初始值大小。

⑪【推荐】使用EntrySet遍历Map类集合,而不是keyset方式,后者需要遍历两次,在JDK1.8+推荐使用Map.foreach遍历。

⑫【推荐】高度注意Map类集合的k,v能不能存储null值的情况,具体如下表格所示:

特别需要注意,受HashMap影响,ConcurrentHashMap会被误以为k,v可以为null,实际上是不可以的,如果为Null会抛NPE.

⑬【参考】合理利用好集合的有序性(sort)和稳定性(order),避免集合的无序性(unsort)和不稳定性(unorder)带来的负面影响。

有序性是指遍历的结果是按照某种比较规则依次排列的,稳定性是指集合每次遍历的元素次序是一定的。

如ArrayList是unsort/order;HashMap是unsort/unorder;TreeSet是sort/order;

⑭【参考】利用Set元素唯一的特性可以对集合元素快速去重,避免使用List的Contains方法进行遍历,对比,去重操作。在jdk1.8+可以使用distinct方法进行去重。

1.6并发处理

①【强制】获取单例对象需要保证线程安全,对象里的方法也要保证线程安全。

②【强制】创建线程池时请指定有意义的线程名字,方便出错时回溯。

③【强制】线程资源必须通过线程池提供,不允许在应用中自行显示创建线程。

④【强制】不允许在应用中通过Executors去创建线程池,应通过ThreadPoolExecutor的方式,以免出现资源耗尽。

⑤【强制】SimpleDateFormat是线程不安全的类,一般不要定义为static,定义为static必须加锁。

可以使用ThreadLocal封装或者在JDK1.8+版本推荐使用Instant代替Date,可以使用LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFomat.

⑥【强制】高并发下,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁,能锁区块,就不要锁整个方法体,能用对象锁,就不要用类锁。

⑦【强制】对多个资源,数据库表,对象,同时加锁时,需要注意加锁的顺序要保持一致,否则可能会出现死锁。

⑧【强制】并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加锁,要么在数据库加乐观锁,使用version作为更新依据。如果每次访问冲突效率20%,推荐使用乐观锁,否则使用悲观锁,乐观锁的重试次数不得小于3此。

⑨【强制】多线程并行处理定时任务时,Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用ScheduledExecutorService则没有这个问题。

⑩【推荐】使用CountDownLatch进行异步转操作,每个线程在退出前必须调用countDown方法,线程执行代码注意Catch异常,确保countDown方法被执行到,避免主线程无法执行到await方法,直到超时才返回结果。

注意:子线程抛出异常堆栈,不能在主线程try-catch到。

⑪【推荐】避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一个seed导致性能下降。

Random的实例包括java.util.Random的实例或者Math.random()的方式。在JDK1.7+,可以使用ThreadLocalRandom来解决。

⑫【推荐】在并发场景下,通过双重检查锁()实现延迟初始化问题隐患可参考(The "Double-Checked Locking is Broken" Declarationhttps://blog.csdn.net/gerryzhu/article/details/17524281),在jdk1.5+将目标声明为volatile类型。

反例:

class LazyInitDemo {   
    private Helper helper = null;  
    public Helper getHelper() {  
    if (helper == null) synchronized(this) { 
        if (helper == null) {        
            helper = new Helper();    
        }      
        return helper;  
    }  // other methods and fields... 

 }

⑬【参考】volatile解决多线程之间内存不可见问题。对于一写多读,是可以解决变量同步问题,但如果多写,同样无法解决线程安全问题。如果是count++操作,可以采用AtomicInteger来实现,如果是JDK1.8+,推荐使用LongAdder,性能比AtomicLong更好(减少乐观锁重试次数)。

⑭【参考】HashMap在容量不够用进行resize时,由于高并发可能出现死链,导致CPU飙升,在开发过程中可以使用其它数据结构或者加锁来规避此风险。

⑮【参考】ThreadLocal无法解决共享对象的更新问题,ThreadLocal对象建议使用static修饰。这个变量是针对一个线程内所有操作共享的,所以设置为静态变量,所有此类实例共享此变量,也就是说类在第一次使用时被装载,只分配一块存储空间,所有此类对象(在这个线程内定义的)都可以操控这个变量。

1.7控制语句

①【强制】在一个switch语句块中,每个case要么通过break/return来终止,要么注释说明程序将执行到哪一个case为止,在一个switch块内,都必须包含一个default语句放在最后,即使空代码。

②【强制】在if/else/for/while/do语句中,必须使用大括号,哪怕只有一行代码,避免采用单行编码的格式:if(xxx) todo;

③【强制】在高并发场景中,避免使用“等于”判断作为中断/退出的条件。如果并发没有处理好,容易出现等值被击穿的情况,例如:把库存等于0时作为不能售卖的条件,如果并发没控制好,出现了超卖,库存变为负数,就无法终止售卖了。

④【强制】在表达异常的分支时,少用if-else这种形式,可以改写成如下这种形式:

if (condition) {
    ... return obj;    
}   
// 接着写 else 的业务逻辑代码;

如果非得使用If-else方式,【强制】避免代码维护困难,层级不要超过3级。 超过3层的可以使用卫语句,策略模式,状态模式。

⑤【推荐】除了常用方法(如getXxx,isXxx等)外,不要在条件判断中执行其它复杂语句,将复杂逻辑的判断结果赋值给一个有意义的布尔字段,然后在条件中使用该布尔字段,提高可读性。

// 伪代码如下 
final boolean existed = (file.open(fileName, "w") != null) && (...) || (...); 
if (existed) { 
... 
}

⑥【推荐】循环体中的语句要考量性能,以下语句尽量移至循环体外,如定义对象,变量,获取数据库连接,进行不必要的try-catch操作。

⑦【推荐】避免采用取反逻辑运算符。取反逻辑不利于快速理解,且取反运算必然存在对应的正向逻辑写法。

⑧【推荐】接口入参保护,这种场景常见于批量操作的接口对象。例如对于超大批量的操作应该通过卫语句直接返回。

⑨【参考】下列情形,需要进行参数校验:

 1)调用频次低的方法。

 2)执行时间开销很大的方法。此情形中,参数校验时间几乎可以忽略不计,但不校验带来的错误及回退得不偿失。

 3)需要极高稳定性和可用性的方法。

 4)对外提供的开放接口,RPC/API/HTTP接口等。

 5)敏感权限入口。

⑩【参考】下列情形,不需要进行参数校验:

 1)极有可能被循环调用,但在方法说明里必须注明外部参数检查要求。

 2)底层调用频度较高的方法。比如Dao层的方法,一般在Controller或者Service层就做了参数校验了,没必要再做一遍。

 3)被声明成private只会被自己代码所调用的方法,如果可以确定调用方法的入参已经做了校验,就可以省略。

1.8注释规约

①【强制】类/类方法/类属性 必须使用javadoc注解,不得使用//代替/**xx*/。

②【强制】所有的抽象方法(包括接口)都必须使用javadoc注解,除了返回值,参数,异常外还需要指出该方法做什么事情,实现什么功能。对子类的实现要求或者调用注意事项,请一并说明。

③【强制】所有的类必须添加创建者和创建时间

④【强制】方法内部单行注释,使用//并另起一行,多行注释使用/*xx*/,注意与代码对齐。

⑤【强制】所有的枚举类型字段必须有注释,并说明每个数据项的用途。

⑥【推荐】与其“半吊子”(看笑了)用英文来注释,不如用中文注释把问题说清楚。专有名词和关键字用英文即可。

⑦【推荐】代码修改的同时,注释也要一起作相应修改。尤其是参数,返回值,异常,核心逻辑等的修改。

⑧【推荐】谨慎注释掉代码。在上方详细地说明,而不是简单的注释掉,如果无用,则删除。

代码被注释掉有两种动机:1)后续会恢复此段代码的逻辑。2)永久不用。 如果是前者没有备注很难了解动机,后者则可直接删除(代码仓库里有历史记录)。

⑨【参考】对于注释的要求:

1)能够准确反映设计思想和代码逻辑。

2)能够描述业务含义。

以便自己日后再看可以快速理解以及代码继任者可以快速上手。

⑩【参考】好的命名和代码结构是可以自解释的,注释力求简洁准确,表达到位,避免出现过度注释。

⑪【参考】特殊注释标记,请注明标记人和标记时间。注意及时处理这些标记,线上故障经常会来源于这类标记处的代码。

1)代办事宜(TODO):(标记人,标记时间,[预计处理时间])表示需要实现,但目前还未实现的功能。

2)错误,不能工作(FIXME):(标记人,标记时间,[预计处理时间])表示代码是错误的,不能工作,需要及时纠正的情况。

1.9其它

①【强制】在使用正则表达式时,利用好正则表达式的预编译功能,可以有效提高匹配效率。

说明:不要在方法体内定义:Pattern pattern = Pattern.compile(“规则”); 

//正确使用姿势
private static final Pattern pattern = Pattern.compile(regexRule);
 
private void func(...) {
    Matcher m = pattern.matcher(content);
    if (m.matches()) {
        ...
    }
}

②【强制】使用velocity调用POJO类的属性时,建议直接使用属性名取值即可,模板引擎会自动按规则进行调用getXxx()如果是布尔基本类型字段,不需要加isXxx,会自动调用isXxx方法。

③【强制】后台输送给页面的变量必须加$!{var}——中间的感叹号。

说明:如果 var 等于 null 或者不存在,那么${var}会直接显示在页面上。

④【强制】注意Math.random()方法的返回值 0≤x≤1,这个方法可以取到0值,所以要防止除0异常。

⑤【强制】获取当前毫秒数使用System.currentTimeMillis(),而非new Date().getTime。前者更精确,在JDK1.8+涉及时间的场景,推荐使用Instant。

⑥【推荐】不要在视图模型中加入任何复杂逻辑。MVC理论,不然分层就没多大意义了。

⑦【推荐】任何数据结构的构造器或初始化应该指定大小,避免数据结构无限增长吃光内存。

⑧【推荐】及时清理不再使用的代码段或配置信息。

对于垃圾代码或过时的配置信息,坚决清理干净,避免程序过度臃肿,代码冗余。

对于后续要恢复的代码,在注释代码的上方,统一规定使用三个斜杠注释///来说明代码被注释的理由。


2.异常日志

2.1异常处理

①【强制】java类库中定义的可以通过预检查方式规避的RuntimeException异常不应该通过catch的方式来处理,比如NPE,IndexOutofBoundsException等。不得不采用catch的异常如字符串转数字,采用catch来捕获NumberFormatException。

②【强制】异常不要用来做流程控制,条件控制。因为异常做流程控制的效率很低。

③【强制】catch时分清稳定代码和非稳定代码,稳定代码是无论如何也不会发生异常的代码,对于非稳定异常的代码,catch尽可能区分异常类型,再做对应的异常处理。

对大段代码try-catch,使程序无法根据对应异常做出正确的应激反应,也不利于问题定位,是不负责任的表现。

④【强制】捕获异常是为了处理它,如果捕获了异常啥也不做,那不如抛给方法的调用者。最外层的业务调用者必须处理异常,将其转化为用户可以理解的内容。

⑤【强制】有try块放到了事务代码中,catch异常后,如果需要回滚事务,一定要注意手动回滚事务。

⑥【强制】finally块必须对资源对象、流对象进行关闭,有异常也要做try-catch。

⑦【强制】不要在finally块中使用return,否则将不会再执行try块中的return。

⑧【强制】捕获异常与抛出异常必须是一致的,或者捕获异常是抛出异常的父类。

⑨【推荐】方法的返回值可以为null,不强制返回空集合或空对象等,必须添加注解说明何种情况下会返回null值。

本手册明确防止 NPE 是调用者的责任。

⑩【推荐】防止NPE,是程序员的基本修养,注意NPE的产生场景:

1)返回类型为基本数据类型,return包装类型数据对象,如果包装类型数据对象为null,则会导致自动拆箱时抛NPE。

//反例
public int f() { 
    return Integer;
}

2)数据库的查询结果可能为null。

3)集合里的元素即使isNotEmpty,取出的元素也可能为null。

4)远程调用返回对象时,一律要求进行空指针判断,防止NPE。

5)对于Session中获取的数据,建议NPE检查,避免空指针。

6)级联调调用obj.getA().getB().getC()...易产生空指针。

建议使用JDK1.8+提供的Optional类来避免NPE。

⑪【推荐】定义时区分checked/unchecked异常,避免直接抛出RuntimeException,更不允许直接抛出Exception或throwable。应使用业务含义自定义异常,推荐业界已经定义过的异常:DAOException,ServiceException。

⑫【参考】对于公司对外开放的http/api开放接口必须使用“错误码”;而应用内部推荐使用异常抛出,跨应用RPC调用方式推荐使用Result方式,封装isSuccess(),错误码,错误简短信息。

⑬【参考】避免出现重复的代码DRY((Don’t Repeat Yourself)原则。

2.2日志规约

①【强制】应用中不允许直接使用日志系统(Log4j,Logback)中的API,而依赖使用Slf4j中提供的API,使用门面模式的日志框架,有利于维护各个类的日志处理方式统一。

②【强制】日志文件至少保存15天,因为有些异常具备以“周”为频次发生的特点。

③【强制】应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:

appName_logType_logName.log,通过这种命名可以清楚得看出日志属于哪个应用,什么类型,什么目的,也有利于归类查找。正例:mppserver 应用中单独监控时区转换异常,如:mppserver_monitor_timeZoneConvert.log 。推荐对日志进行分类,如将错误日志和业务日志分开存放,便于查看及日志系统进行监控。

④【强制】对trace/debug/info级别的日志输出,必须使用条件输出形式或使用占位符的方式。(这里发现规约里一个错别字,建议打成了建设,截图为证,哈哈)

⑤【强制】避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false。

⑥【强制】异常日志应该包括两块信息:异常的案发现场信息(参数等)和异常堆栈信息。

⑦【推荐】谨慎地记录日志,生产环境禁止输出debug日志;有选择地输出info日志,如果使用warn来记录刚上线时的业务行为信息,一定要注意日志的量,避免把服务器磁盘撑爆,并记得及时删除这些观察日志。

⑧【推荐】可以使用warn级别日志来输出用户输入参数错误的情况,避免用户投诉时无所适从。如非必要,请不要在此场景打出error日志,避免频繁告警。

⑨【推荐】尽量使用英文来描述日志错误信息,如果日志中的错误信息用英文不能描述清楚的话,可以使用中文来描述,国际化团队除外。


3.单元测试

①【强制】好的单元测试必须遵循AIR原则。

好的单元测试在线上就好像空气(AIR)一样,并不存在,但在测试质量的保障上,却是非常关键的。

好的单元测试具备三个特性-> A:Automatic(自动化) I:Independent(独立性)R: Repeatable(可重复)

②【强制】单元测试必须是全自动的,而非交互式的。测试用例通常是被定期执行的,执行过程必须完全自动化才有意义。输出结构需要人工检查的不是好的单元测试,不允许使用System.out来人肉验证,必须使用assert来验证。

③【强制】保持单元测试的独立性。为了保持单元测试稳定可靠且便于维护,单元测试用例之间绝不能相互调用,也不能依赖执行的先后次序。反例:method2 需要依赖 method1 的执行,将执行结果作为 method2 的输入。 

④【强制】单元测试是可以重复执行的,不能受到外界环境影响。

⑤【强制】对于单元测试,要保证测试粒度足够小,有助于精确定位问题。粒度至多是类级别,推荐是方法级。

⑥【强制】核心业务,核心应用,核心模块的增量代码确保单元测试通过。

⑦【强制】单元测试代码必须写在如下工程目录下:src/test/java,不允许写在业务代码目录下。

源码构建会跳过测试目录,测试框架扫描一般扫描测试目录。

⑧【推荐】单元测试的基本目标:语句的覆盖率达到70%+,核心模块的语句覆盖率和分支覆盖率要达到100%。

⑨【推荐】编写单元测试代码遵守BCDE原则,以保证被测试模块的交付质量。

B:Border,边界值测试,包括循环边界,特殊取值,特殊时间点,特殊顺序等。

C:Corret,正确的输入,返回正确的结果。

D:Design,与设计文档结合,来编写单元测试。

E:Error,强制错误信息输入(如非法数据,异常流程,非业务允许输入等),并得到预期结果。

⑩【推荐】对于数据库的相关查询,更新,删除操作,不能假设数据库里的数据是存在的,或者直接操作数据库把数据插进去,请使用程序插入或导入数据的方式来准备数据。

⑪【推荐】和数据库相关的单元测试,可以设定自动回滚机制,确保测完后不留下脏数据。或者对单元测试后的数据留下明显的前后缀标识。

⑫【推荐】对于不可测试的代码建议做必要的重构,变成可以测试的代码。避免为了达到测试要求,书写不规范的测试代码。

⑬【推荐】在设计评审阶段,开发人员需要和测试一起确定单元测试范围,单元测试范围最好覆盖所有测试用例。

⑭【推荐】单元测试作为一种质量保障手段,不建议代码发布后补充单元测试用例,建议在项目提测前完成单元测试。

⑮【参考】为了更方便的进行单元测试,业务代码应避免以下情况:

1)构造方法中做的事情过多。

2)存在过多的全局变量和静态方法。

3)存在过多的外部依赖。

4)存在过多的条件语句。

⑯【参考】不要对单元测试存在以下误解:

1)那是测试同学干的事情。这是开发手册,这里面的所有内容都是跟开发强相关的。

2)单元测试代码是多余的。系统的整体功能和各单元测试部件的正常是强相关的。

3)单元测试代码不需要维护。一年半载后,那么单元测试几乎处于废弃状态。

4)单元测试与线上故障没有辨证关系。好的单元测试能够最大限度地规避线上故障。


4.安全规约

①【强制】隶属于用户个人的页面或功能,必须做权限校验。

②【强制】用户的敏感信息需要作脱敏处理,不能直接展示。例如手机号:135xxxx5520

③【强制】用户输入的SQL参数严格使用参数绑定或者使用 METADATA 字段值限定,防止SQL注入。

④【强制】用户传入的任何参数必须做有效性验证。

忽略参数校验可能会导致:

 1)pageSize过大导致内存溢出

 2)恶意order by导致数据库查询变慢

 3)任意重定向

 4)SQL注入

 5)反序列化注入

 6)正则输入源串拒绝服务ReDoS

⑤【强制】 禁止向HTML页面输出未经安全过滤或未经正确转义的用户数据。

⑥【强制】表单,AJAX提交必须进行CSRF安全验证。

⑦【强制】在使用平台资源,譬如短信,邮件,电话,下单,支付,必须实现正确的防重放机制,如数量限制,疲劳度控制,验证码校验,避免被滥刷而导致资损。

⑧【推荐】发帖,评论,发送即时消息等用户生成内容的场景,必须实现防刷,文本内容违禁词过滤等风控策略。


5.MSQL数据库

5.1建表规约

①【强制】表达是与否概念的字段,必须使用is_xxx的方式命名,字段类型是unsigned tinyint ,1表示是,0表示否。

POJO类中,任何布尔类型字段的变量,都不要加is前缀。

②【强制】表名、字段名必须使用小写字母或数字,避免使用数字开头,禁止两个下划线之间仅出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称要慎重考虑。

③【强制】表名不适用符数名词。

④【强制】禁用保留字,如desc,range,match,delay等,详情参考MYSQL官方保留字。

⑤【强制】主键索引名为pk_字段名;唯一索引命名为uk_字段名;普通索引命名为idx_字段名。

⑥【强制】小数类型使用Decimal,禁止使用float或double。

⑦【强制】如果存储的字符串长度几乎相等,使用char定常的字符串类型。

⑧【强制】varchar是可变长字符串,不预先分配存储空间,长度不要超过5000,如果存储长度大于此值,定义字段类型为text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。

⑨【强制】表必备三字段:id,gmt_create,gmt_modified

⑩【推荐】表的命名最好是“业务名称“_"表的作用"。例如:alipay_task。

⑪【推荐】库名与应用名尽量一致。

⑫【推荐】如果修改字段含义或对字段表示的状态追加时,需要及时更新字段的注释。

⑬【推荐】字段允许适当冗余,以提高查询性能,但必须考虑数据一致。冗余字段应该遵循:

1)不是频繁修改的字段。

2)不是varchar超长字段,更不能是text字段。

⑭【推荐】单表容量超过500万行或单表容量超过2GB,才推荐进行分库分表。(如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表)

⑮【参考】合适的字符存储长度,不但节约数据库表空间,节约索引存储,更重要的时提升检索速度。

例如:

5.2索引规约

①【强制】业务上具有唯一特性的字段,即便是多个字段的组合,也必须建立唯一索引。

②【强制】超过三个表禁止join,需要join的字段,数据类型必须绝对一致,多表关联查询时,保证被关联的字段需要有索引。

③【强制】在varchar字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度即可。

索引长度和索引区分度是一对矛盾体,一般对字符串类型数据,索引长度达到20,区分度会达到90%以上,索引区分度可以通过count(distinct left (列名,索引长度)/count(*)来计算。

④【强制】页面搜索严禁左模糊或全模糊,如果一定要这样请走搜索引擎。

⑤【推荐】如果有order by的场景,请注意利用索引的有序性。order by最后的字段是组合索引的一部分,并且放在索引组合的最后,避免出现file_sort的情况,影响查询性能。

正例:where a = ?, b = ? order by c; 索引a_b_c 。

反例:where a > ? order by b;索引a_b无法排序。

⑥【推荐】利用覆盖索引来查询,避免回表。

覆盖索引只是一种查询效果,用explain的结果在extra会出现:using index.

⑦【推荐】利用延迟关联或子查询优化超多分页的场景。

MySQL并不是跳过offset行,而是取offset+N行,然后返回放弃前offset行,返回N行,那么当offset行特别大时,效率会非常低下,要么控制返回的页数,要么对超过特定阈值的页数进行SQL改写。

正例: SELECT a.* FROM 表 1 a, (select id from 表 1 where 条件 LIMIT 100000,20 ) b where a.id=b.id

⑧【推荐】SQL性能优化的目标:至少要达到range级别,要求是ref级别,如果可以是consts最好。

性能:all<index<range<ref<eq_ref<const<system<null

⑨【推荐】建立组合索引时,区分度最高的建在最左边。

存在非等号和等号混合时,在建索引时,请把等号条件的列前置。如:where c>? and d=? 那么即使 c 的区分度更高,也必须把 d 放在索引的最前列,即索引 idx_d_c。

⑩【推荐】防止因字段类型不同造成的隐式转换,导致索引失效。

⑪【参考】创建索引时,避免有如下极端误解:

 1)宁滥勿缺,认为有一个查询就需要建一个索引。

 2)宁缺勿滥,认为索引会占用存储空间,严重拖慢新增和更新速度。

 3)抵制唯一索引,认为业务的唯一性一律需要在应用层通过”先查后插“方式解决。

5.3SQL语句

①【强制】不要使用count(列名)或count(常量)代替cout(*),count(*)是SQL92定义的标准统计行数语法,跟数据库无关,跟NULL和非NULL无关。

②【强制】count(distinct col)计算该列除Null以外的不重复行数,注意count(distinct col1,col2)如果其中有一列全为NULL,另一列有不同值,返回结果仍为0。

③【强制】当某一列的值全为Null时,count(col)返回值为0,sun(col)返回值为Null,所以使用sum()要注意NPE问题。

④【强制】使用ISNULL()来判断是否是Null值。NULL值与任何值的直接比较都是NULL。

⑤【强制】代码中,写分页查询的逻辑时,若count为0应直接返回,避免执行后面的分页语句。

⑥【强制】避免使用外键与级联,一切外键概念必须在应用层解决。

⑦【强制】禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。

⑧【强制】数据订正时(特别是删除、修改记录操作)要先select,避免出现误删除,确认无误才能执行更新语句。

⑨【推荐】in操作能避免则避免,若实在无法避免,需要仔细评估in后边集合元素数量,控制在1000个以内。

⑩【参考】如果有国际化需要,所有的字符存储与表示,均以utf-8编码,注意字符统计函数的区别。

如果有需要存储表情,需要使用utf8mb4,注意它与utf8的区别。

⑪【参考】TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少,但 TRUNCATE 无事务且不触发 trigger,有可能造成事故,故不建议在开发代码中使用此语句。 

5.4ORM映射

①【强制】在表查询中,一律不要使用*作为查询字段列表,需要哪些字段必须明确写明。

②【强制】POJO类的布尔属性不能加is,而数据库对应字段必须加is_,要求在resultMap中进行字段与属性之间的映射。

③【强制】不要用resultClass当返回参数,即使所有类属性名与数据库字段一一对应,也需要定义;反过来,每一张表也要有一个POJO类与之对应。配置映射关系,使字段与DO类解耦,方便维护。

④【强制】sql.xml配置参数使用#{param},不要使用${param},后者容易SQL注入。

⑤【强制】IBatis自带的queryForList(String statementName,int start,int size)不推荐使用。

其实现方式是先查询出statementName对应的所有数据,然后再根据结果取subList,损耗性能。

⑥【强制】不允许直接拿HashMap或HashTable作为查询结果集的输出。

resultClass=”Hashtable”,会置入字段名和属性值,但是值的类型不可控。 

⑦【强制】更新数据库表记录时,必须同步更新记录对应的gmt_modified字段值为当前时间。

⑧【推荐】不要写一个大而全的数据跟新接口。传入一个POJO类,不管时不是自己目标更新字段,都进行update table col1,col2,col3...这是不对的,执行sql时,不要更新无改动字段,一是容易出错,二是效率低,三是增加binlog存储。

⑨【参考】@Transactional事务不要滥用,事务会影响数据库的QPS,另外使用事务要考虑各方面的回滚方案。包括缓存回滚,搜索引擎回滚,消息补偿,统计修正等。

⑩【参考】<isEqual>中的 compareValue 是与属性值对比的常量,一般是数字,表示相等时带 上此条件;<isNotEmpty>表示不为空且不为 null 时执行;<isNotNull>表示不为 null 值时 执行。 


6.工程结构

6.1应用分层

①【推荐】图中默认上层依赖于下层,箭头关系表示可直接依赖,如开放接口层可直接依赖于web层,也可直接依赖于service层,依此类推:

                                                  

 1)开放接口层:可直接封装成Service方法暴露成RPC接口;通过Web封装成Http接口;进行网管安全控制,流量控制等。

 2)终端显示层:各个端的模板渲染并执行显示的层,当前主要是velocity渲染、js渲染、jsp渲染、移动端展示等。

 3)Web层:主要是对访问控制进行转发,各类基本参数进行校验,或者不复用的业务简单处理。

 4)Service层:相对具体的业务逻辑服务层。
 5)Manager层:通用业务处理层,它具有以下特征:

       A.对第三方平台封装的层,预处理返回结果及转化异常信息。

       B.对Service通用能力的下沉,如缓存方案,中间件通用处理。

       C.与DAO层交互,对多个DAO的组合复用。

 6)外部接口或第三方平台:包括其它部门开放的RPC开放接口,基础平台,其它公司提供的http接口。

②【参考】(分层异常处理规约)

在DAO层,产生的异常类型有很多,无法用细粒度的异常进行catch,使用catch(Exception e)的方式,并throw new DAOException(e),不需要打印日志,因为日志在Manager/Service层一定需要捕获并打印到日志文件中,如果同台服务器再打印日志,浪费性能和存储。在Service层出现异常,必须记录出错日志到磁盘,尽可能带上参数信息,相当于保护案发现场。如果Manager层和Service层同机部属,日志处理方式与DAO层一致,如果是单独部属,则采用与Service层一致的处理方式。web层绝不应该继续往上抛异常,因为已经处于顶层,如果意识到这个异常将导致页面无法正常渲染,那么就应该跳转到友好错误页面,加上用户容易理解的错误提示信息。开放接口层需要将异常处理为错误码和错误信息方式返回。

③【参考】分层领域模型规约:

DO(Data Object):此对象与数据库表结构一一对应,通过DAO层向上传递数据源对象。

DTO(Data Transfer Object):数据传输对象,Manager或Sercice层向外传输的对象。

BO(Business Object):业务对象,由Service层输出的封装业务逻辑的对象。

AO(Application Object):应用对象,在web层和service层之间抽象的复用对象模型,极为贴近展示层,复用度不高。

VO(View Object):显示对象,通常是web层向模板引擎层传输的对象。

Query:数据查询对象,各层接收上层的查询请求。超过2个参数的查询封装,禁止使用Map类来传输。

6.2二方库依赖

①【强制】定义GAV遵循以下规则:

 1)GroupId格式:com.{公司/BU}.业务线[.子业务线],最多4级。

 {公司/BU} 例如alibaba/taobao/tmall等BU一级,子业务线可选。

 正例:com.taobao.jstorm ,com.alibaba.dubbo.register。

②【强制】二方库版本号命名方式:主版本号.次版本号.修订号

 1)主版本号:产品方向改变,或者大规模API不兼容,或者架构不兼容升级。

 2)次版本号:保持相对兼容性,增加主要功能特性,影响范围极小的API不兼容修改。

 3)修订好:保持完全兼容性,修复bug,新增次要功能特性。

③【强制】线上应用不要依赖SNAPSHOT版本(安全包除外)。

④【强制】二方库新增或升级,除保持功能点外其它jar包仲裁结果不变。如果有改变,必须明确评估和验证,建议进行dependency:reslove前后信息对比,如果仲裁结果完全不一致,那么通过dependency:tree命令,找出差异点,进行<excludes>排除jar包。

⑤【强制】二方库里可以定义枚举类型,参数可以使用枚举类型,但接口返回值不允许使用枚举类型,或者包含枚举类型的POJO对象。

⑥【强制】依赖于一个二方库群时,必须定义一个统一的版本变量,避免版本号不一致。

例如:依赖 springframework-core,-context,-beans,它们都是同一个版本,可以定义一 个变量来保存版本:${spring.version},定义依赖的时候,引用该版本。 

⑦【强制】禁止在子项目pom依赖中中出现相同的GroupId,ArtifactId,但是不同的Version。

在本地调试时会使用各子项目指定的版本号,但是合并成一个 war,只能有一个版本号 出现在最后的 lib 目录中。可能出现线下调试是正确的,发布到线上却出故障的问题。 

⑧【推荐】所有pom文件中的依赖声明,全部放在<dependencies>语句块中,所有版本仲裁放在<dependencyManagement>语句块中。

<dependencyManagement>里只是声明版本,并不实现引入,因此子项目需要显式的声 明依赖,version 和 scope 都读取自父 pom。而<dependencies>所有声明在主 pom 的 <dependencies>里的依赖都会自动引入,并默认被所有的子项目继承。

⑨【推荐】二方库不要有配置项,最低限度不要再增加配置项。

⑩【参考】为避免应用二方库的依赖冲突问题,二方库的发布者应当遵循以下原则:

 1)精简可控原则。移除一切不必要的API和依赖,只包含Service API,必要的领域模型对象,utils类,枚举和常量等。如果依赖其它二方库,尽量是provided引入,让二方库使用者去依赖具体的版本号,无log具体实现,只依赖日志框架。

 2)稳定可追溯原则。每个版本的变化应该被记录,二方库由谁维护,源代码在哪里,都需要能方便查到。除非用户主动升级版本,否则二方库的行为不应该发生变化。

6.3服务器

①【推荐】高并发服务器建议调小TCP协议的time_wait超时时间。

操作系统默认240 秒后,才会关闭处于 time_wait 状态的连接,在高并发访问下,服 务器端会因为处于 time_wait 的连接数太多,可能无法建立新的连接,所以需要在服务器上 调小此等待值。

正例:在 linux 服务器上请通过变更/etc/sysctl.conf 文件去修改该缺省值(秒):net.ipv4.tcp_fin_timeout = 30

②【推荐】调大服务器所支持的最大文件句柄数(FileDescriptor,简写为fd)。

主流操作系统将TCP/UDP连接采用与文件一样的方式去管理,即一个连接对应一个fd。主流的linux服务器默认最大支持fd数量为1024,当并发连接数量很大时,容易因为 fd 不足而出现“open too many files”错误,导致新的连接无法建立。

③【推荐】给jvm环境参数: -XX:+HeapDumpOnOutOfMemoryError参数,让jvm碰到OOM场景时,输出dump信息。

④【推荐】在线上生产环境,Jvm的最小堆-xmn和最大对-xmx设置一样大小的内存容量,避免在GC后调整堆大小带来的压力。

⑤【参考】服务器内部重定向使用forward;外部重定向使用url拼装工具类来生成,否则会带来Url维护不一致的问题和潜在安全风险。


7.设计规约

①【强制】存储方案和底层数据结构的设计获得评审一致通过,并沉淀成为文档。

有缺陷的底层数据结构容易导致系统风险上升,可扩展性下降,重构成本也会因为历史数据迁移和系统平滑过渡而陡然增加,所以存储方案和数据结构需要认真的设计和评审,生产环境提交执行后需要double check。

②【强制】在需求分析阶段,如果与系统交互的User超过一类,并且相关的User Case超过5个,使用用例图来表示更加清晰的结构化需求。

③【强制】如果某个业务对象的状态超过3个,使用状态图来表达,并且明确状态变化的各个触发条件。

状态图的核心是对象状态,首先明确对象有多少种状态,然后明确两两状态之间是否存在直接转换关系,再明确触发状态转换的条件是什么。

正例:淘宝订单状态有已下单、待付款、已付款、待发货、已发货、已收货等。比如已下单与已收货这两种状态之间是不可能有直接转换关系的。

④【强制】如果系统中某个功能上链路调用超过3个,使用时序图来表达且明确各个调用环节的输入与输出。

时序图反映了一系列对象间的交互与协作关系,清晰立体地反映系统的调用纵深链路。

⑤【强制】如果系统中的模型类超过5个,并且存在复杂的依赖关系,使用类图来表达且明确类之间的关系。

类图像建筑领域的施工图,如果搭平房,可能不需要,但如果建造蚂蚁 Z 空间大楼,肯定需要详细的施工图。

⑥【强制】如果系统中超过2个对象存在协作关系,并且需要表示复杂的处理流程,需要用活动图来表示。

活动图是流程图的扩展,增加了能够体现协作关系的对象泳道,支持表示并发等。 

⑦【推荐】需求分析与系统设计需在考虑主干功能的同时,需要充分评估异常流程与业务边界。

反例:用户在淘宝付款过程中,银行扣款成功,发送给用户扣款成功短信,但是支付宝入款时由于断网演练产生异常,淘宝订单页面依然显示未付款,导致用户投诉。 

⑧【推荐】类在设计和实现时要符合单一原则。

单一原则最易理解却是最难实现的一条规则,随着系统演进,很多时候,忘记了类设计的初衷。 

⑨【推荐】谨慎使用继承方式进行扩展,优先考虑聚合/组合方式来实现。

不得已使用继承的话,必须符合里氏代换原则,此原则说父类能够出现的地方子类一定能够出现,比如,“把钱交出来”,钱的子类美元、欧元、人民币等都可以出现。 

⑩【推荐】系统设计时,根据依赖倒置原则,尽量依赖抽象类与接口,有利于扩展与维护。

低层次模块依赖于高层次模块的抽象,方便系统间的解耦。

⑪【推荐】系统设计时,注意对扩展开放,对修改闭合。

极端情况下,交付的代码都是不可修改的,同一业务域内的需求变化,通过模块或类的扩展来实现。

⑫【推荐】系统设计阶段,公性业务或公共行为抽取出来的公共模块、公共配置、公共类、公共方法等,避免出现重复代码或重复配置的情况。

随着代码的重复次数不断增加,维护成本指数级上升。

⑬【推荐】避免出现如下误解:敏捷开发 = 讲故事 + 编码 + 发布。

敏捷开发是快速交付迭代可用的系统,省略多余的设计方案,摒弃传统的审批流程,但核心关键点上的必要设计和文档沉淀是需要的。

⑭【参考】系统设计的主要目的是:明确需求、理顺逻辑、后期维护,次要目的是指导编码。

避免为了设计而设计,系统设计文档有助于后期的系统维护,所以设计结果需要进行分类归档保存

⑮【参考】设计的本质就是识别和表达系统难点,找到系统的变化点,并隔离变化点。

世间众多设计模式目的是相同的,即隔离系统变化点。

⑯【参考】系统架构设计的目的:

 1)确定系统边界,确定系统在技术层面的做与不做。

 2)确定系统内,模块之间的关系。确定模块之间的依赖关系及模块的宏观输入与输出

 3)确定指导后续设计与演化的原则。使后续的子系统或模块设计在规定的框架内继续演化。

 4)确定非功能性需求。非功能性需求是指安全性,可用性,可扩展性。

 

附录:专有名词解释

1. POJO(Plain Ordinary Java Object): 在本手册中,POJO 专指只有 setter / getter / toString 的简单类,包括 DO/DTO/BO/VO 等。

2. GAV(GroupId、ArtifactctId、Version): Maven 坐标,是用来唯一标识 jar 包。

3. OOP(Object Oriented Programming): 本手册泛指类、对象的编程处理方式。

4. ORM(Object Relation Mapping): 对象关系映射,对象领域模型与底层数据之间的转换, 本文泛指 iBATIS, mybatis 等框架。

5. NPE(java.lang.NullPointerException): 空指针异常。

6. SOA(Service-Oriented Architecture): 面向服务架构,它可以根据需求通过网络对松散 耦合的粗粒度应用组件进行分布式部署、组合和使用,有利于提升组件可重用性,可维护性。
 7. IDE(Integrated Development Environment): 用于提供程序开发环境的应用程序,一般 包括代码编辑器、编译器、调试器和图形用户界面等工具,本《手册》泛指 IntelliJ IDEA 和 eclipse。

8. OOM(Out Of Memory): 源于 java.lang.OutOfMemoryError,当 JVM 没有足够的内存 来为对象分配空间并且垃圾回收器也无法回收空间时,系统出现的严重状况。

9. 一方库: 本工程内部子项目模块依赖的库(jar 包)。

10. 二方库: 公司内部发布到中央仓库,可供公司内部其它应用依赖的库(jar 包)。 11. 三方库: 公司之外的开源库(jar 包)。 


使用IDEA开发的同学可以下载阿里提供的代码规范检测开源插件:https://github.com/alibaba/p3c

 

参考资料:阿里巴巴java技术开发手册。

 

发布了89 篇原创文章 · 获赞 69 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/lovexiaotaozi/article/details/102816175