1、简介
统一规范标准将有助于提高行业编码规范化水平,帮助行业人员提高开发质量和效率、大大降低代码维护成本
2017年年初,首次公开了《阿里巴巴Java开发手册》,自从第一个版本起,倍受业界关注。为了让开发者更加方便、快速的将规范推动并实行起来,阿里巴巴基于手册内容,研发了一套自动化的IDE检测插件(IDEA、Eclipse), 该插件在扫描代码后,将不符合《手册》的代码按Blocker/Critical/Major三个等级显示在下方,甚至在IDEA上,还基于Inspection机制提供了实时检测功能,编写代码的同时也能快速发现问题所在。对于历史代码,部分规则实现了批量一键修复的功能,提升代码质量,提高团队研发效能。
接下来我们先来学习学习阿里编程规范再基于Intellij来学习阿里插件的安装及使用:
2、命名:
-
代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
反例:_name / name / n a m e / n a m e / n a m e name / name_ / name name/name/name / name -
代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。
说明:正确的英文拼写和语法可以让阅读者易于理解,避免歧义。注意,纯拼音命名方式更要避免采用。比如:正例:renminbi / alibaba / taobao / youku / hangzhou 等国际通用的名称,可视同英文。 反例:DaZhePromotion [打折] / getPingfenByName() [评分] / int 某变量 = 3
-
在 long 或者 Long 赋值时,数值后使用大写的 L,不能是小写的 l,小写容易跟数字 1 混淆,造成误解。
-
杜绝完全不规范的缩写,避免望文不知义。
反例:AbstractClass“缩写”命名成 AbsClass;condition“缩写”命名成 condi,此类随意缩写严重降低了代码的可阅读性。 -
为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词组合来表达其意。比如:
正例:在 JDK 中,表达原子更新的类名为:AtomicReferenceFieldUpdater。 反例:int a 的随意命名方式。
-
常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
-
不允许硬编码,即不允许任何魔法值( 即未经定义的常量 ) 直接出现在代码中。比如:
正例:PAGE_SIZE / THREAD_POOL_SIZE 反例: String key =” Id # taobao _”+ tradeId;
-
在常量与变量的命名时,表示类型的名词放在词尾,以提升辨识度。比如:
正例:startTime / workQueue / nameList / TERMINATED_THREAD_COUNT 反例:startedAt / QueueOfWork / listName / COUNT_TERMINATED_THREAD
-
不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。
说明:大而全的常量类,杂乱无章,使用查找功能才能定位到修改的常量,不利于理解和维护。
正例:缓存相关常量放在类 CacheConsts 下;系统配置相关常量放在类 ConfigConsts 下。 -
如果变量值仅在一个固定范围内变化用 enum 类型来定义。
-
类型与中括号紧挨相连来表示数组。
正例:定义整形数组 int[] arrayDemo;
反例:在 main 参数中,使用 String args[]来定义。 -
方法名、参数名、成员变量、局部变量都统一使用lowerCamelCase 风格,必须遵从驼峰形式。
正例: localValue / getHttpMessage() / inputUserId -
类名使用UpperCamelCase 风格,必须遵从驼峰形式,但以下情形例外: ( 领域模型的相关命名 )DO / BO / DTO / VO 等。比如:
正例: MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion 反例: macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion
-
如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式。
说明:将设计模式体现在名字中,有利于阅读者快速理解架构设计理念。比如:public class OrderFactory; public class LoginProxy; public class ResourceObserver;
-
接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的简洁性,并加上有效的 Javadoc 注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量。
-
包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。
正例:应用工具类包名为 com.alibaba.ai.util、类名为 MessageUtils(此规则参考 spring 的框架结构) -
抽象类命名使用 Abstract 或 Base 开头 ;
-
异常类命名使用 Exception 结尾 ;
-
测试类命名以它要测试的类的名称开始,以 Test 结尾;
-
枚举类名建议带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。
-
避免在子父类的成员变量之间、或者不同代码块的局部变量之间采用完全相同的命名,使可读性降低。
说明:子类、父类成员变量名相同,即使是 public 类型的变量也是能够通过编译,而局部变量在同一方法内的不同代码块中同名也是合法的,但是要避免使用。对于非 setter/getter 的参数名称也要避免与成员变量名称相同。 -
POJO 类中布尔类型的变量,都不要加 is ,否则部分框架解析会引起序列化错误。
-
Service / DAO 层方法命名规约
1)获取单个对象的方法用 get 做前缀。
2)获取多个对象的方法用 list 做前缀(习惯:getXXXList)。
3)获取统计值的方法用 count 做前缀。
4)插入的方法用 save( 推荐 ) 或 insert 做前缀。
5)删除的方法用 remove( 推荐 ) 或 delete 做前缀。
6)修改的方法用 update 做前缀(或modify)。 -
领域模型命名规约
1)数据对象: xxxDO , xxx 即为数据表名。
2)数据传输对象: xxxDTO , xxx 为业务领域相关的名称。
3)展示对象: xxxVO , xxx 一般为网页名称。
4)POJO 是 DO / DTO / BO / VO 的统称,禁止命名成 xxxPOJO 。 -
方法名的命名,需要使用“动宾结构短语”或“是动词+表语结构短语”,如果宾语是一个对象集合,还是最好使用复数。比如:
增加:createXxx(Xxx xxx); 修改:modifyXxx(Xxx xxx); 查询: 获取单个对象: getXxx(int id); 不分条件列举: listXxxs(); 对有条件查询: searchXxxs(条件); 删除:deleteXxxs()
3、格式规约
-
如果是大括号内为空,则简洁地写成{}即可,大括号中间无需换行和空格;如果是非空代码块则:
1) 左大括号前不换行。
2) 左大括号后换行。
3) 右大括号前换行。
4) 右大括号后还有 else 等代码则不换行;表示终止的右大括号后必须换行。 -
左小括号和字符之间不出现空格;同样,右小括号和字符之间也不出现空格;而左大括号前需要空格。
-
if/for/while/switch/do 等保留字与括号之间都必须加空格。
-
任何二目、三目运算符的左右两边都需要加一个空格。
说明:运算符包括赋值运算符=、逻辑运算符&&、加减乘除符号等。 -
采用 4 个空格缩进,禁止使用 tab 字符。
-
注释的双斜线与注释内容之间有且仅有一个空格。
-
单行太长需换行
-
方法体内的执行语句组、变量的定义语句组、不同的业务逻辑之间或者不同的语义之间插入一个空行。相同业务逻辑和语义之间不需要插入空行。
4、注释规约
-
类、类属性、类方法的注释必须使用 Javadoc 规范,使用/*内容/格式,不得使用 //xxx 方式。
-
所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释、除了返回值、参数、 异常说明外,还必须指出该方法做什么事情,实现什么功能。对子类的实现要求,或者调用注意事项,需要一并说明。
-
方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释,注意与代码对齐。
-
所有的枚举类型字段必须要有注释,说明每个数据项的用途。
-
代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑等的修改。
-
注释掉的代码尽量要配合说明,而不是简单的注释掉。说明:代码被注释掉有两种可能性:
1)后续会恢复此段代码逻辑。如果没有备注信息,难以知晓注释动机。
2)永久不用。建议直接删掉(代码仓库保存了历史代码)。 -
特殊注释标记,请注明标记人与标记时间。注意及时处理这些标记,通过标记扫描, 经常清理此类标记。线上故障有时候就是来源于这些标记处的代码。
1)待办事宜(TODO):( 标记人,标记时间,[预计处理时间]) 表示需要实现,但目前还未实现的功能。这实际上是一个 Javadoc 的标签,目前的 Javadoc还没有实现,但已经被广泛使用。只能应用于类,接口和方法(因为它是一个 Javadoc 标签)。
2)错误,不能工作(FIXME):(标记人,标记时间,[预计处理时间])在注释中用 FIXME 标记某代码是错误的,而且不能工作,需要及时纠正的情况。
5、OOP规约
-
避免通过类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。
-
所有的覆写方法,必须加@ Override 注解。
-
对外暴露的接口签名,原则上不允许修改方法签名,避免对接口调用方产生影响。
-
接口过时必须加@Deprecated 注解,并清晰地说明采用的新接口或者新服务是什么。
-
在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。 不要在方法体内定义:
Pattern pattern = Pattern.compile(规则);
-
获取当前毫秒数 System.currentTimeMillis(); 而不是 new Date().getTime();
说明:如果想获取更加精确的纳秒级时间值,用 System.nanoTime()。在 JDK8 中,针对统计时间等场景,推荐使用Instant 类。 -
对于“明确停止使用的代码和配置”,如方法、变量、类、配置文件、动态配置属性等要坚决从程序中清理出去,避免造成过多垃圾。
-
Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals。比如:
正例: ”test”.equals(object); 反例: object.equals( ”test” );
-
所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较。(注意空指针)
说明:对于 Integer var = ?在-128 至 127 之间的赋值, Integer 对象是在IntegerCache . cache 产生,会复用已有对象,这个区间内的 Integer 值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用 equals 方法进行判断。 -
关于基本数据类型与包装数据类型的使用标准如下:
1)所有的 POJO 类属性必须使用包装数据类型。
2)RPC 方法的返回值和参数必须使用包装数据类型。
3)所有的局部变量【推荐】使用基本数据类型。 -
序列化类新增属性时,请不要修改 serialVersionUID 字段,避免反序列失败 ; 如果完全不兼容升级,避免反序列化混乱,那么请修改 serialVersionUID 值。
-
构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中。
-
使用索引访问用 String 的 split 方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有抛 IndexOutOfBoundsException 的风险。说明:
String str = “a,b,c,,”; String[] ary = str.split(“,”); //预期大于 3,结果是 3 System.out.println(ary.length);
-
当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起,便于阅读。
-
类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter / setter方法。
-
final 可提高程序响应效率,声明成 final 的情况:
1)不需要重新赋值的变量,包括类属性、局部变量。
2)对象参数前加 final ,表示不允许修改引用的指向。
3)类方法确定不允许被重写。
6、集合处理
-
关于 hashCode 和 equals 的处理,遵循如下规则:
1)只要重写 equals ,就必须重写 hashCode 。
2)因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须重写这两个方法。
3)如果自定义对象做为 Map 的键,那么必须重写 hashCode 和 equals 。 -
不要在 foreach 循环里进行元素的 remove / add 操作。 remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。
反例:List<String> a = new ArrayList<String>(); a.add(“1”); a.add(“2”); for (String temp : a) { if(“1”.equals(temp)){ a.remove(temp); //不要这么做 } }
说明:以上代码的执行结果肯定会出乎大家的意料,那么试一下把“1”换成“2”,会是同样的结果吗?
正例:Iterator<String> it = a.iterator(); while(it.hasNext()){ String temp = it.next(); if(删除元素的条件){ it.remove(); } }
-
集合初始化时,尽量指定集合初始值大小。
说明: ArrayList 尽量使用 ArrayList(int initialCapacity) 初始化。 -
使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。
说明:keySet 其实是遍历了 2 次,一次是转为 Iterator 对象,另一次是从 hashMap 中取出 key 所对应的 value。而 entrySet 只是遍历了一次就把 key 和 value 都放到了 entry 中,效 率更高。如果是 JDK8,使用 Map.foreach 方法。Map<String, String> map = new HashMap<String, String>(); map.put(“1”, “@@”); map.put(“2”, “#”); /** * JDK8推荐使用 */ map.forEach((K, V) -> { System.out.println(“Key : ” + K + “ Value : ” + V); }); /** * foreach推荐使用 */ for (Map.Entry<String, String> entry : map.entrySet()) { System.out.println(“Key : ” + entry.getKey()); System.out.println(“Value : ” + entry.getValue()); } /** * 不推荐使用 */ for (String key : map.keySet()) { System.out.println(“Key : ” + key); System.out.println(“Value : ” + map.get(key)); }
-
Map 类集合 K/V 能不能存储 null 值的情况,如下表格:
集合类 | Key | Value | Super | 说明 |
---|---|---|---|---|
Hashtable | 不允许为 null | 不允许为 null | Dictionary | 线程安全 |
ConcurrentHashMap | 不允许为 null | 不允许为 null | AbstractMap | 分段锁技术 |
TreeMap | 不允许为 null | 允许为 null | AbstractMap | 线程不安全 |
HashMap | 允许为 null | 允许为 null | AbstractMap | 线程不安全 |
7、控制语句
-
在每一个switch 块内,每个 case 要么通过 break/return 等来终止,要么注释说明程序将继续执行到哪一个 case 为止;在每一个 switch 块内,都必须包含一个 default 语句并且放在最后,即使它什么代码也没有。
-
在 if/else/for/while/do 语句中必须使用大括号,即使只有一行代码。
-
尽量少用 else, if-else 的方式可以改写成:
if(condition){ … return obj; } // 接着写 else 的业务逻辑代码;
说明:如果非得使用if()…else if()…else…方式表达逻辑,【强制】请勿超过3层,超过请使用状态设计模式或者卫语句。
卫语句示例:public void today() { if (isBusy()) { System.out.println(“change time.”); return; } if (isFree()) { System.out.println(“go to travel.”); return; } System.out.println(“stay at home to learn Alibaba Java Coding Guidelines.”); return; }
-
除常用方法(如 getXxx/isXxx)外,不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。
正例:boolean existed = (file.open(fileName, “w”) != null) && (…) || (…); if (existed) { … }
反例:
if ((file.open(fileName, “w”) != null) && (…) || (…)) { … }
-
方法中需要进行参数校验的场景:
1)调用频次低的方法。
2)执行时间开销很大的方法,参数校验时间几乎可以忽略不计,但如果因为参数错误导致中间执行回退,或者错误,那得不偿失。
3)需要极高稳定性和可用性的方法。
4)对外提供的开放接口,不管是RPC/API/HTTP接口。
5)敏感权限入口。 -
方法中不需要参数校验的场景:
1)极有可能被循环调用的方法,不建议对参数进行校验。但在方法说明里必须注明外部参数检查。
2)底层的方法调用频度都比较高,一般不校验。一般 DAO 层与 Service 层都在同一个应用中,部署在同一 台服务器中,所以 DAO 的参数校验,可以省略。
3)被声明成private只会被自己代码所调用的方法,如果能够确定调用方法的代码传入参数已经做过检查或者肯定不会有问题,此时可以不校验参数。
8、异常
-
异常不要用来做流程控制,条件控制,因为异常的处理效率比条件分支低。
-
对大段代码进行 try-catch,这是不负责任的表现。catch 时请分清稳定代码和非稳 定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理。
-
捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。
-
有 try 块放到了事务代码中,catch 异常后,如果需要回滚事务,一定要注意手动回滚事务。
-
不能在 finally 块中使用 return,finally 块中的 return 返回后方法结束执行,不会再执行 try 块中的 return 语句。
-
方法的返回值可以为 null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回 null 值。调用方需要进行 null 判断防止 NPE 问题。
-
防止 NPE,是程序员的基本修养,注意 NPE 产生的场景:
1)返回类型为包装数据类型,有可能是null,返回int值时注意判空。
反例:public int fun(){ return Integer 对象}; 如果为 null,自动解箱抛 NPE。
2)数据库的查询结果可能为null。
3)集合里的元素即使isNotEmpty,取出的数据元素也可能为null。
4)远程调用返回对象,一律要求进行NPE判断。
5)对于Session中获取的数据,建议NPE检查,避免空指针。
6)级联调用obj.getA().getB().getC();一连串调用,易产生NPE。 -
在代码中使用“抛异常”还是“返回错误码”,对于公司外的 http/api 开放接口必须使用“错误码”;而应用内部推荐异常抛出;跨应用间 RPC 调用优先考虑使用 Result 方式,封装 isSuccess、“错误码”、“错误简短信息”。
说明:关于 RPC 方法返回方式使用 Result 方式的理由:
1)使用抛异常返回方式,调用方如果没有捕获到就会产生运行时错误。
2)如果不加栈信息,只是new自定义异常,加入自己的理解的error message,对于调用端解决问题的帮助不会太多。如果加了栈信息,在频繁调用出错的情况下,数据序列化和传输的性能损耗也是问题。 -
避免出现重复的代码(Don’t Repeat Yourself),即DRY原则。
9、日志
-
应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
import org.slf4j.Logger; import org.slf4j.LoggerFactory; private static final Logger logger = LoggerFactory.getLogger(Abc.class);
-
日志文件推荐至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。
-
应用中的扩展日志(如打点、临时监控、访问日志等)命名方式: appName_logType_logName.log。其中:
logType:日志类型,推荐分类有 stats/desc/monitor/visit 等;
logName:日志描述。这种命名的好处:通过文件名就可知道日志文件属于什么应用、什么类型、什么目的、也有利于归类查找。
正例:mppserver 应用中单独监控时区转换异常,如: mppserver_monitor_timeZoneConvert.log
说明:推荐对日志进行分类,错误日志和业务日志尽量分开存放,便于开发人员查看,也便于通过日志对系统进行及时监控。 -
对 trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方式。
说明:logger.debug(“Processing trade with id: ” + id + ” symbol: ” + symbol); 如果日志级别是 warn,上述日志不会打印,但是会执行字符串拼接操作,如果 symbol 是对象, 会执行 toString()方法,浪费了系统资源,执行了上述操作,最终日志却没有打印。 正例:(条件)if (logger.isDebugEnabled()) { logger.debug(“Processing trade with id: ” + id + ” symbol: ” + symbol); }
正例:(占位符)
logger.debug(“Processing trade with id: { } symbol : { } “, id, symbol);
-
避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false。
正例: -
可以使用warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。注意日志输出的级别,error 级别只记录系统逻辑出错、异常等重要的错误信息。如非必要,请不要在此场景打出 error 级别。
-
谨慎地记录日志。生产环境禁止输出 debug 日志;有选择地输出 info 日志;如果使 用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志。
说明:大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。记录日志时请思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处?
10、单元测试
-
好的单元测试必须遵守 AIR 原则。
说明:单元测试在线上运行时,感觉像空气(AIR)一样并不存在,但在测试质量的保障上,却是非常关键的。好的单元测试宏观上来说,具有自动化、独立性、可重复执行的特点。
A:Automatic(自动化)
I:Independent(独立性)
R:Repeatable(可重复) -
单元测试应该是全自动执行的,并且非交互式的。测试框架通常是定期执行的,执行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试。单元测试中不准使用 System.out 来进行人肉验证,必须使用 assert 来验证。
-
保持单元测试的独立性。为了保证单元测试稳定可靠且便于维护,单元测试用例之间决不能互相调用,也不能依赖执行的先后次序。
-
对于单元测试,要保证测试粒度足够小,有助于精确定位问题。单测粒度至多是类级别,一般是方法级别。
说明:只有测试粒度小才能在出错时尽快定位到出错位置。单测不负责检查跨类或者跨系统的交互逻辑,那是集成测试的领域。 -
核心业务、核心应用、核心模块的增量代码确保单元测试通过。
说明:新增代码及时补充单元测试,如果新增代码影响了原有单元测试,请及时修正。 -
单元测试的基本目标:语句覆盖率达到 70%;核心模块的语句覆盖率和分支覆盖率都要达到 100%
说明:在工程规约的应用分层中提到的 DAO 层、Manager 层、可重用度高的 Service都应该进行单元测试。 -
编写单元测试代码遵守 BCDE 原则,以保证被测试模块的交付质量。
B:Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。
C:Correct,正确的输入,并得到预期的结果。
D:Design,与设计文档相结合,来编写单元测试。
E:Error,强制错误信息输入(如:非法数据、异常流程、非业务允许输入等),并得到预期的结果。 -
和数据库相关的单元测试,可以设定自动回滚机制,不给数据库造成脏数据。或者对单元测试产生的数据有明确的前后缀标识。
-
在设计评审阶段,开发人员需要和测试人员一起确定单元测试范围,单元测试最好覆盖所有测试用例(UC)。
-
单元测试作为一种质量保障手段,不建议项目发布后补充单元测试用例,建议在项目提测前完成单元测试。
11、工程规约
图中默认上层依赖于下层,箭头关系表示可直接依赖,如:开放接口层可以依赖于Web 层,也可以直接依赖于 Service 层,依此类推。
-
终端显示层:各个端的模板渲染并执行显示层。当前主要是 velocity 渲染、JS 渲染、JSP 渲 染、移动端展示层等。
-
开放接口层:可直接封装 Service接口暴露成 RPC 接口、通过 Web 封装成 http 接口、网关控制层等。
-
Web 层(Controller):主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。
-
Service 层:相对具体的业务逻辑服务层。
-
Manager 层:通用业务处理层,它有如下特征:
1)对第三方平台封装的层,预处理返回结果及转化异常信息;
2)对Service层通用能力的下沉,如缓存方案、中间件通用处理;
3)与DAO层交互,对DAO的业务通用能力的封装。 -
DAO 层:数据访问层,与底层 MySQL、Oracle、Hbase 进行数据交互。
-
外部接口或第三方平台:包括其它部门 RPC 开放接口,基础平台,其它公司的 HTTP 接口。
-
分层异常处理规约:
- 在 DAO 层,产生的异常类型有很多,无法用细粒度的异常进行catch,使用catch(Exception e)方式,并throw new DAOException(e),不需要打印日志。因为日志在 Manager/Service 层一定需要捕获并打到日志文件中去,如果同台服务器再打日志,浪费性能和存储。
- 在 Service 层出现异常时,必须记录出错日志到磁盘,尽可能带上参数信息,相当于保护案发现场。
- 如果 Manager 层与 Service 同机部署,日志方式与 DAO 层处理一致;如果是单独部署,则采用与 Service 一致的处理方式
- Web 层绝不应该继续往上抛异常,因为已经处于顶层,如果意识到这个异常将导致页面无法正常渲染,那么就应该直接跳转到友好错误页面,加上用户容易理解的错误提示信息。
- 开放接口层要将异常处理成错误码和错误信息方式返回。
-
分层领域模型规约:
- DO(Data Object):与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。(Entity)
- DTO(Data Transfer Object):数据传输对象,Service 和 Manager 向外传输的对象。
- BO(Business Object):业务对象。可以由 Service 层输出的封装业务逻辑的对象。
- QUERY:数据查询对象,各层接收上层的查询请求。注:超过 2 个参数的查询封装,禁止使用 Map 类来传输。
- VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象。
“勿以善小而不为,勿以恶小而为之”、“细节决定成败”,有太多的名言告诉我们,要注重细节。一个优秀的程序员,必须要有坚实的基础,而对于命名规则这样容易掌握的基础更是应该如此。
转载:https://blog.csdn.net/lianghecai52171314/article/details/102711958