以前项目中整理的,目前华为阿里都有正式的代码规范流行了。这里仅仅提供一下参考。
目前我们大部分的项目,都是web为主,少不了要和数据库打交道的。一般情况下我们会将代码整体的划分为3个module(当然如果项目比较大,module会比较多):
1 xxx-biz :主要是底层数据库存取,各种manager实现,对外提供的service接口实现以及处理业务流程控制的各种AO等。
2 xxx-protocal:主要存放对外提供service的接口以及对外提供接口返回的数据模型,一般称之为DTO。
3 xxx-web:主要实现web成处理的相关逻辑,比如 webx的screen,action,control)。
有人疑惑 AO(Application Object)它是各种业务流程控制的地方,为什么不放到 xxx-web 层中那?
确实,如果相对来说你的项目如果相对来说比较独立,比如说不对外提供 service ,放到web层来处理业务逻辑确实层次更分明一些。
但是基于以下两点考虑,最好还是将AO放到xxx-biz层来做,不要放到web层:
1) 一般情况下我们都会提供各种对外的接口,如果AO放到web层,碰到一些 APP只是提供各种service, web层独立部署,这个时候 AO就不能够被各种service的实现类公用。
2) 一些和事务相关的业务逻辑,也只能放到server端来做,放到web层做事务的话,可能是使用的远程调用,在淘宝就是HSF调用,这样就不能够保证事务正确性了。
一般web工程分层都是按照持久层,业务逻辑层,展现层来划分。
对应到淘宝的webx中持久层就是各种dao,业务逻辑层就是manager和AO。
展现层就是各种screen,action,control以及velocity和vm等。
Ø webx中,dao,manager,ao到底具体的分工、底职责是什么?
DAO:
这个比较好理解,主要处理项目中涉及到数据存储和获取方面的功能,一般使用ibatis,hibernate,spring jdbc等。
Mananger:
Manager层主要是调用dao处理简单业务逻辑,比如一个功能需要几张数据库表中的数据,这个地方需要调用不同的dao,将数据库中获取到的entity对象转换组装成业务数据模型DTO。
数据是否需要添加cache等功能,也可以在这一层来处理。
AO:
AO主要负责screen传递进来的数据检验,业务流程转向控制,事务管理。
Screen中最好调用各个AO来处理业务,不要直接调用dao,manager来处理,目前我们为了图方便,一般似乎都这么干,以后这个地方要杜绝。
Ø Dao,manager,ao,service中的异常如何抛出?
Dao:
一般直接throw DataAccessException 就可以,当然你也可以自己封装一个和项目相关的xxxdaoException也可以,不过觉得没有必要。
但是不要用catch语句吃掉异常, 方便manager层知道调用出现了什么问题,快速定位sql错误,代码也比较简洁。
Manager:
Manager层涉及到DAO层的操作,要捕获dao层抛出的DataAccessException。
Manager层的方法最好是统一抛出ManagerException,如果确实没有异常捕捉,那就不用抛出了。
Manager层的代码只处理业务上的异常,不要捕获运行时异常(不知道运行时异常的最好去baidu一下)。
AO:
一般不需要抛出异常。根据不同的业务流程,返回不同的结果,到screen中自行处理即可。
如果在screen中用户提交的数据没有做数据验证,对非法的数据就需要抛出异常了。
我比较赞同抛异常的用法,AO最好要对数据安全合法性做检查,不要留到screen中去做,这样AO也同样可以被一些对外提供的远程调用公用,代码也更健壮。
Service:
这里指的service就是指提供给外部远程调用的一些接口,如淘宝的HSF。
这些service一定要对调用参数的合法性做严格校验,尤其是需要入库的接口。
这些接口我觉得最好抛出异常(如果确实没必要也可以不用抛出),比如可以统一抛出一个项目封装的异常(DaogouServiceException),如果调用失败,明确的告诉调用方什么地方出问题了、出了什么问题、也方便调用方定位问题。
Ø DTO,VO和entitiy domain,POJO的区别与联系?
entitiy domain :对应数据库中的表结构或者将其称之为 PO,persistent Object)。
这个对象中的属性,如long类型,最好使用对象类型的Lang来表示,能够看出数据库中字段的真实情况。
DTO(data transfer object):
给远程调用如hsf使用,不使用entity domain是为防止客户端暴露server端的数据表结构。
Manager 调用dao,封装 Entity domain,传输给AO。
Manager转换DTO为 entity domain ,调用dao对数据进行持久化。
VO(value Object):
AO层转换DTO为VO后给展现层使用,如velocity,jsp中使用的对象。
页面form中的数据映射为VO,然后再转换为DTO传输到manager层处理业务,在stucts时代就是FormBean充当VO的。
POJO:plain ordinary java object,就是一个普通的java对象。
搞了真么多名词,总结一下那就是如下对应关系:
Web层的数据表示是VO 。
业务层的数据表示是(DTO,也有叫BO,business object 业务对象 ,这个和dto的区别我也没搞清楚,估计就是叫法和所包含的范围有所不同吧,这里统一使用DTO表示)。
持久化的数据表示是entity domain(PO),其对应于数据库中的表字段。
为什么要定义出来这么多的对象,相互之间传递转化那?
在一个规范的J2EE架构中初衷是不同层的数据表示应该被限制在层内,而不应该扩散到其它层,这样可以降低层间的耦合性,提高J2EE架构整体的可维护性和可扩展性。
比如说Web层的逻辑进行了修改,那么只需要修改VO的结构,而不需要触动业务层和持久层的代码修改。
同样滴,当数据库表进行了小的调整,那么也只需要修改持久层数据表示,而不需要触动业务层代码和Web层代码。
当然上面都是理想化的状态。一般情况下我们都不会严格按照上面的规范去做,尤其是taobao使用的是半对象化的ibatis持久化工具,就更没有必要遵守上面的规则了。
取而代之的是直接定义一个POJO(我们内部一般称之为DO),从数据库层到业务逻辑层,再到页面展示层一杆到底都使用这个POJO,然后传输给velocity渲染引擎去渲染。
自己也比较赞成这种使用方式,原因就是简单!现实中没有几个项目能够做到各个层之间的对象模型修改后不跟着变化的,或许可能是自己技术内功不够导致认识错误吧。
我觉得包结构的划分不要太细,本来没有多少代码,添加太多包,查找起来都比较费时。
目前我们导购店铺中,感觉划分也比较乱,让人一看心里就烦躁的感觉。
一般我觉得有如下几个包就可以了:
Com.taobao.daogou.biz.dao(数据库相关操作)
Com.taobao.daogou.biz.manager(简单业务逻辑操作)
Com.taobao.daogou.biz.ao(业务流程控制以及事务控制)
Com.taobao.daogou.biz.service(对外提供的远程调用实现)
Com.taobao.daogou.biz.domain (存放 前面提到的POJO)
接口与实现类最好放到同一个包内,比如说接口SiteInfoDAO.Java,其对应的实现类是SiteInfoDAOImpl.java。
Ø java源文件
1)1个Java源文件的最大推荐长度是500行。
2)1个method的最大推荐长度是50~100行
3)Java源文件中每行的长度要求不超过80个字符
4)Java源文件的结构书写顺序如下:
源文件开始处的注释
package 语句和 import 语句
class/interface的代码
5)import语句按以下顺序声明:
标准API(java.xx)
Extension API(javax.xxx)
其他外部API(org.w3c.dom.xxx)
本系统的API(com.taobao.common.StringUtils)
Ø 命名规范
命名规范使程序更易读,从而更易于理解。它们也可以提供一些有关标识符功能的信息,以助于理解代码。
标识符类型
命名规则
例子
包(Package)
一个唯一package名的前缀总是全部小写的ASCII字母并且是一个顶级域名,通常是com,edu,gov,mil,net,org,或1981年ISO 3166标准所指定的标识国家的英文双字符代码。package名的后续部分根据不同机构各自内部的命名规范而不尽相同。这类命名规范可能以特定目录名的组成来区分部门(department),项目(project),机器(machine),或注册名(login names)。
com.sun.eng
com.apple.quicktime.v2
类(Classe)
命名规则:class名是个一名词,采用大小写混合的方式,每个单词的首字母大写。尽量使你的class名简洁而富于描述。使用完整单词,避免缩写词(除非该缩写词被更广泛使用,像URL,HTML)
class SiteInfo;
class ImageSprite;
接口(Interface)
命名规则:大小写规则与class名相似
interface asterDelegate;
interface Storing;
方法(Method)
method名是一个动词,采用大小写混合的方式,第一个单词的首字母小写,其后单词的首字母大写。Method命名时,尽可能参照 Java API中类似功能的方法名
run();
runFast();
getBackground();
变量(Variable)
除了变量名外,所有实例,包括类,均采用大小写混合的方式,第一个单词的首字母小写,其后单词的首字母大写。变量名不应以下划线或美元符号开头,尽管这在语法上是允许的。
变量名应简短且富于描述。变量名的选用应该易于记忆,即,能够指出其用途。尽量避免单个字符的变量名,除非是一次性的临时变量。临时变量通常被取名为i,j,k,m和n,它们一般用于整型;c,d,e,它们一般用于字符型。
char c;
int i;
float myWidth;
实例变量(Instance Variable)
大小写规则和变量名相似,除了前面需要一个下划线
int _employeeId;
String _name;
Customer _customer;
常量(Constant)
类常量和ANSI常量的声明,应该全部大写,单词间用下划线隔开。(尽量避免ANSI常量,容易引起错误)
static final int MIN_WIDTH = 4;
static final int MAX_WIDTH = 999;
static final int GET_THE_CPU = 1;
Ø 变量声明与初始化
推荐一行一个声明,因为这样以利于写注释。亦即,
int level; // indentation level
int size; // size of table
要优于
int level, size;
不要将不同类型变量的声明放在同一行,例如:
int foo, fooarray[]; //WRONG!
注意:上面的例子中,在类型和标识符之间放了一个空格,另一种被允许的替代方式是使用制表符:
int level; // indentation level
int size; // size of table
Object currentEntry; // currently selected table entry
初始化:
尽量在声明局部变量的同时初始化。唯一不这么做的理由是变量的初始值依赖于某些先前发生的计算。
只在代码块的开始处声明变量。(一个块是指任何被包含在大括号’{‘和’}'中间的代码。)不要在首次用到该变量时才声明之。
这会把注意力不集中的程序员搞糊涂,同时会妨碍代码在该作用域内的可移植性。
void myMethod() {
int int1 = 0; // beginning of method block
if (condition) {
int int2 = 0; // beginning of 'if' block
...
}
}
该规则的一个例外是for循环的索引变量
for (int i = 0; i < maxLoops; i++) { … }
避免声明的局部变量覆盖上一级声明的变量。例如,不要在内部代码块中声明相同的变量名:
int count;
...
myMethod() {
if (condition) {
int count = 0; // AVOID!
...
}
...
}
Ø 排版与缩进
尽量避免一行的长度超过80个字符,因为很多终端和工具不能很好处理之。
当一个表达式无法容纳在一行内时,可以依据如下一般规则断开之:
在一个逗号后面断开
在一个操作符前面断开
宁可选择较高级别(higher-level)的断开,而非较低级别(lower-level)的断开
新的一行应该与上一行同一级别表达式的开头处对齐
如果以上规则导致你的代码混乱或者使你的代码都堆挤在右边,那就代之以缩进8个空格。
以下是断开方法调用的一些例子:
someMethod(longExpression1, longExpression2, longExpression3,
longExpression4, longExpression5);
var = someMethod1(longExpression1,
someMethod2(longExpression2,
longExpression3));
以下是两个断开算术表达式的例子。前者更好,因为断开处位于括号表达式的外边,这是个较高级别的断开。
longName1 = longName2 * (longName3 + longName4 - longName5)
+ 4 * longname6; //PREFFER
longName1 = longName2 * (longName3 + longName4
- longName5) + 4 * longname6; //AVOID
以下是两个缩进方法声明的例子。前者是常规情形。后者若使用常规的缩进方式将会使第二行和第三行移得很靠右,所以代之以缩进8个空格
//CONVENTIONAL INDENTATION
someMethod(int anArg, Object anotherArg, String yetAnotherArg,
Object andStillAnother) {
...
}
//INDENT 8 SPACES TO AVOID VERY DEEP INDENTS
private static synchronized horkingLongMethodName(int anArg,
Object anotherArg, String yetAnotherArg,
Object andStillAnother) {
...
}
if语句的换行通常使用8个空格的规则,因为常规缩进(4个空格)会使语句体看起来比较费劲。比如:
//DON’T USE THIS INDENTATION
if ((condition1 && condition2)
|| (condition3 && condition4)
||!(condition5 && condition6)) { //BAD WRAPS
doSomethingAboutIt(); //MAKE THIS LINE EASY TO MISS
}
//USE THIS INDENTATION INSTEAD
if ((condition1 && condition2)
|| (condition3 && condition4)
||!(condition5 && condition6)) {
doSomethingAboutIt();
}
//OR USE THIS
if ((condition1 && condition2) || (condition3 && condition4)
||!(condition5 && condition6)) {
doSomethingAboutIt();
}
这里有三种可行的方法用于处理三元运算表达式:
alpha = (aLongBooleanExpression) ? beta : gamma;
alpha = (aLongBooleanExpression) ? beta
: gamma;
alpha = (aLongBooleanExpression)
? beta
: gamma;