根据学习部分极客时间 《设计模式之美》专栏 (王争 前Google工程师)和《阿里 java 规范》整理总结。

分别介绍编码规范的三个部分:命名与注释(Naming and Comments)、代码风格(Code Style)和编程技巧(Coding Tips)。

关于命名

  • 命名的关键是能准确达意。对于不同作用域的命名,我们可以适当地选择不同的长度。作用域小的变量(比如临时变量),可以适当地选择短一些的命名方式。除此之外,命名中也可以使用一些耳熟能详的缩写。
  • 我们可以借助类的信息来简化属性、函数的命名,利用函数的信息来简化函数参数的命名。
  • 命名要可读、可搜索。不要使用生僻的、不好读的英文单词来命名。除此之外,命名要符合项目的统一规范,不要用些反直觉的命名。
  • 接口有两种命名方式:一种是在接口中带前缀“I”;另一种是在接口的实现类中带后缀“Impl”。对于抽象类的命名,也有两种方式,一种是带上前缀“Abstract”,一种是不带前缀。这两种命名方式都可以,关键是要在项目中统一。

关于注释

  • 注释的目的就是让代码更容易看懂。只要符合这个要求的内容,你就可以将它写到注释里。总结一下,注释的内容主要包含这样三个方面:做什么、为什么、怎么做。对于一些复杂的类和接口,我们可能还需要写明“如何用”。
  • 注释本身有一定的维护成本,所以并非越多越好。类和函数一定要写注释,而且要写得尽可能全面、详细,而函数内部的注释要相对少一些,一般都是靠好的命名、提炼函数、解释性变量、总结性注释来提高代码可读性。

补充:

开发前,我一般先写注释,再写代码。比如写一个方法,我会先拆分业务逻辑,把注释给写上。后面再看注释,写代码。

// todo
public void createOrder(RequestVo request) {// todo 校验用户登录// todo 校验商品// todo 创建订单// todo 拼装、返回结果集
}

关于注释:之前我的看法只要逻辑清晰命名准确达意就不用写注释了,现在回过来想这个问题,代码是需要不断维护的,即使当时你思路清晰那么过了一段时间后还能那么清晰么。人的大脑只会记住关键的信息,那么注释就是帮助我们梳理自己的想法和逻辑沉淀下来,是百利无害的事情,当别人接手也能迅速理解,降低沟通成本。如何注释才是好的注释呢?文中提到三点:做什么、为什么做、怎么做、怎么用(API)。这里最重要的事做什么,。我再补充一点,可以加下使用场景或者业务场景。

关于命名:这点我基本无疑义,总结下来就是两点:简洁达意和风格统一。

理论五:让你最快速地改善代码质量的20条编程规范(中)

1 函数、类多大才合适?

函数的代码行数不要超过一屏幕的大小,比如 50 行。类的大小限制比较难确定。

2.一行代码多长最合适?

最好不要超过 IDE 显示的宽度。当然,限制也不能太小,太小会导致很多稍微长点的语句被折成两行,也会影响到代码的整洁,不利于阅读。

3. 善用空行分割单元块

对于比较长的函数,为了让逻辑更加清晰,可以使用空行来分割各个代码块。在类内部,成员变量与函数之间、静态成员变量与普通成员变量之间、函数之间,甚至成员变量之间,都可以通过添加空行的方式,让不同模块的代码之间的界限更加明确。

4. 四格缩进还是两格缩进?

我个人比较推荐使用两格缩进,这样可以节省空间,特别是在代码嵌套层次比较深的情况下。除此之外,值得强调的是,不管是用两格缩进还是四格缩进,一定不要用 tab 键缩进。

5. 大括号是否要另起一行?

我个人还是比较推荐将大括号放到跟上一条语句同一行的风格,这样可以节省代码行数。但是,将大括号另起一行,也有它的优势,那就是,左右括号可以垂直对齐,哪些代码属于哪一个代码块,更加一目了然。

6. 类中成员的排列顺序

在 Google Java 编程规范中,依赖类按照字母序从小到大排列。类中先写成员变量后写函数。成员变量之间或函数之间,先写静态成员变量或函数,后写普通变量或函数,并且按照作用域大小依次排列。

关于编码技巧

1. 将复杂的逻辑提炼拆分成函数和类。

