如何构建子程序,写代码该注意些什么

 代码规范主要涵盖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. 【强制】捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。 说明:如果预期对方抛的是绣球,实际接到的是铅球,就会产生意外情况。
 

猜你喜欢

转载自blog.csdn.net/moveflower/article/details/80608266