1 基本规范
1.1 命名规则
需遵守java基本命名规范,以下列举需要着重注意的地方和我们的一些规则。
l 统一采用驼峰标识。
l 不允许使用汉语拼音命名,特殊含义的除外。
l 遇到缩写如XML时,需全部大写,如loadXMLDocument()。
l 局部变量及输入参数不要与类成员变量同名(set方法与构造函数除外)
l 常量必须都是大写字母,名称包含多个词时,使用下划线来分割,并且保证命名有完整含义,比如PRODUCT_TYPE。枚举值也是常量,遵守该规则。
l 项目名与模块名之间,使用”-”隔开,不再使用”_”。区分代码和项目的命名规则。
l Package名必须全部小写,尽量使用单个单词。
l Interface名统一采用首字母为I。
l 注意单类中,变量名的一致性。例如,在一个service中的几个方法,都定义有useName变量,或者进行简写uname等,不论全写还是简写,前后方法对于同一变量的命名都需要保持一致,便于可读。
l 在同一个方法中,不建议一个变量表示多个意义不同的数值。
1.2 声明规则
1.2.1 修饰符顺序
修饰符应该按照如下顺序排列:public, protected, private, abstract, static, final, transient, volatile, synchronized, native, strictfp。
1.2.2 类定义顺序
类内容定义的顺序
l 静态成员变量 / Static Fields
l 静态初始化块 / Static Initializers
l 成员变量 / Fields
l 初始化块 / Initializers
l 构造器 / Constructors
l 静态成员方法 / Static Methods
l 成员方法 / Methods
l 重载自Object的方法如toString(), hashCode() 和main方法
l 类型(内部类) / Types(Inner Classes)
同等的顺序下,再按public, protected,default, private的顺序排列。
1.2.3 方法名前缀
Service层接口方法名称,统一前缀。
查询方法前缀: get、find。
搜索性查询方法前缀:query、search。
统计类前缀:count。
操作类前缀:insert、add、create、update、delete。
可能还有其他前缀。
同包名下的service类,尽量统一所有方法的前缀,以便于以后对方法进行或事物或权限或业务等控制。
1.2.4 方法重载
方法重载中需要注意参数的使用,相同参数个数,不同参数类型的方法重载要注意调用方传递参数null时的问题。需要与调用方沟通协调好。
比如
方法1:public void getIdListByCondition(Integer type, String userName, String email){}
方法2:public void getIdListByCondition(Integer type, Date createTime, Integer age){}
当调用方使用时,遇到类似问题,如果调用方的后面参数为null。那么,使用方式是
String userName = null;
String email = null;
getIdListByCondition(type, userName, email);
如果直接写成 getIdListByCondition(type, null, null);
将会编译不通过。
但是,在有些场景下,这样的写法对于调用方来说是对的。比如,使用HttpInvoker方式为调用方提供服务。调用方第一次拿到的接口,只有方法1,服务端也只有方法1的实现。调用方直接使用null给方法赋了值。
不过,随着业务需要,服务端又实现了方法2,并将接口提供给调用方更新,这时,调用方以前的代码就会出现错误。
在开发中,需要避免这类由service方法重载而导致的已开发代码出现错误的情况。
1.3 注释规则
1.3.1 Javadoc注释
Javadoc使用 /** */ 注释。
l 所有的源文件添加javadoc注释,其中列出说明、作者、版本信息、日期等。
统一设置格式,打开eclipse->Window->Preferences->Java->Code Style->Code Templates->Comments->Types->Edit,在Pattern框中填入
/**
*@comment
*@author ${user}
*@date ${date} ${time}
*@version 1.0.0
*@see
*/
@comment后填写类的相关说明。
@ version 默认是1.0.0版本。
@see标签可选,如果没有,可以将该标签删除。
l 所有方法添加javadoc注释,列举方法功能说明(重要方法需要举例说明),同时还需要对方法参数的含义提供解释,对返回结果、异常等作出说明。如果有接口,只需要对接口方法进行注释说明。
统一设置格式,打开eclipse->Window->Preferences->Java->Code Style->Code Templates->Comments->Methods->Edit,在Pattern框中填入
/**
${tags}
@exception
@Author ${user}
@Date ${date} ${time}
@since 1.0.0
-
@see
*/
在@exception中,要对方法抛出的异常进行说明。
Since值要注意,在类的版本号修改后,新建方法的Since值与类版本号要一致。
@see标签可选,如果没有,可以将该标签删除。
l Pojo类,需要对各个成员变量进行javadoc注释。
l 如果注释中有超过一个段落,用<p>分隔。
l 示例代码以<pre></pre>包裹。
1.3.2 失效代码注释
失效代码注释,使用/.../界定。不允许使用//。在注释首行写明,注释掉该段代码的原因以及为何保留不删去的原因。
1.3.3 代码细节注释
由//界定,注释代码细节。所有重要的操作步骤,建议都在开始前注释说明。
抽取的私有方法,也使用//来进行说明。
说明写在代码操作前,除非变量定义,否则不推荐使用行末注释。如:
方法(int a){
int count = 0; //定义计数值count
//如果a小于1,则……
if(a < 1){
count++;//计数加1。 不推荐行末这样注释
……
}
}
1.3.4 注释内容细节要求
l 注释中的每一个单词都要有其不可缺少的意义。
@param name 名称 这么写没有意义,指出具体业务或者实体含义。
l 空注释标签应该删去。
@return 自动生成的标签,如果感觉没有内容要写,就将这样的标签删去。
l 对于调用复杂的API尽量提供代码示例。
l 对于已知的bug或者对于调用方产生的影响需要声明。
l 如果方法抛出了异常,用@throws尽量写明异常原因。
l 如果方法允许null作为参数,或者允许返回值为null,需要在JavaDoc中说明,尤其是null有特殊业务逻辑含义时。
l 还没有完成或者代码质量不好,还需要继续完善的,用//TODO: 声明。
l 可以运行,但是性能差需要改进,或者存在隐患的,用//XXX: 声明。
l 代码错误,不能正常运行,需要修复的,用//FIXME: 声明。
1.4 编程规则
1.4.1 基本规范
l 方法需要对传入参数进行校验,尤其是面对不可知的调用者时。一般来说,当不能确保传入参数肯定有效时,都需要对参数进行空值或者范围有效性的判断。分布式开发时,对于service的参数,必须加判断。
l 调用某个具有返回值的方法时,需要对返回值进行空值判断。除非可以确保返回值肯定不为空。分布式开发时,对于service方法的返回值,必须加判断。
l 变量,参数和返回值定义尽量基于接口而不是具体实现类。如List list = new ArrayList()。
l 代码中不能出现System.out.println(),e.printStackTrace()等。
l 代码中不要出现空代码块。
l 代码中不能出现警告。警告部分,多余变量或者代码块删去或注掉,保留部分使用标注去掉警告。
l 缩小变量的作用域。
1.4.2 异常处理
l Service方法对外抛出异常,统一为RuntimeException。需要自己定义继承于RuntimeException的顶级异常类以及继承于顶级异常类的各个子异常类,以供在开发中使用。
异常类定义不用特别细,低级异常细节对于调用方来说,意义不大。比如,数据库连接不上或者cache连接不上,或者代码原因产生查询异常等等,这些对于调用方来说,都是应用异常,所以直接使用一类异常进行定义即可。
目前,我们可以预知定义的异常:
BasicException 顶级异常
ApplicationException应用异常
ParameterException参数异常
UnauthorizedException权限异常
UnknownException未知异常
随着业务需要,添加异常类,需要粗粒度来把控异常类的定义。
l 除非确保程序肯定无异常,否则都需要进行异常捕获。
l 如果要抛出异常,重新抛出的异常必须保留原来的异常。统一定义为throw new NewException("信息", e) 或者 throw new NewException( e)。
如果是代码需要,编程时主动抛出异常,定义为throw new NewException("信息")。
l 在所有异常被捕获但没有重新抛出的地方必须要记入log日志。如果是分布式开发,那么service层,异常抛出前,也必须将原异常记入log日志中。
l 对于空catch块,需要注明原因,否则不允许出现。
1.4.3 代码质量要求
l 方法
n 方法参数在10个以内。太多的方法参数影响代码可读性。考虑用值对象代替这些参数或重新设计。
n 方法长度在60行以内。复杂业务方法80行以内,复杂方法可按照功能点分拆成私有方法。注释不算在内。
n 方法内,嵌套层数最多不超过6层。即if,while,do,for等等关键词组成的代码块,如
if(……){
for(……){
if(……){
……
}
}
}
n 这类多关键词逻辑的嵌套使用,不要超过6层。
n 如果单关键词语句,嵌套层数不超过3层。
1.4.4 其他要求
l 单类的文件长度2000行以内(包含注释)。
l 匿名内部类代码在20行以内。
l service方法中,表示特殊值的参数,如类型、状态等等, 这些值通过业务预先设定,并将直接体现到程序、数据库中,那么推荐使用枚举来限定参数。
如:假设用户的类型有0:普通,1:VIP用户,2:管理员。 Service方法,有通过该用户类型作为参数进行更新或查询操作的,定义时不推荐直接使用int,而是使用枚举值来限定。
l 注意向下兼容原则。原则上,已经正在线上使用的service服务,其service接口定义的方法,除了bug修改等修订版本可以直接更新原方法实现外。对于接口定义的修改(如加参数,变返回值),接口功能的修改等等,都是不允许的,只允许重载新的方法。调用方与调用模式是未知的。
当一个调用者调用A方法,并已经上线使用,另一个调用者也希望调用A方法,但是需要增加参数,那么我们会重载一个B方法,供其调用。这个原则一般都会遵循。但是,当调用者调用A方法,过了一段时间,发现A方法不能满足要求,需要修改。此时往往会忽略兼容原则,而是直接改了A方法。这时应该遵循的是:重载出B方法,对A方法做废弃代码标注,调用者逐步修改调用A方法的地方,等到确认所有调用者都修改结束,在后续更新中删除A方法。
我们的service服务,可能是本地直接调用,也可能是作为服务供远程通过TCP或HTTP等协议调用,所以,在写service时,需要养成良好的代码习惯。
l 注意分层细节。页面的标签类内容、cookie等不允许在service层中进行调用,页面的非数据资源内容,不允许传入到service以下。同样,service层的方法都是纯业务逻辑或者数据逻辑的,不允许出现为了页面标签或者页面特性而存在的方法。
2 数据库
2.1 MySql
2.1.1 命名
l 表名称
命名:业务模块缩写_表名。
每个表必须以其所属的业务模块的名称缩写作为其名称前缀。
表名代表了该表的业务含义,不能使用拼音。
前缀缩写与表名,表名多个单词之间,均使用”_”来分隔。
如:
sc_update_recond
sc是搜索中心searchcenter缩写,update_recond表示是变更记录表。
l 列名称
主键:表名_id。不含前缀。如update_recond_id。
列名,多个词之间使用”_”隔开。如user_name。
列名如果使用简写,相同含义的必须要前后统一。
通用性字段,比如,createtime,或者create_time,在同一个项目中必须统一,不允许一张表使用create_time,另一张表使用create_date,出现两种命名不同的方式。
2.1.2 字段类型
原则上,我们定义字段的类型,需要对业务要求进行预估,本着用小不用大的原则来设计。
l 整数类型
TINYINT,1字节
SMALLINT,2字节
MEDIUMINT,3字节
INT,4字节
BIGINT,8字节
在定义整数的字段时,类型状态等字段,使用TINYINT即可。不允许使用INT、BIGINT。
主键可以使用BIGINT或者INT。
其他诸如权重值、计数值等字段,按照实际业务预估,选择类型。
l 字符串
CHAR,VARCHAR,TINYBLOB/ TINYTEXT,BLOB/ TEXT,MEDIUMBLOB/ MEDIUMTEXT,LONGBLOB/ LONGTEXT。
CHAR的使用需要看具体业务,如果该字段下所有的值为定长,比如,商品的全站唯一码,所有的码长均相同,这样的情况下才可以使用。
VARCHAR字段使用,长度也需要按照业务进行预估。其长度超过255时,将占用2字节。一般情况下,UTF-8字符下,最大长度会达到21845。因此,在文本到了一定的值,是使用TEXT还是使用VARCHAR,需要做个评估。一般来说,能用VARCHAR,尽量使用VARCHAR,而不是TEXT。
BLOB是二进制字符串,TEXT是非二进制字符串。两者都可存放大容量的信息。BLOB区分大小写。
l 时间
DATE,3字节
DATETIME,8字节
TIMESTAMP,4字节
TIME,3字节
YEAR,1字节
按照实际业务使用。比如,有的需要只记录年份的,那么采用YEAR。只记录到天的,使用DATE。记录到具体时间的,使用DATETIME。
2.2 MongoDB
该要求目前适用于2.2.0以前的版本。不包含2.2.0及其以上版本。
2.2.1 命名
l Collection名称
命名:业务模块缩写_collection业务名称。
每个Collection必须以其所属的业务模块的名称缩写作为其名称前缀。
Collection名称代表了该表的业务含义,不能使用拼音。
前缀缩写与Collection名多个单词之间,均使用”_”来分隔。
l 字段名称
字段名称尽可能短。建议不超过2个字母。比如定义使用a-z或者a1-z1等,开发人员维护好具体字段名称和model类成员变量的名称对应关系即可。不允许使用成员变量名称直接作为字段名。
主键id,除非业务需要,否则一律由MongoDB默认的_id作为主键,并自己生成主键值覆盖默认值。
2.2.2 字段类型
所有的整数类型,除非业务真的庞大,否则一律使用int。不允许使用long。
超过int最大范围,MongoDB会将该值提升为double,到时可在代码中进行替换。尽量减少long类型的使用。因为long在BSON中的体现是NumberLong(1),而其他int、double都是纯数字。
2.2.3 注意事项
l 尽可能的减少MongoDB的数据BSON串的大小。
l 尽可能少而快的返回数据。需要查询什么字段就返回什么字段,不要返回多余内容。
l 尽量避免count的使用。MongoDB的count非常慢,除非是全Collection无参数的count。尽量避开count查询类的业务,可采用计数器、后端统计等方式替换部分业务需要。分页时,避开展示全部页码、记录数的样式。当然,如果数据量不大,一般在20w以内,可以酌情考虑该问题。
l 多条件查询,抛开全表扫描类的字段(type、status等),其他的所有条件,尽量做联合索引。同时,需要考虑排序的问题。MongoDB执行时只会使用1个索引,因此,如何做到性能最佳的联合索引,需要开发人员自己用实际数据进行测试(字段索引的前后顺序会导致性能的变化)。注意:不要看网上的关于索引的优化资料,很多时候,他们所述的优化方式并不可靠,需要我们按照实际业务,测试自己的索引顺序。
l Java客户端默认的最大连接是10,无论是直接调用java客户端driver代码,还是使用orm框架,都要注意设置这个值。
l 通过一些唯一键id,如主键id数组,查询对象,返回的结果顺序,并不能保证是按照传入的id顺序排序的(一般没有设置排序,可能会被按照文档顺序来排序)。此时,需要我们在内存中做一个排序转换。
l 字符串模糊匹配,MongoDB是按照正则方式执行的。匹配到内容,会立即返回,进行下一条匹配。因此,在大数据量时,按照正常的业务,我们对于模糊匹配的字符串,多个之间采用的是and的关系,尽量避免出现or关系的查询性业务出现。这个要求也适用于其他数据库。如果有or的要求,那么需要对该类业务进行判断,是否真的需要,需要,则使用基于Lucene的全文检索来实现。
备注
一些常见的问题
1、Socket(mina框架)、RMI、HttpInvoker、Hessian、Burlap、Rest、WebService性能。
2、Rest与WebService的关系。