2. 通过拆分成多个函数或将参数封装为对象的方式,来处理参数过多的情况。

​ 我个人觉得,函数包含 3、4 个参数的时候还是能接受的,大于等于 5 个的时候,我们就觉得参数有点过多了,会影响到代码的可读性,使用起来也不方便。针对参数过多的情况,一般有 2 种处理方法。

  • 考虑函数是否职责单一,是否能通过拆分成多个函数的方式来减少参数。示例代码如下所示:

    public User getUser(String username, String telephone, String email);// 拆分成多个函数
    public User getUserByUsername(String username);
    public User getUserByTelephone(String telephone);
    public User getUserByEmail(String email);
    
  • 将函数的参数封装成对象。示例代码如下所示:

    
    public void postBlog(String title, String summary, String keywords, String content, String category, long authorId);// 将参数封装成对象
    public class Blog {private String title;private String summary;private String keywords;private Strint content;private String category;private long authorId;
    }
    public void postBlog(Blog blog);
    

3. 函数中不要使用参数来做代码执行逻辑的控制。

不要在函数中使用布尔类型的标识参数来控制内部逻辑,true 的时候走这块逻辑,false 的时候走另一块逻辑。这明显违背了单一职责原则和接口隔离原则。我建议将其拆成两个函数,可读性上也要更好。我举个例子来说明一下。

// 将其拆分成两个函数
public void buyCourse(long userId, long courseId);
public void buyCourseForVip(long userId, long courseId);

不过,如果函数是 private 私有函数,影响范围有限,或者拆分之后的两个函数经常同时被调用,我们可以酌情考虑保留标识参数。示例代码如下所示:


// 拆分成两个函数的调用方式
boolean isVip = false;
//...省略其他逻辑...
if (isVip) {buyCourseForVip(userId, courseId);
} else {buyCourse(userId, courseId);
}// 保留标识参数的调用方式更加简洁
boolean isVip = false;
//...省略其他逻辑...
buyCourse(userId, courseId, isVip);

除了布尔类型作为标识参数来控制逻辑的情况外,还有一种“根据参数是否为 null”来控制逻辑的情况。针对这种情况,我们也应该将其拆分成多个函数。拆分之后的函数职责更明确,不容易用错。具体代码示例如下所示:


