代码规范主要涵盖5个方面:类,方法,变量,语句(代码的顺序性与结构型【可读性与可维护性】),防御式编程&&错误处理技术
1.多使用抽象数据类型(ADT:数据以及对数据进行的操作的集合),另外对Car类型进行ADT封装时要注意其抽象的层次:
public interface CarDao{
void addCar(Car car);
void removeCar(Car car);
void updateCar(Car car);
Car searchCar(Car car);
//List<Car> searchCarByCode(String carCode);
//PagebleResult<Car> searchCarByPage(Pageble pageData);
}
我们希望能够把注释掉的部分转移到另一个接口中:
public interface CarCollectionDao{
List<Car> searchCarByCode(String carCode);
PagebleResult<Car> searchCarByPage(Pageble pageData);
Map<String,Integer> obtainCarStatusCount(List<String> carStatusList);
}
因为获取到的单个Car在Facade层是将要被填充到某个Car的详情页的,而多个Car的数据显然是要填充到Car的列表管理页
因此,应当注意其层次不能混乱,类的接口应当展现一致的抽象层次
2.隐藏全局数据
比如在某个Dao接口的实现类中:
public class DefaultOrderDao{
private final static String order_query_base_table = "select distinct({o.pk}) from {" + OrderModel._TYPECODE + " as o ";
private final static String order_query_base_condition = "} where 1=1 "
public OrderModel obtainOrdersByProductCode(String productCode){
StringBuilder table = new StringBuilder(order_query_base_table);
* sb.append(" join " + ProductModel._TYPECODE + " as p on {p.pk} = {o.product} ");
*
* StringBuilder condition = new StringBuilder("order_query_base_condition");
* condition.append(" p.code = ?productCode ");
* sb.append(condition.toString())
*
* FlexibleSearchQuery query = new FlexibleSearchQuery(sb.toString());
* query.addQueryParameter("productCode",productCode);
*
* ...
* }
* }
*
* 当查询条件比较多的时候或需要联的表很多的时候,这种写法简直是一个噩梦,说真的我不想再看到一个超过千行的Dao层实现类
*
* 你应当把这些烦人的全局常量都封装到另一个类中,举例:
*
* public class OrderSqlString{
* final public static String TABLE_BASE = "select distinct({o.pk}) from {" + OrderModel._TYPECODE + " as o ";
* final public static String CONDITION_BASE = "} where 1=1 and ";
* final public static String JOIN_PRODUCT = " join " + ProductModel._TYPECODE + " as p ";
* final public static String CONDITION_PRODUCT_CODE = " and {p.code} ";
* final public static String JOIN_COUNTRY = " join " + CountryModel._TYPECODE + " as c ";
* final public static String CONDITION_COUNTRY_ISOCODE = " and {c.isocode} ";
*
* ...
* }
*
* public class DefaultOrderDao{
*
* public OrderModel obtainOrdersByProductCode(Map<String,Object> requestParams){
*
* StringBuilder table = new StringBuilder(OrderSqlString.TABLE_BASE);
* StringBuilder condition = new StringBuilder(OrderSqlString.CONDITION_BASE);
*
* Map<String,Object> sqlQueryParamsMap = buildSqlQueryParamsMap(table,condition,requestParams);
*
* sb.append(condition.toString())
*
* FlexibleSearchQuery query = new FlexibleSearchQuery(sb.toString());
* query.addQueryParameters(sqlQueryParamsMap);
*
* ...
* }
*
* private Map<String,Object> buildSqlQueryParamsMap(StringBuilder table,
* StringBuilder condition,
* Map<String,Object> requestParams){
* Map<String,Object> sqlQueryParamsMap = new HashMap<>;
*
* boolean productSqlBeenUsed = false;
* boolean countrySqlBeenUsed = false;
*
* 伪代码:循环遍历requestParams
*
* {
* if(请求参数中含有productCode){
* if(!productSqlBeenUsed){
* table.append(OrderSqlString.JOIN_PRODUCT);
* productSqlBeenUsed = true;
* }
* condition.append(OrderSqlString.CONDITION_PRODUCT_CODE + " = ?productCode ");
* sqlQueryParamsMap.put("productCode",值);
* continue;
* }
* if(请求参数中含有isocodes){
* //Country表需要和Product表进行联表查询时
* if(!productSqlBeenUsed){
* table.append(OrderSqlString.JOIN_PRODUCT);
* productSqlBeenUsed = true;
* }
* if(!countrySqlBeenUsed){
* table.append(OrderSqlString.JOIN_COUNTRY);
* countrySqlBeenUsed = true;
* }
* condition.append(OrderSqlString.CONDITION_COUNTRY_ISOCODE + " in (?isocodeList) ");
* sqlQueryParamsMap.put("isocodeList",值);
* continue;
* }
* ...
* }
* }
* }
*
* 希望各位同事编写代码时能考虑到它的可维护性
*
*/
/**
* 3.创建子程序
* 真心不希望看到一个方法的方法体长达三五百行甚至更长,感觉是在面向过程
*
* 在一定范围内(65~150行),子程序的长度与错误量成反比,若是子程序的长度更长时,则维护成本会快速增高
*
* (1)引入中间,易懂的抽象,比如你在某个方法写到第95行的时候,你需要从对象拿到一个参数:
*
* 不建议的写法 :
* public JSONObject buildOrderJsonData(OrderList orderList){
* ...
*
* JSONObject orderJson = new JSONObject();
*
* double orderTotalPrice = 0;
* OrderData order = orderList.get(orderCount);
* if(order != null){
* List<OrderEntryData> orderEntryList = order.getEntries();
* if(orderEntryList != null && orderEntryList.size() > 0){
* for(OrderEntry entry : orderEntryList){
* obtainOrderTotalPrice += entry.getProduct().getPrice();
* }
*
* }
* }
*
* orderJson.put("totalPrice",orderTotalPrice);
*
* ...
* }
*
* 此时应当建立子程序:
*
* public JSONObject buildOrderJsonData(OrderList orderList){
* ...
* JSONObject orderJson = new JSONObject();
* double orderTotalPrice = obtainOrderTotalPrice(orderList.get(orderCount));
* orderJson.put("totalPrice",orderTotalPrice);
* ...
* }
*
* private double obtainOrderTotalPrice(OrderData order){
* double obtainOrderTotalPrice = 0;
* if(order != null){
* List<OrderEntryData> orderEntryList = order.getEntries();
* if(orderEntryList != null && orderEntryList.size() > 0){
* for(OrderEntry entry : orderEntryList){
* obtainOrderTotalPrice += entry.getProduct().getPrice();
* }
* }
* }
* return obtainOrderTotalPrice;
* }
*
*
* (
2)处理事件的隐藏顺序时(如:你要获取Target数据,需要先从A表中读取A数据,
* 再根据A数据从另一个方法或表中读取B数据,最后得到Target数据),请创建子程序
*
* 见obtainTokenBySaleStore方法与obtainAccountBySaleStore方法
*
* public JSONObject shelveProduct(Map<String,Object> requestMap){
* ...
* String token = obtainTokenBySaleStore(requestMap);
* Api api = new Api();
* JSONObject response = obtainShelveProductResponse(api,token);
* return response;
* }
*
* public JSONObject offProduct(Map<String,Object> requestMap){
* ...
* String token = obtainTokenBySaleStore(requestMap);
* Api api = new Api();
* JSONObject response = obtainOffProductResponse(api,token);
* return response;
* }
*
* private JSONObject obtainOffProductResponse(Api api,String token){
* //处理逻辑
* }
*
* private JSONObject obtainShelveProductResponse(Api api,String token){
* JSONObject jsonObject = new JSONObject();
* if(token == null){
* jsonObject.put("status","fail");
* jsonObject.put("message","token is null.");
* }else {
* Response response = api.do(token);
* if(response == null){
* jsonObject.put("status","fail");
* jsonObject.put("message","response is null.");
* }else {
* if(response.haveException()){
* jsonObject.put("status","fail");
* jsonObject.put("message",response.getExceptionInfo());
* }else{
* jsonObject.put("status","success");
* jsonObject.put("data",response);
* }
* }
* }
* return jsonObject;
* }
*
* private String obtainTokenBySaleStore(Map<String,Object> requestMap){
* String tokenStr = null;
*
* String storeID = (String) requestMap.getKey("storeID");
* SaleStoreModel saleStore = saleStoreDao.obtainSaleStoreByID(storeID);
*
* AccountModel account = obtainAccountBySaleStore(saleStore);
* if(account != null){
* String targetToken = account.getToken();
* if(targetToken!=null && targetToken.length() > 0){
* tokenStr = targetToken;
* }
* }
*
* return tokenStr;
* }
*
* private AccountModel obtainAccountBySaleStore(SaleStoreModel saleStore){
* //另外的复杂的处理
* }
*
* (3)避免代码重复时(代码复用),请创建子程序
*
* (4)简化复杂的布尔判断(与(2)中obtainResponse方法相似)时,请创建子程序
*
*
* (5)该部分代码
是用于数据的构建(如创建数据模型保存至数据库,或过滤数据时),请创建子程序
*
* public ShelveProductModel buildModelByJsonData(JsonObject productJson){
* ShelveProductModel product = modelService.create(ShelveProductModel.class);
* ...
*
* String code = (String)productJson.get("code");
* product.setCode(code);
*
* Double price = (Double)productJson.get("price");
* product.setPrice(price);
*
* Map<String,String> attrMap = obtainAttrMapFromJson(productJson);
* product.setAttrMap(attrMap);
*
* List<WareHouseModel> WareHouseList = obtainAttrMapFromJson(productJson);
* product.setWareHouseList(WareHouseList);
*
* ...
* modelService.save(product);
*
* }
*
*
*
* private Map<String,String> obtainAttrMapFromJson(JsonObject productJson){
* //处理过程
* }
*
* private List<WareHouseModel> obtainAttrMapFromJson(JsonObject productJson){
* //处理过程
* }
*
*/
/**
* 4.防御式编程(想必对这方面有经验的各位有所了解,此处简单提及,以供查漏补缺)
* 对外部数据的处理:
* (1)检查所有来源于外部的数据的值
* 对于字符串,是否过长?
这时应该使用String类型保存数据还是使用TEXT?是否被编码解码?
* 是否被转义?比如前端传到后台的字符串含有"#,*,/,%"等字符
* 对于涉及到金钱的类型,你是应该用Double类型还是BigDecimal?
* 甚至注入攻击
* (2)检查子程序所有输入参数的值
* 同(1),只不过数据源变成类程序内部而已
* (3)决定如何处理错误的输入数据
* 检测到非法的参数通过错误处理技术来处理
* (4)常用错误处理技术
* 【0】断言(预防)
* 【1】返回中立值(int类型返回0,String类型返回空字符串等等)
* 【2】把警告信息记录在日志文件中
* 【3】返回一个错误码(状态值或状态变量)以及对应的信息
* 【4】局部处理,见第4条中的obtainShelveProductResponse方法
* 【5】异常处理,不再赘述
*/
/**
* 5.变量
*
* (1)变量初始化原则:在第一次使用它的时候初始化(声明+初始化)
* (2)变量作用域
* 【1】变量的跨度与攻击窗口:
*
* line 1 : int a = 0;
* line 2 : int b = 0;
* line 3 : int c = 1;
* ...
* line N : a = b + c;
* ...
* line 3N : a = 2;
*
* 第一次引用a和第二次引用a中间有N-2行代码,第二次引用a和第三次引用a中间有2N-2行代码,
* 其a变量的跨度有两个为N-2,2N-2,a变量的攻击窗口范围是line 1~line3 N
*
* 第一次引用b和第二次引用b中间有N-3行代码,其b变量的跨度为N-3,b变量的攻击窗口范围是line 2~lin N
*
* 第一次引用c和第二次引用c中间有N-4行代码,其c变量的跨度为N-4,c变量的攻击窗口范围是line 3~lin N
*
* 攻击窗口的概念意义:可能有新代码加入到某个变量的攻击窗口中,不当的修改了这个变量就会对该变量造成影响
*
* 【2】变量的存活时间:
*
* 变量存在期间的总跨度数越多,单个跨度越长,攻击窗口的范围就越广,变量的存活时间就越长,这种情况是不利于维护的
* 所以,我们应该尽量减少变量的存活时间:使变量的引用局部化
* 变量的实际生命周期比想象中要短,因此在程序中多加断言,使用变量时坚持变量初始化原则,尽量减小变量作用域
*
* 【3】减小变量作用域的一般原则:
* 在循环开始之前
先去初始化该循环中使用的变量,而不是在该循环所属的子程序的开始处初始化这些变量;
* 直到变量即将被使用时再为其赋值;
* 把相关语句放在一起;
* 把相关语句提取成单独的子程序;
* 根据需要扩展变量的作用域:把全局变量转变为类成员变量非常困难,反之则容易很多,因此应该倾向于
* 选择该变量所能具有的最小作用域:先是将变量局限于某一个特定的循环,然后局限于某个子程序(方法),
* 其次是类的变量,最后万不得已的情况下将它作为全局变量。
*
* (3)一个变量只用于一种用途,增加代码的可读性
*
* (4)变量的命名(驼峰
命名法就不再赘述了)
* 【1】变量的名字要描述出该变量所代表的事物
* currentDate:当前日期,
* linesPerPage:每页的行数,
* 又比如你输入一些订单号,有些在数据库中存在,有些不存在
* ordersWhichExist:存在的订单集合
* ordersWhichNotExsit:不存在的订单集合
* 【2】变量名字的长度
* 8-20长度的变量命易于调试;
* 较长的名字适用于很少用到的变量和全局变量;
* 较短的名字适用于局部变量和循环变量
* 【3】变量命名中的计算值限定词
* 总额(Total,Sum),
* 平均值(Average),
* 最大值(Max),
* 最小值(Min),
* 计数值(Record,Count)
*
* 使用的时候,请将限定词加到名字的最后面,例如一个遍历订单的For循环:
*
* for(int orderCount = 0;orderCount < orderTotal;orderCount++){
* //do something
* }
*
* 【4】变量命名中的对仗词
*
* begin/end;
* first/last;
* locked/unlocked;
* min/max;
* next/prev;
* old/new;
* visible/invisible;
* source/target;
*
* 使用的时候,请将对仗词加到名字的最后面,例如一个根据订单创建日期的数据库查询操作:
* 定义变量名为:orderCreateDateBegin与orderCreateDateEnd
*
* 【5】为循环下标命名
* for(int orderCount = 0;orderCount < orderTotal;orderCount++){
* //do something
* }
* 或
* for(int orderIndex = 0;orderIndex < orderTotal;orderIndex++){
* //do something
* }
*
* 【6】为状态变量命名
* threadLocked与threadUnLocked;
* conditionUsed与conditionNotUsed;
*
* 【7】为临时变量命名
* 模糊的命名
* temp = sqrt(9 + 4 * a);
* array[0] = (- b + temp);
* array[1] = (- b - temp);
*
* 精确的命名
* discriminant = sqrt(9 + 4 * a);
* array[0] = (- b + discriminant);
* array[1] = (- b - discriminant);
*
* 【8】为布尔值命名
* hasDone,hasFound,hasError,success,ok,hasUsed
*
* 【9】为枚举类型命名(前缀命名约定)
* Color_Red,Color_Blue,Color_White
*
*
*/
/**
* 6.语句
* (1)直线型代码
*
* 有明确的顺序的直线型代码
*
* ExpendseData expendseData = initExpendseData();
*
* aStepExpendseData(expendseData);
* bStepExpendseData(expendseData);
* cStepExpendseData(expendseData);
* dStepExpendseData(expendseData);
*
* displayExpendseData(expendseData);
*
* 代码间隔开的空行也是有意义的:空行将数据初始化过程,处理过程,显示结果的过程隔开,一目了然
*
* 顺序无关的直线性代码通过第二标准来判断语句或代码块的顺序,意思就是:按照就近原则(把相关的操作放在一起)
*
* 比如商品有标题,描述,图片列表,价格,数量,税率,物流政策,退货政策,支付政策等,我在组织代码的时候,这样写:
*
* ShelveProdutModel product = modelService.create(ShelveProdutModel.class);
*
* product.setTitle(title);
* product.setDescription(description);
* product.setPictures(pictureList);
*
* product.setPrice(price);
* product.setQutity(number);
* product.setRate(rate);
*
* product.setPaymentPolicy(paymentPolicy);
* product.setShippingPolicy(shippingPolicy);
* product.setReturnPolicy(returnPolicy);
*
* ...
*
* modelService.save(product);
*
* (2)if语句,系统处理大量错误代码
* if(token == null){
* jsonObject.put("status","fail");
* jsonObject.put("message","token is null.");
* }else {
* Response response = api.do(token);
* if(response == null){
* jsonObject.put("status","fail");
* jsonObject.put("message","response is null.");
* }else {
* if(response.haveException()){
* jsonObject.put("status","fail");
* jsonObject.put("message",response.getExceptionInfo());
* }else{
* jsonObject.put("status","success");
* jsonObject.put("data",response);
* }
* }
* }
* return jsonObject;
* (3)循环语句
* 必须熟知 break,continue,return,try-catch块中finally的用法
*
*/
}
* (1)
关于
equals
的用法
* Object
的
equals
方法容易抛空指针异常,应使用常量或确定有值的对象来调用
equals
。
*
正例:
"test".equals(object);
*
反例:
object.equals("test");
*
*
(
2
)定义方法的顺序
*
类内方法定义的顺序依次是:公有方法或保护方法
>
私有方法
> getter/setter
方法。
*
* (3)
集合处理
*
关于
hashCode
和
equals
的处理,遵循如下规则:
* 1
) 只要重写
equals
,就必须重写
hashCode
。
* 2
) 因为
Set
存储的是不重复的对象,依据
hashCode
和
equals
进行判断,所以
Set
存储的 对象必须重写这两个方法。
* 3
) 如果自定义对象作为
Map
的键,那么必须重写
hashCode
和
equals
。
*
说明:
String
重写了
hashCode
和
equals
方法,所以我们可以非常愉快地使用
String
对象 作为
key
来使用。
*
*
(
4
)不要在
foreach
循环里进行元素的
remove/add
操作
* remove
元素请使用
Iterator
方式,如果并发操作,需要对
Iterator
对象加锁。
*
正例:
*
Iterator
<String>
iterator = list.iterator();
*
while (iterator.hasNext()) {
*
String item = iterator.next();
*
if (
删除元素的条件
) {
*
iterator.remove();
*
}
*
}
*
反例:
*
List
<String>
list = new ArrayList
<String>
();
*
list.add("1");
*
list.add("2");
*
for (String item : list) {
*
if ("1".equals(item)) {
*
list.remove(item);
*
}
*
}
*
说明:以上代码的执行结果肯定会出乎大家的意料,那么试一下把“
1
”换成“
2
”,会是同样的 结果吗?
*
*
(
5
)注释规约
* 1.
【强制】类、类属性、类方法的注释必须使用
Javadoc
规范,使用
/**
内容
*/
格式,不得使用
// xxx
方式。
说明:在
IDE
编辑窗口中,
Javadoc
方式会提示相关注释,生成
Javadoc
可以正确输出相应注 释;在
IDE
中,工程调用方法时,不进入方法即可悬浮提示方法、参数、返回值的意义,提高 阅读效率。
2.
【强制】所有的抽象方法(包括接口中的方法)必须要用
Javadoc
注释、除了返回值、参数、 异常说明外,还必须指出该方法做什么事情,实现什么功能。 说明:对子类的实现要求,或者调用注意事项,请一并说明。
3.
【强制】所有的类都必须添加创建者和创建日期。
4.
【强制】方法内部单行注释,在被注释语句上方另起一行,使用
//
注释。方法内部多行注释 使用
/* */
注释,注意与代码对齐。
5.
【强制】所有的枚举类型字段必须要有注释,说明每个数据项的用途。(6)异常日志:1. 【强制】Java 类库中定义的可以通过预检查方式规避的 RuntimeException 异常不应该通过 catch 的方式来处理,比如:NullPointerException,IndexOutOfBoundsException 等等。
说明:无法通过预检查的异常除外,比如,在解析字符串形式的数字时,不得不通过 catch NumberFormatException 来实现。 正例:if (obj != null) {...} 反例:try { obj.method() } catch (NullPointerException e) {…}
2. 【强制】异常不要用来做流程控制,条件控制。 说明:异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式 要低很多。
3. 【强制】catch 时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。 对于非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理。 说明:对大段代码进行 try-catch,使程序无法根据不同的异常做出正确的应激反应,也不利 于定位问题,这是一种不负责任的表现。
正例:用户注册的场景中,如果用户输入非法字符,或用户名称已存在,或用户输入密码过于 简单,在程序上作出分门别类的判断,并提示给用户。
4. 【强制】捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请 将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的 内容。
5. 【强制】有 try 块放到了事务代码中,catch 异常后,如果需要回滚事务,一定要注意手动回 滚事务。
6. 【强制】finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。 说明:如果 JDK7 及以上,可以使用 try-with-resources 方式。
7. 【强制】不要在 finally 块中使用 return。 说明:finally 块中的 return 返回后方法结束执行,不会再执行 try 块中的 return 语句。
8. 【强制】捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。 说明:如果预期对方抛的是绣球,实际接到的是铅球,就会产生意外情况。