AICon全球人工智能与机器学习技术大会9折特惠中,点击立减¥480>> 了解详情
写点什么

做了 1000 次 Code Review,我学到 3 点经验

2020 年 2 月 08 日

做了1000次Code Review,我学到3点经验


当我在 LinkedIn 工作时,工作的很大一部分内容是做 Code Review。在这个过程中,我发现一些人很容易犯的错误,于是把它们整理起来并分享给团队。


经验 1:当出现错误时 Throw an exception

我看到的一个常见模式是:


List<String> getSearchResults(...) {  try {    List<String> results = // make REST call to search service    return results;  } catch (RemoteInvocationException e) {    return Collections.emptyList();  }}
复制代码


上面的方法可能是很多新手工程师的做法,但这种模式会有问题。在我曾经参与的移动应用中,这种模式导致移动应用程序的故障。用户搜索开始后,我们的后端发生错误开始 throwing exceptions,但在应用程序的 API server 中并沒有 throwing exceptions。


因此,从应用角度看,前端会收到 200 个成功的响应,然后显示空白的搜索结果给使用者,而团队却毫不知情。


如果 API thrown an exception,那我们的监控系统会立刻发现它,并能及时修复。


很多时候,当捕捉到异常后,我们倾向于返回 empty object。Java 中 empty object 的样例包括 Optional.empty()、null 和 empty list。这种情况经常发生在 URL 解析中。如果 URL 无法从字符串解析得到的话,不要返回 null,而要问问自己:


URL 格式为什么是不合法的?这是一个需要在 upstream 解决的数据问题吗?


对于这种任务来说,empty object 并不是恰当的工具。如果出现异常行为,那么就应该 throw an exception。


经验 2:尽可能使用最具体的类型(type)

基本而言,这条建议恰好与 stringly typed programming 相反。


我经常看到下面所示的代码:


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"
复制代码


用最具体的类型(type)可避免很多 bug。


现在问题是:好心的程序员为什么会写出糟糕的 stringly typed 代码?


答案在于外部世界不是强类型的。字符串有很多不同的来源,比如:


  • url 中的查询和路径参数

  • JSON

  • 不支持枚举的数据库

  • 编写糟糕的库


在上述场景中,我们应使用如下的策略来避免该问题:将字符串解析和序列化放在程序的边缘之处。


下面是这样一个样例:


// 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 necessaryString redirectLink = serializeQueryParam("context");
复制代码


这种方式有很多优点。立即发现格式错误的数据;如果出现任何问题,应用程序将提前 fails。数据被验证一次后,不必在整个应用程序中继续捕获解析异常。


此外,强类型使方法签名更具描述性,我们不再需要在每个方法上编写那么多的 javadocs。


经验 3:用 Optionals 而非 nulls

Java 8 带来最棒的特性之一是Optional类,它代表一个可能存在也可能不存在的实体。


一个小问题:


唯一拥有自己缩写的例外(exception)是什么?答案是 NPE 或空指针异常。截至目前,它是 Java 中最常见的异常,并被称为价值10亿美元的错误


Optional能让我们完全从程序中移除 NPE。但是,必须以正确的方式使用它。如下是关于使用Optional的一些建议:


  • 我们不能在得到Optional的任何时候都简单地调用它的.get(),相反,我们要仔细考虑Optional不存在的情况并给出一个合理的默认值;

  • 如果还没有合理的默认值,那么像.map().flatmap()这样的方法允许我们推迟到以后再做决定;

  • 如果外部库返回null来表示为空的情况,那么立即使用Optional.ofNullable()wrap 该值。相信我,你以后会感谢自己的。null 值在程序内部有“bubble up”的倾向,所以最好在源代码中停止它们;

  • 在方法的返回类型中使用Optional。这种做法非常好,因为我们不需要读取 javadoc 来确定值是否可能不存在。


额外建议:尽可能采用“Unlift”方法

我们应避免下面所示的方法:


// 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
复制代码


上述不推荐使用的方法有哪些共同点?那就是它们都使用了 container objects 作为参数,比如 Optional、List 或 Task。


如果返回类型是相同种类的 container,那就更糟糕了(比如,param methods 接收 Optional,返回值也是 Optional)。


为什么呢?


1)Promise<A> method(Promise<B> param)要比 2)A method(B param)更缺少灵活性。