public List<Transaction> selectTransactions(Long userId, Date startDate, Date endDate) {if (startDate != null && endDate != null) {// 查询两个时间区间的transactions}if (startDate != null && endDate == null) {// 查询startDate之后的所有transactions}if (startDate == null && endDate != null) {// 查询endDate之前的所有transactions}if (startDate == null && endDate == null) {// 查询所有的transactions}
}// 拆分成多个public函数,更加清晰、易用
public List<Transaction> selectTransactionsBetween(Long userId, Date startDate, Date endDate) {return selectTransactions(userId, startDate, endDate);
}public List<Transaction> selectTransactionsStartWith(Long userId, Date startDate) {return selectTransactions(userId, startDate, null);
}public List<Transaction> selectTransactionsEndWith(Long userId, Date endDate) {return selectTransactions(userId, null, endDate);
}public List<Transaction> selectAllTransactions(Long userId) {return selectTransactions(userId, null, null);
}private List<Transaction> selectTransactions(Long userId, Date startDate, Date endDate) {// ...
}

4. 函数设计要职责单一。

我们在前面讲到单一职责原则的时候,针对的是类、模块这样的应用对象。实际上,对于函数的设计来说,更要满足单一职责原则。相对于类和模块,函数的粒度比较小,代码行数少,所以在应用单一职责原则的时候,没有像应用到类或者模块那样模棱两可,能多单一就多单一。

具体的代码示例如下所示:


public boolean checkUserIfExisting(String telephone, String username, String email)  { if (!StringUtils.isBlank(telephone)) {User user = userRepo.selectUserByTelephone(telephone);return user != null;}if (!StringUtils.isBlank(username)) {User user = userRepo.selectUserByUsername(username);return user != null;}if (!StringUtils.isBlank(email)) {User user = userRepo.selectUserByEmail(email);return user != null;}return false;
}// 拆分成三个函数
public boolean checkUserIfExistingByTelephone(String telephone);
public boolean checkUserIfExistingByUsername(String username);
public boolean checkUserIfExistingByEmail(String email);

5. 移除过深的嵌套层次,方法包括:去掉多余的 if 或 else 语句,使用 continue、break、return 关键字提前退出嵌套,调整执行顺序来减少嵌套,将部分嵌套逻辑抽象成函数。

代码嵌套层次过深往往是因为 if-else、switch-case、for 循环过度嵌套导致的。我个人建议,嵌套最好不超过两层,超过两层之后就要思考一下是否可以减少嵌套。过深的嵌套本身理解起来就比较费劲,除此之外,嵌套过深很容易因为代码多次缩进,导致嵌套内部的语句超过一行的长度而折成两行,影响代码的整洁。解决嵌套过深的方法也比较成熟,有下面 4 种常见的思路。

  • 去掉多余的 if 或 else 语句。代码示例如下所示:

    
    // 示例一
    public double caculateTotalAmount(List<Order> orders) {if (orders == null || orders.isEmpty()) {return 0.0;} else { // 此处的else可以去掉double amount = 0.0;for (Order order : orders) {if (order != null) {amount += (order.getCount() * order.getPrice());}}return amount;}
    }// 示例二
    public List<String> matchStrings(List<String> strList,String substr) {List<String> matchedStrings = new ArrayList<>();if (strList != null && substr != null) {for (String str : strList) {if (str != null) { // 跟下面的if语句可以合并在一起if (str.contains(substr)) {matchedStrings.add(str);}}}}return matchedStrings;
    }
    
  • 调整执行顺序来减少嵌套。具体的代码示例如下所示:

    
    // 重构前的代码
    public List<String> matchStrings(List<String> strList,String substr) {List<String> matchedStrings = new ArrayList<>();if (strList != null && substr != null) {for (String str : strList) {if (str != null) {if (str.contains(substr)) {matchedStrings.add(str);}}}}return matchedStrings;
    }// 重构后的代码:先执行判空逻辑,再执行正常逻辑
    public List<String> matchStrings(List<String> strList,String substr) {if (strList == null || substr == null) { //先判空return Collections.emptyList();}List<String> matchedStrings = new ArrayList<>();for (String str : strList) {if (str != null) {if (str.contains(substr)) {matchedStrings.add(str);}}}return matchedStrings;
    }
    
  • 将部分嵌套逻辑封装成函数调用,以此来减少嵌套。具体的代码示例如下所示:

    
    // 重构前的代码
    public List<String> appendSalts(List<String> passwords) {if (passwords == null || passwords.isEmpty()) {return Collections.emptyList();}List<String> passwordsWithSalt = new ArrayList<>();for (String password : passwords) {if (password == null) {continue;}if (password.length() < 8) {// ...} else {// ...}}return passwordsWithSalt;
    }// 重构后的代码:将部分逻辑抽成函数
    public List<String> appendSalts(List<String> passwords) {if (passwords == null || passwords.isEmpty()) {return Collections.emptyList();}List<String> passwordsWithSalt = new ArrayList<>();for (String password : passwords) {if (password == null) {continue;}passwordsWithSalt.add(appendSalt(password));}return passwordsWithSalt;
    }private String appendSalt(String password) {String passwordWithSalt = password;if (password.length() < 8) {// ...} else {// ...}return passwordWithSalt;
    }
    

    除此之外,常用的还有通过使用多态来替代 if-else、switch-case 条件判断的方法。这个思路涉及代码结构的改动。

6. 用字面常量取代魔法数。

常用的用解释性变量来提高代码的可读性的情况有下面 2 种.

  • 常量取代魔法数字。示例代码如下所示:

    
    public double CalculateCircularArea(double radius) {return (3.1415) * radius * radius;
    }// 常量替代魔法数字
    public static final Double PI = 3.1415;
    public double CalculateCircularArea(double radius) {return PI * radius * radius;
    }
    
  • 使用解释性变量来解释复杂表达式。示例代码如下所示:

    
    if (date.after(SUMMER_START) && date.before(SUMMER_END)) {// ...
    } else {// ...
    }// 引入解释性变量后逻辑更加清晰
    boolean isSummer = date.after(SUMMER_START)&&date.before(SUMMER_END);
    if (isSummer) {// ...
    } else {// ...
    }
    

7. 用解释性变量来解释复杂表达式,以此提高代码可读性。

https://time.geekbang.org/column/article/188882

其他《阿里 JAVA 规范》

OOP 规约:

1. 【强制】POJO 类中的任何布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误。

说明:在本文 MySQL 规约中的建表约定第一条,表达是与否的变量采用 is_xxx 的命名方式,所以,需要

在设置从 is_xxx 到 xxx 的映射关系。

反例:定义为基本数据类型 Boolean isDeleted 的属性,它的方法也是 isDeleted(),框架在反向解析的时

候,“误以为”对应的属性名称是 deleted,导致属性获取不到,进而抛出异常。

2. 【推荐】在常量与变量的命名时,表示类型的名词放在词尾,以提升辨识度。

正例:startTime / workQueue / nameList / TERMINATED_THREAD_COUNT

反例:startedAt / QueueOfWork / listName / COUNT_TERMINATED_THREAD

3. 【强制】注释的双斜线与注释内容之间有且仅有一个空格。

正例:
// 这是示例注释,请注意在双斜线之后有一个空格
String commentString = new String();

4.【强制】 POJO 类必须写 toString 方法。

使用 IDE 中的工具:source> generate toString时,如果继承了另一个 POJO 类,注意在前面加一下 super.toString。

说明:在方法执行抛出异常时,可以直接调用 POJO 的 toString()方法打印其属性值,便于排查问题。

5. 【强制】构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中。

6. 【强制】定义 DO/DTO/VO 等 POJO 类时,不要设定任何属性默认值。

反例:POJO 类的 createTime 默认值为 new Date(),但是这个属性在数据提取时并没有置入具体值,在更新其它字段时又附带更新了此字段,导致创建时间被修改成当前时间。

7. 【推荐】final 可以声明类、成员变量、方法、以及本地变量,下列情况使用 final 关键字:

1) 不允许被继承的类,如:String 类。

2) 不允许修改引用的域对象,如:POJO 类的域变量。

3) 不允许被覆写的方法,如:POJO 类的 setter 方法。

4) 不允许运行过程中重新赋值的局部变量。

5) 避免上下文重复使用一个变量,使用 final 关键字可以强制重新定义一个变量,方便更好地进行重构。

8. 【推荐】类成员与方法访问控制从严:

1) 如果不允许外部直接通过 new 来创建对象,那么构造方法必须是 private。

2) 工具类不允许有 public 或 default 构造方法。

3) 类非 static 成员变量并且与子类共享,必须是 protected。

4) 类非 static 成员变量并且仅在本类使用,必须是 private。

