What I learned from doing 1000 code reviews
文章作者从以往代码审核的经验角度提出三个建议
建议1、Throw an exception when things go wrong(当程序出现错误时,抛出一个异常)
例如代码:
List<String> getSearchResults(...) {
try {
List<String> results = // make REST call to search service
return results;
} catch (RemoteInvocationException e) {
return Collections.emptyList();
}
}
核心内容:该建议主要讨论在某些查询功能中,当前程序发请求到后台获取数据时,后台跑出异常。
程序是返回一个空对象(null、空集合等等),还是抛出异常?
场景1:查询交易,如果上游出现报错,下游捕获只返回空对象。
问题:前端只收到查询成功,且收到空查询结果。导致监控程序不能及时发现异常并及时修复问题。
场景2:从字符串解析出URL错误时,直接返回null值。
问题:URL错误问题应该在上游程序修复,不能让来下流程序进行处理。
最终结论:Empty objects are not the right tool for this job. If something is exceptional you should throw an exception(空对象不是一个正确方式针对这种情况。除非有例外清楚,不然就应该抛出一个异常)
个人观点:个人从原则上比较赞同作者观点。有时候返回空对象,下游无法判断具体没有数据,还会异常。而且抛出异常我们尽量区分不同异常。针对某一下流程序来说,捕获到异常不一定意味程序就要中止,存在需要通过异常类型,进行不同逻辑处理。
建议2、Use the most specific type possible(尽可能使用最准确的类型)
作者举例子:
void doOperation(String opType, Data data);
// where opType is "insert", "append", or "delete", this should have clearly been an enum
String fetchWebsite(String url);
// where url is "https://google.com", this should have been an URN
String parseId(Input input);
// the return type is String but ids are actually Longs like "6345789"
核心内容:使用最准确的类型,这样能够避免一些BUG,且也是基于JAVA作为强类型语言的设计。
具体做法:
外部数据不为准确数据类型:
1、在URLS的参数
2、JSON
3、数据库不支持enums
4、写得不好的库
处理方式:
// Step 1: Take a query param representing a company name / member id pair and parse it
// example: context=Pair(linkedin,456)
Pair<String, Long> companyMember = parseQueryParam("context");
// this should throw an exception if malformed
// Step 2: Do all the stuff in your application
// MOST if not all of your code should live in this area
// Step 3: Convert the parameter back into a String at the very end if necessary
String redirectLink = serializeQueryParam("context");
步骤:
1、获取查询参数 公司名称/成员ID 并解析它。如果数据错误则必须抛出异常。
2、如果不是所有代码都应该存在于此区域,请尽可能地完成应用程序中的所有操作
3、如果有必要,在最后时候把参数转化为String
优化:
1、错误数据会被发现
2、如果出现问题,程序较早失败中止。
3、数据被验证过一次,之后不需要在整个程序都保持捕获解析错误。
4、更加准确访问描述,对应方法的文档内容也减少
个人观点:这个在原来开发习惯中一直没有注意这个问题。尤其属于数据字典类型确实使用enums可以避免很多意外情况,就不用通过if或switch来单独判断,减少很多BUG。
建议3、Use Optionals instead of nulls(使用空容器代表null值)
核心内容:用JAVA 8 提供的Optional来替换null,其中解决最多就是 Null Pointer Exception的问题
使用范围建议:
1、当使用Optional时,就不用考虑对象是否存在或合理默认值。而且随时调用get()方法。
2、如果没有合理默认值,使用map()和 .flatMap()方法,等后面再对对象赋值。
3、如果有外部库返回null表示空结果,则利用Optional.ofNullable来分装这个值。null在程序里面造成影响会慢慢扩大,所以在源头就解决这个问题。
4、使用Optional作为方法返回值,就不需要判断什么时候返回值是不存在。
个人观点:目前还是很少用JAVA 8的Optional对象,需要进一步学习。但是非常赞同作者这个建议,null会导致很多NPE问题
建议4、“Unlift” methods whenever possible(使用空容器代表null值)
需要避免方法如下
// AVOID:
CompletableFuture<T> method(CompletableFuture<S> param);
// PREFER:
T method(S param);
// AVOID:
List<T> method(List<S> param);
// PREFER:
T method(S param);
// AVOID:
T method(A param1, B param2, Optional<C> param3);
// PREFER:
T method(A param1, B param2, C param3);
T method(A param1, B param2);
// This method is clearly doing two things, it should be two methods
// The same is true for boolean parameters
这些需要避免方法有什么相同之处呢?
1、入参都是Optional, List, Task。
2、返回参数也是同样的集合(例如入参为Optional,返回参数为Optional)
理由:
方法1:Promise<A> method(Promise<B> param)
方法2:A method(B param)
理由如下:
1、例如当你只有B对象时候,只能调用方法2。
2、方法2重写使得更加灵活、更加容易使用
个人观点:目前还有无法参透这条建议。如果按照作者的建议,对方法进行升级。但是如果过多使用泛型感觉方法的准确度就不高。目前还在理解中。