如果有一个Promise<B>的话,我们可以用 1),也能通过.map函数使用 2)(即promise.map(method))。


但是,如果只有一个 B 的话,我们很容易使用 2),但是无法使用 1),这样来看,2)是更具灵活性的方案。


我喜欢将其称为“unlifting”,因为它与常见的函数式工具方法“lift”恰好相反。采用这种方式进行重写会让方法更具灵活性,对调用者更加易用。


英文原文:


What I Learned From Doing 1000 Code Reviews


2020 年 2 月 08 日 07:006603
用户头像
张卫滨 业精于勤,行成于思。

发布了 387 篇内容, 共 223.7 次阅读, 收获喜欢 361 次。

关注

评论

发布
暂无评论
发现更多内容

log4j日常使用

hasWhere

Bzz矿机分币APP,深圳云矿机系统开发

13823153121

【得物技术】得物WMS如何助力仓库拣货

得物技术

供应链 商品 仓库 供应链物流 仓储

【10分钟玩转腾讯游戏核心数据库】TcaplusDB华东客户Hands-on活动

tcaplus

数据库 TcaplusDB

校友会小程序开发笔记八:校友相册功能模块设计与实现

CC同学

校友录小程序 同学录小程序

MySQL基础之十六:视图、DBA命令和三范式

打工人!

MySQL 6月日更

上云赋智的企业们,也许是这届618的最大赢家

脑极体

网络攻防学习笔记 Day48

穿过生命散发芬芳

网络攻防 6月日更

JVM读书笔记

hasWhere

5分钟速读之Rust权威指南(二十五)Deref

码生笔谈

rust

新员工最想要的10件事

hasWhere

项目经理如何才能不背锅

escray

学习 极客时间 朱赟的技术管理课 6月日更

缓存在哪-HTTP缓存原理介绍

hasWhere

ServeMux详解

Rayjun

Go HTTP

华为的活动

IT蜗壳-Tango

6月日更

第六次作业

Geek_9cf7b5

【21-11】PowerShell文件 IO 操作

耳东@Erdong

PowerShell 6月日更

Linux之cd命令

入门小站

Linux

华为新员工入职180天详细培训计划

hasWhere

新视界 | 透过鸿蒙OS看手机操作系统

架构精进之路

操作系统 6月日更 鸿蒙OS

我曾与纪伯伦的对话

卢卡多多

6月日更

校友会小程序开发笔记六:校友活动聚会功能模块设计与实现

CC同学

校友录小程序

校友会小程序开发笔记七:校友互助功能模块设计与实现

CC同学

校友录小程序 同学录小程序

无需JavaScript可以实现的9个网页功能

devpoint

html5 html/css 6月日更

☕【JVM性能调优】「CMS垃圾回收器」调优化方案

李浩宇/Alex

CMS GC jvm调优 6月日更

JavaScript 学习(五)

空城机

JavaScript 前端 6月日更

「SQL数据分析系列」7. 数据生成、转换以及操作

数据与智能

数据库 sql

为什么系统要分层?如何知晓公司系统中采用的是什么分层架构?

程序员小毕

Java 架构 分布式 设计模式

VideoLab - 高性能且灵活的 iOS 视频剪辑与特效框架

Bear

开源 视频处理 Metal 视频特效 AVFoundation

web.xml文件中的7个错误的安全配置

hasWhere

深入浅出 Ajax 的核心原理

悟空聊架构

ajax 架构 前端 6月日更 悟空聊架构

数据cool谈(第1期)数据库寻路,开源有态度

数据cool谈(第1期)数据库寻路,开源有态度

做了1000次Code Review,我学到3点经验-InfoQ