5) 类 static 成员变量如果仅在本类使用,必须是 private。

6) 若是 static 成员变量,考虑是否为 final。

7) 类成员方法只供类内部调用,必须是 private。

8) 类成员方法只对继承类公开,那么限制为 protected。

说明:任何类、方法、参数、变量,严控访问范围。过于宽泛的访问范围,不利于模块解耦。

思考:如果是一个 private 的方法,想删除就删除,可是一个 public 的 service 成员方法或成员变量,删除一下,不得手心冒点汗吗?变量像自己的小孩,尽量在自己的视线内,变量作用域太大,无限制的到处跑,那么你会担心的。

日期时间:

1. 【强制】日期格式化时,传入 pattern 中表示年份统一使用小写的 y。

说明:日期格式化时,yyyy 表示当天所在的年,而大写的 YYYY 代表是 week in which year(JDK7 之后引入的概念),意思是当天所在的周属于的年份,一周从周日开始,周六结束,只要本周跨年,返回的 YYYY 就是下一年。

正例:表示日期和时间的格式如下所示:

new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")

2. 【强制】在日期格式中分清楚大写的 M 和小写的 m,大写的 H 和小写的 h 分别指代的意义。

说明:日期格式中的这两对字母表意如下:

1) 表示月份是大写的 M;

2) 表示分钟则是小写的 m;

3) 24 小时制的是大写的 H;

4) 12 小时制的则是小写的 h。

3. 【推荐】使用枚举值来指代月份。如果使用数字,注意 Date,Calendar 等日期相关类的月份 month 取值在 0-11 之间。

说明:参考 JDK 原生注释,Month value is 0-based. e.g., 0 for January.

正例: Calendar.JANUARY,Calendar.FEBRUARY,Calendar.MARCH 等来指代相应月份来进行传参或比较。

集合处理:

1.【强制】关于 hashCode 和 equals 的处理,遵循如下规则:

1) 只要覆写 equals,就必须覆写 hashCode。

2) 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须覆写这两种方法。

3) 如果自定义对象作为 Map 的键,那么必须覆写 hashCode 和 equals。

说明:String 因为覆写了 hashCode 和 equals 方法,所以可以愉快地将 String 对象作为 key 来使用。

2.【强制】判断所有集合内部的元素是否为空,使用 isEmpty()方法,而不是 size()==0 的方式。

说明:在某些集合中,前者的时间复杂度为 O(1),而且可读性更好。

正例:

Map<String, Object> map = new HashMap<>(16);if(map.isEmpty()) {System.out.println("no element in this map.");
}

3.【强制】在使用 Collection 接口任何实现类的 addAll()方法时,都要对输入的集合参数进行NPE 判断。

说明:在 ArrayList#addAll 方法的第一行代码即 Object[] a = c.toArray(); 其中 c 为输入集合参数,如果为 null,则直接抛出异常。

4.【强制】不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。

正例:List<String> list = new ArrayList<>();list.add("1");list.add("2");Iterator<String> iterator = list.iterator();while (iterator.hasNext()) {String item = iterator.next();if (删除元素的条件) {iterator.remove();}
}反例:for (String item : list) {if ("1".equals(item)) {list.remove(item);} }

说明:以上代码的执行结果肯定会出乎大家的意料,那么试一下把“1”换成“2”,会是同样的结果吗?

5. 【强制】在 JDK7 版本及以上,Comparator 实现类要满足如下三个条件,不然 Arrays.sort,Collections.sort 会抛 IllegalArgumentException 异常。

说明:三个条件如下

1) x,y 的比较结果和 y,x 的比较结果相反。

2) x > y,y > z,则 x > z。

3) x = y,则 x,z 比较结果和 y,z 比较结果相同。

反例:下例中没有处理相等的情况,交换两个对象判断结果并不互反,不符合第一个条件,在实际使用中

可能会出现异常。

new Comparator<Student>() {@Override
public int compare(Student o1, Student o2) {return o1.getId() > o2.getId() ? 1 : -1; }
};

6. 【推荐】使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。

说明:keySet 其实是遍历了 2 次,一次是转为 Iterator 对象,另一次是从 hashMap 中取出 key 所对应的

value。而 entrySet 只是遍历了一次就把 key 和 value 都放到了 entry 中,效率更高。如果是 JDK8,使用

Map.forEach 方法。

正例:values()返回的是 V 值集合,是一个 list 集合对象;keySet()返回的是 K 值集合,是一个 Set 集合对

象;entrySet()返回的是 K-V 值组合集合。

  1. 【推荐】高度注意 Map 类集合 K/V 能不能存储 null 值的情况,如下表格:

反例:由于 HashMap 的干扰,很多人认为 ConcurrentHashMap 是可以置入 null 值,而事实上,存储 null 值时会抛出 NPE 异常。

前后端规约:

1. 【强制】前后端交互的 API,需要明确协议、域名、路径、请求方法、请求内容、状态码、响应体。

说明:

1) 协议:生产环境必须使用 HTTPS。

2) 路径:每一个 API 需对应一个路径,表示 API 具体的请求地址:

​ a) 代表一种资源,只能为名词,推荐使用复数,不能为动词,请求方法已经表达动作意义。

​ b) URL 路径不能使用大写,单词如果需要分隔,统一使用下划线。

​ c) 路径禁止携带表示请求内容类型的后缀,比如".json",".xml",通过 accept 头表达即可。

3) 请求方法:对具体操作的定义,常见的请求方法如下:

​ a) GET:从服务器取出资源。

​ b) POST:在服务器新建一个资源。

​ c) PUT:在服务器更新资源。

​ d) DELETE:从服务器删除资源。

4) 请求内容:URL 带的参数必须无敏感信息或符合安全要求;body 里带参数时必须设置 Content-Type。

5) 响应体:响应体 body 可放置多种数据类型,由 Content-Type 头来确定。

2.【强制】前后端数据列表相关的接口返回,如果为空,则返回空数组[]或空集合{}。

说明:此条约定有利于数据层面上的协作更加高效,减少前端很多琐碎的 null 判断。

3. 【强制】服务端发生错误时,返回给前端的响应信息必须包含 HTTP 状态码,errorCode、errorMessage、用户提示信息四个部分。

说明:四个部分的涉众对象分别是浏览器、前端开发、错误排查人员、用户。其中输出给用户的提示信息要求:简短清晰、提示友好,引导用户进行下一步操作或解释错误原因,提示信息可以包括错误原因、上下文环境、推荐操作等。 errorCode:参考附表 3。errorMessage:简要描述后端出错原因,便于错误排查人员快速定位问题,注意不要包含敏感数据信息。

正例:常见的 HTTP 状态码如下

1) 200 OK: 表明该请求被成功地完成,所请求的资源发送到客户端。

2) 401 Unauthorized: 请求要求身份验证,常见对于需要登录而用户未登录的情况。

3) 403 Forbidden:服务器拒绝请求,常见于机密信息或复制其它登录用户链接访问服务器的情况。

4) 404 Not Found: 服务器无法取得所请求的网页,请求资源不存在。

5) 500 Internal Server Error: 服务器内部错误。

4. 【强制】对于需要使用超大整数的场景,服务端一律使用 String 字符串类型返回,禁止使用Long 类型。

说明:Java 服务端如果直接返回 Long 整型数据给前端,JS 会自动转换为 Number 类型(注:此类型为双精度浮点数,表示原理与取值范围等同于 Java 中的 Double)。Long 类型能表示的最大值是 2 的 63 次方-1,在取值范围之内,超过 2 的 53 次 (9007199254740992)的数值转化为 JS 的 Number 时,有些数值会有精度损失。扩展说明,在 Long 取值范围内,任何 2 的指数次整数都是绝对不会存在精度损失的,所以说精度损失是一个概率问题。若浮点数尾数位与指数位空间不限,则可以精确表示任何整数,但很不幸,双精度浮点数的尾数位只有 52 位。

反例:通常在订单号或交易号大于等于 16 位,大概率会出现前后端单据不一致的情况,比如,“orderId”: 362909601374617692,前端拿到的值却是: 362909601374617660。

异常处理:

1.【推荐】方法的返回值可以为 null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回 null 值。

说明:本手册明确防止 NPE 是调用者的责任。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败、序列化失败、运行时异常等场景返回 null 的情况。

2. 【推荐】防止 NPE,是程序员的基本修养,注意 NPE 产生的场景:

1) 返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE。

反例:public int f() { return Integer 对象}, 如果为 null,自动解箱抛 NPE。

2) 数据库的查询结果可能为 null。

3) 集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。

4) 远程调用返回对象时,一律要求进行空指针判断,防止 NPE。

5) 对于 Session 中获取的数据,建议进行 NPE 检查,避免空指针。

6) 级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。

正例:使用 JDK8 的 Optional 类来防止 NPE 问题。

3. 【强制】捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。

4. 【参考】对于公司外的 http/api 开放接口必须使用 errorCode;而应用内部推荐异常抛出;跨应用间 RPC 调用优先考虑使用 Result 方式,封装 isSuccess()方法、errorCode、errorMessage;而应用内部直接抛出异常即可。

说明:关于 RPC 方法返回方式使用 Result 方式的理由:

1)使用抛异常返回方式,调用方如果没有捕获到就会产生运行时错误。

2)如果不加栈信息,只是 new 自定义异常,加入自己的理解的 error message,对于调用端解决问题的帮助不会太多。如果加了栈信息,在频繁调用出错的情况下,数据序列化和传输的性能损耗也是问题。

日志规约:

1.【强制】在日志输出时,字符串变量之间的拼接使用占位符的方式。

说明:因为 String 字符串的拼接会使用 StringBuilder 的 append()方式,有一定的性能损耗。使用占位符仅是替换动作,可以有效提升性能。

正例:logger.debug(“Processing trade with id: {} and symbol: {}”, id, symbol);

2.【推荐】尽量用英文来描述日志错误信息,如果日志中的错误信息用英文描述不清楚的话使用中文描述即可,否则容易产生歧义。

说明:国际化团队或海外部署的服务器由于字符集问题,使用全英文来注释和描述日志错误信息。

设计规约:

1.【强制】存储方案和底层数据结构的设计获得评审一致通过,并沉淀成为文档。

说明:有缺陷的底层数据结构容易导致系统风险上升,可扩展性下降,重构成本也会因历史数据迁移和系统平滑过渡而陡然增加,所以,存储方案和数据结构需要认真地进行设计和评审,生产环境提交执行后,需要进行 double check。

正例:评审内容包括存储介质选型、表结构设计能否满足技术方案、存取性能和存储空间能否满足业务发展、表或字段之间的辩证关系、字段名称、字段类型、索引等;数据结构变更(如在原有表中新增字段)也需要进行评审通过后上线。

2.【强制】如果系统中某个功能的调用链路上的涉及对象超过 3 个,使用时序图来表达并且明确各调用环节的输入与输出。

说明:时序图反映了一系列对象间的交互与协作关系,清晰立体地反映系统的调用纵深链路。

3.【推荐】类在设计与实现时要符合单一原则。

说明:单一原则最易理解却是最难实现的一条规则,随着系统演进,很多时候,忘记了类设计的初衷。

4.【推荐】谨慎使用继承的方式来进行扩展,优先使用聚合/组合的方式来实现。

说明:不得已使用继承的话,必须符合里氏代换原则,此原则说父类能够出现的地方子类一定能够出现,比如,“把钱交出来”,钱的子类美元、欧元、人民币等都可以出现。

5.【推荐】系统设计阶段,根据依赖倒置原则,尽量依赖抽象类与接口,有利于扩展与维护。

说明:低层次模块依赖于高层次模块的抽象,方便系统间的解耦。

6.【推荐】系统设计阶段,注意对扩展开放,对修改闭合。

说明:极端情况下,交付的代码是不可修改的,同一业务域内的需求变化,通过模块或类的扩展来实现。

7. 【推荐】系统设计阶段,共性业务或公共行为抽取出来公共模块、公共配置、公共类、公共方法等,在系统中不出现重复代码的情况,即 DRY 原则(Don’t Repeat Yourself)。

说明:随着代码的重复次数不断增加,维护成本指数级上升。随意复制和粘贴代码,必然会导致代码的重复,在维护代码时,需要修改所有的副本,容易遗漏。必要时抽取共性方法,或者抽象公共类,甚至是组件化。

正例:一个类中有多个 public 方法,都需要进行数行相同的参数校验操作,这个时候请抽取:

private boolean checkParam(DTO dto) {…}

8.【推荐】避免如下误解:敏捷开发 = 讲故事 + 编码 + 发布。

说明:敏捷开发是快速交付迭代可用的系统,省略多余的设计方案,摒弃传统的审批流程,但核心关键点上的必要设计和文档沉淀是需要的。

反例:某团队为了业务快速发展,敏捷成了产品经理催进度的借口,系统中均是勉强能运行但像面条一样的代码,可维护性和可扩展性极差,一年之后,不得不进行大规模重构,得不偿失。

9.【参考】设计文档的作用是明确需求、理顺逻辑、后期维护,次要目的用于指导编码。

说明:避免为了设计而设计,系统设计文档有助于后期的系统维护和重构,所以设计结果需要进行分类归档保存。

10.【参考】可扩展性的本质是找到系统的变化点,并隔离变化点。

说明:世间众多设计模式其实就是一种设计模式即隔离变化点的模式。

正例:极致扩展性的标志,就是需求的新增,不会在原有代码交付物上进行任何形式的修改。

11.【参考】代码即文档的观点是错误的,清晰的代码只是文档的某个片断,而不是全部。

说明:代码的深度调用,模块层面上的依赖关系网,业务场景逻辑,非功能性需求等问题是需要相应的文档来完整地呈现的。

未完待续~

让你最快速地改善代码质量的 20 条编程规范相关推荐

  1. 快速改善代码质量的20条代码规范

    目录 1.关于命名 2.关于注释 3.关于代码风格 4.关于编码技巧 5.统一编码规范 1.关于命名 命名的关键是能准确达意.对于不同作用域的命名,我们可以适当地选择不同的长度. 我们可以借助类的信息 ...

  2. Python 工匠:善用变量来改善代码质量

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由鹅厂优文发表于云+社区专栏 作者:朱雷 | 腾讯IEG高级工程师 『Python 工匠』是什么? 我一直觉得编程某种意义上是一门『手艺 ...

  3. 【笔记】编程的原则:改善代码质量的101个方法

    代码必然被修改 Code will be changed 代码不是写完就结束了,它在日后必然会被修改.没有写完就扔的一次性代码. 在编写代码的时候,我们应将"代码会被修改"这一点作 ...

  4. java编程规范每行代码窄字符,wiki/0xFE_编程规范.md at master · islibra/wiki · GitHub

    0xFE_编程规范 使用UTF-8编码 使用空格缩进 命名 清晰表达意图, 少用缩写(行业通用除外, 如: request=req, response=resp, message=msg), 不应使用 ...

  5. python的有效变量名_python里用变量命名改善代码质量

    编程时,总会遇到各种各样的变量,取一个好的变量名能够有效提高代码的可读性,而且python是一种,动态类型的语言,良好的变量名,能够在编写代码或者再次阅读代码时提高效率. 1. 变量名不要太宽泛,要有 ...

  6. 【《Effective C#》提炼总结】提高Unity中C#代码质量的21条准则

    原则1   尽可能地使用属性而不是可直接访问的数据成员         ● 属性(property)一直是C#语言中比较有特点的存在.属性允许将数据成员作为共有接口的一部分暴露出去,同时仍旧提供面向对 ...

  7. 20条开发规范,写出诗一样的代码

    文章目录 简介 命名 1.命名的长度,多长合适 2.利用上下文简化命名 2.1 利用类class上下文简化命名 2.2 利用函数function上下文简化命名 3. 命名可读.可搜索 3.1 命名可读 ...

  8. 使用Lint检查提高代码质量

    使用Lint检查提高代码质量 1.概述 2.代码中使用标记 2.1 概述 2.2 在工程中使用标记 2.3 一些标记的使用 2.3.1 Nullness标记 2.3.2 资源标记 2.3.3 线程标记 ...

  9. idea2020shezhi代码检查级别_GitLab 13.1:告警管理扩展,新代码质量工具和安全合规等...

    昨天Gitlab官方博客发布了Gitlab新的月度版本Gitlab13.1,该版本搭理扩展了告警管理,新增加了改善代码质量的工具集以及安全和合规方面的内容,更多内容请和虫虫一起往下学习. 概述 自动化 ...

最新文章

  1. 杨超越的声音+高晓松的脸~如此酸爽的技术,你值得拥有!
  2. IDEA跟Eclipse险些打一架。Maven:都住手,我来一统天下
  3. 【机器学习】一文深层解决模型过拟合
  4. 系统设计4:Web服务和流量限制
  5. java socket 传输压缩文件_java基于socket传输zip文件功能示例
  6. 《DirectX 9.0 3D游戏开发编程基础》 第一章 初始化Direct3D 读书笔记
  7. URL的语法及HTTP报文
  8. symfony ajax,如何在php或symfony中使用jQuery ajax上传文件
  9. 原地踏步 – 没有突破高效理念的结果
  10. 图片版坦克大战源代码之坦克类(二)
  11. WEB实现百度离线地图展示
  12. 阿贝尔定理(幂级数收敛半径的)
  13. kalman滤波理解一:理论框架
  14. 12款响应式 Lightbox(灯箱)效果插件
  15. IE9打开的html文件打印不了,IE9无法查看打印预览的2个解决方法
  16. Solidity函数中pure、view、constant的用法
  17. 富文本带图片导出word
  18. 判断div是否在可视区内
  19. srsRAN源码分析----enb端协议栈是如何运作
  20. Java8新特性总结

热门文章

  1. 加速度传感器灵敏度的几种表示方式
  2. ‘A’与”A”的区别
  3. C# NPOI 批量导出Excel 打包下载
  4. 《电务工作日志》Android版,工作日志(wifi之dhd debug 2)
  5. 正常情况下如何卸载计算机软件?
  6. troubleshooting之解决YARN队列资源不足导致的application直接失败
  7. 【PHP】 解决报错:Error: php71w-common conflicts with php-common-5.4.16-43.el7_4.x86_64
  8. 网络游戏数据同步的实现 一:状态同步、帧同步的基本原理概述
  9. 2.1、用JsonParser解析json树模型
  10. gensim bm25模型保存与加载