贾言

架构师说, 用20个字描述代码评审的内容, 自省也省人。由于是一字一含义, 不连贯, 为了增强趣味性, 每句都增加对应的歪解。只是对常见评审的描述, 不尽之处,欢迎补充!

验幻空越重 -- 言欢空月虫

验: 公共方法都要做参数的校验,参数校验不通过明确抛出异常或对应响应码。

  • java bean验证已经是一个很古老的技术了,会避免我们很多问题,可参考:http://beanvalidation.org/ http://www.infoq.com/cn/news/2010/03/javaee6-validation https://www.sitepoint.com/using-java-bean-validation-method-parameters-return-values/
  • 在接口中也明确使用验证注解修饰参数和返回值, 作为一种协议要求调用方按验证注解约束传参, 返回值验证注解约束提供方按注解要求返回参数

幻: 在代码中要杜绝幻数,幻数可定义为枚举或常量以增强其可读性

空: 要时刻警惕空指针异常

  • 常见的 a.equals(b) 要把常量放到左侧
  • aInteger == 10 如果 aInteger 为空时会抛出空指针异常
  • 不确认返回集合是否可为空时要做非空判断, 再做for循环
  • 使用空对象模式, 约定返回空集合, 而非null
  • 使用StringUtils判断字符串非空

越: 如果方法传入数组下标作为参数,要在一开始就做下标越界的校验,避免下标越界异常

重: 不要写重复代码,重复代码要使用重构工具提取重构

命循频异长 - 明勋品宜昌

命: 包 / 类 / 方法 / 字段 / 变量 / 常量的命名要遵循规范,要名副其实, 这不但可以增加可读性,还可以在起名的过程中引导我们思考方法 / 变量 / 类的职责是否合适

有意义很重要, 典型无意义命名:

public static final Integer CODE_39120 = 39120;
public static final String MESSAGE_39120 = "[包裹]与[库房号]不一致,确定装箱?"; public static final Integer CODE_39121 = 39121;
public static final String MESSAGE_39121 = "[包裹]与[箱号]的承运类型不一致,确定装箱?"; Rule rule1 = request.getRuleMap().get("1050"); 

CODE_39120这个名字和幻数没多大区别。

循: 不要在循环中调用服务,不要在循环中做数据库等跨网络操作

频: 写每一个方法时都要知道这个方法的调用频率,一天多少,一分多少,一秒多少,峰值可能达到多少,调用频率高的一定要考虑性能指标, 考虑是否会打垮数据库,是否会击穿缓存

异: 异常处理是程序员最基本的素质,不要处处捕获异常,对于捕获了只写日志,没有任何处理的 catch 要问一问自己,这样吃掉异常,是否合理

下面是一个反例, 在导出文件的controller方法中做了两层的try...catch, 在catch块中记录日志后什么都没做, 这样用户看不到真正想要的内容, 研发也只有看日志才能发现错误, 而“看日志”, 通常只有业务方反馈问题时才会看, 就会导致研发人员发现错误会比现场人员还会晚。

@RequestMapping(value = "/export")
public void export(CityRelationDomain condition, HttpServletResponse response) { ZipOutputStream zos = null; BufferedWriter bufferedWriter = null; try { condition.setStart(0); condition.setSize(MAX_EXPORT_LINES); List<CityRelationDomain> list = cityRelationService.getOrdersByCondition(condition); response.setCharacterEncoding("GBK"); response.setContentType("multipart/form-data"); response.setHeader("Content-Disposition", "attachment;fileName=export.zip"); zos = new ZipOutputStream(response.getOutputStream()); bufferedWriter = new BufferedWriter(new OutputStreamWriter(zos, "GBK")); bufferedWriter.write("订单类型编码,始发城市-省,始发城市-市,目的城市-省,目的城市-市"); ZipEntry zipEntry = new ZipEntry("export.csv"); zos.putNextEntry(zipEntry); for (CityRelationDomain domain : list) { try { bufferedWriter.newLine(); bufferedWriter.write(CSVExportUtil.trans2CSV(domain.getOrderCode())); bufferedWriter.write(','); bufferedWriter.write(CSVExportUtil.trans2CSV(domain.getProvinceNameFrom())); bufferedWriter.write(','); bufferedWriter.write(CSVExportUtil.trans2CSV(domain.getCityNameFrom())); bufferedWriter.write(','); bufferedWriter.write(CSVExportUtil.trans2CSV(domain.getProvinceNameTo())); bufferedWriter.write(','); bufferedWriter.write(CSVExportUtil.trans2CSV(domain.getCityNameTo())); } catch (Exception e) { e.printStackTrace(); } } bufferedWriter.newLine(); bufferedWriter.flush(); zos.closeEntry(); bufferedWriter.close(); } catch (Exception e) { e.printStackTrace(); logger.error("导出CSV文件异常"); } finally { try { if (zos != null) { zos.close(); } if (bufferedWriter != null) { bufferedWriter.close(); } } catch (IOException e) { e.printStackTrace(); } }
} 

长: 如果一行代码过长,要分解开来;如果一个方法过长,要重构方法;如果一个类过长要考虑拆分类

依轮线日简 - 依伦先日贱

依: 如果调用了外部依赖, 一定要搞清楚这个外部依赖可以提供的性能指标,最好约定 SLA

轮: 不要重复造轮子,如果已经有成熟类库实现了类似功能,要优先使用成熟类库的方法,这是因为成熟类库中的方法都经过很多人的测试验证,通常情况下我们自己实现的质量最大等同于成熟类库的质量。

线: 要注意我们的 jsf 服务,web 应用,消费消息的 worker 都是多线程环境,要注意线程安全问题,最典型的 HashMap,SimpleDateFormat ,ArrayList 是非线程安全的,另外如果使用 Spring 自动扫描服务,那么这个服务默认是单例,其内部成员是多个线程共享的,如果直接用成员变量是有线程不安全的。

两个典型的错误代码片段:

无视 SimpleDateFormat 非线程安全

@Service public class AService { private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd"); public void doSomething() { //use FORMAT } }

使用 Service 成员变量

@Service
public class BService { private Pojo b; public void doB() { b = getB(); process(b); }
} 

日: 打印日志和设定合理的日志级别,如有必要要添加 if 条件限定是否打印日志,在日志中使用 JSON 序列化,生成长字符串的 toString() 都要做 if 限定打印,否则配置的日志级别没达到,也会做大量字符串拼接,占用很多 gc 年轻代内存. 另外一定要通过log4j打印日志而不是直接把日志打印到控制台。

典型错误示例:

@Service
public class FooService { private static final Logger LOGGER = LoggerFactory.getLogger(FooService.class); public void doFooThing(Foo foo) { LOGGER.debug("get parameter foo {}", JSONObject.toString(foo)); try {/*do something*/} catch (Exception ex) {ex.printStackTrace();} }
} 

简: 尽可能保持整体设计的简洁,方法实现的简洁,要根据情况使用内存缓存,redis 缓存,jmq 异步处理。这里的简需要把握好分寸。

接偶正分壮 - 洁偶正粉妆

接: 接口是用来隔离变化的,如果一个业务有几种不同的形态,但都有相同的处理,那么可以定义接口来隔离业务形态的不同,在服务调用处,通过业务类型字段来获得不同的服务类。而不要实现一个类,然后在类的各个方法中都根据业务类型做 if else 或更复杂的各种判断。

典型示例:

做法1:

public interface BarService {    void doBarThing(Bar b); void doBarFatherThing(Bar b);
}
public class BarServiceImpl  implement BarService{ public void doBarThing(Bar b) { if (b.getType() == BarType.A) { //do some logic } else (b.getType() == BarType.B) { //do some B type logic
        } //do other doBarThing logic
    } public void doBarFatherThing(Bar b) { if (b.getType() == BarType.A) { //do some logic } else (b.getType() == BarType.B) { //do some B type logic
        } //do other doBarFatherThing logic
    }
} 

做法2:

public interface BarService { void doBarThing(Bar b); void doBarFatherThing(Bar b);
}
public class BarServiceFactory { public BarService getBarService(BarType type) { // get bar service logic
    }
}
//如果有公共逻辑就定义, 没有就不定义
public class BaseBarService implement BarService { public void doBarThing(Bar b) { //do other doBarThing logic
    } public void doBarFatherThing(Bar b) { //do other doBarFatherThing logic
    } }
public class TypeABarService extends BaseBarService  implement BarService { public void doBarThing(Bar b) { // doATypeThing super.doBarThing(b); } public void doBarFatherThing(Bar b) { // do bar type A service
super.doBarFatherThing(b); //如果需要就调用, 不需要就不调用父类
    } } 

做法 2 的好处是将不同类型的逻辑解耦,各自发展,不会相互影响,如果添加类型也不必影响现有类型逻辑。

偶: 认识系统之间的耦合关系,通过同步数据来做两个系统之间的交互是一种很强的耦合关系,会使数据接收方依赖于数据发送方的数据库定义,如果发送方想改数据结构,必须要求下游接收方一起修改;通过接口调用是一种常见的系统耦合关系,接口的提供方要保证接口的可用性,接口的调用方要考虑接口不可用时的应对方案; mq 消息是一种解耦的方法,两个系统不存在实时的耦合关系。但是 mq 解耦的方式不能滥用,在同一系统内不宜过多使用 mq 消息来做异步,要尽可能保证接口的性

能, 而不是通过 mq 防止出问题后重新消费。

正: 模块之间依赖关系要正向依赖,不能让底层模块依赖于上层模块;不能让数据层依赖于服务层也不能让服务层依赖于 UI 层; 也不能在模块之间形成循环依赖关系。

分: 分而治之,复杂的问题要分解成几个相对简单的问题来解决,首先要分析出核心问题, 然后分析出核心的入参是什么,结果是什么,入参通过几步变化可以得出结果。

壮: 时刻注意程序的健壮性,从两个方面实践提升健壮性:

  • 契约,在设计接口时定义好协议参数,并在实现时第一时间校验参数,如果参数有问题,直接返回给调用方; 如果出现异常情况, 也按异常情况约定应对策略
  • 考虑各种边界条件的输出,比如运单号查询服务, 要考虑用户输入错误运单时怎么返回,有边界的查询条件,如果用户查询条件超过边界了, 应该返回什么
  • 为失败做设计,如果出问题了有降级应对方案。

作者:赵玉开,十年以上互联网研发经验,2013年加入京东,在运营研发部任架构师,期间先后主持了物流系统自动化运维平台、青龙数据监控系统和物流开放平台的研发工作,具有丰富的物流系统业务和架构经验。在此之前在和讯网负责股票基金行情系统的研发工作,具备高并发、高可用互联网应用研发经验。

【本文来自51CTO专栏作者张开涛的微信公众号(开涛的博客),公众号id: kaitao-1234567】

转载于:https://www.cnblogs.com/rinack/p/9018066.html

京东资深架构师代码评审歪诗相关推荐

  1. 【转载】京东资深架构师代码评审歪诗

    京东资深架构师代码评审歪诗 摘自:https://494947.kuaizhan.com/78/26/p4577983897aab9 2017-08-27 阅读74 作者:赵玉开,十年以上互联网研发经 ...

  2. Java代码评审歪诗!让你写出更加优秀的代码!

    贾言 代码评审歪诗 窗外风雪再大 也有我陪伴着你 全文字数:2000字 阅读时间:5分钟 贾言 代码评审歪诗 验幻空越重 命循频异长 依轮线日简 接偶正分壮 架构师说, 用20个字描述代码评审的内容, ...

  3. 最佳实践-代码评审歪诗

    本文有赵玉开老师总结整理,经本人同意转载至此.如需转载,请务必保留出处! 本文作者:赵玉开,十年以上互联网研发经验,2013年加入京东,在运营研发部任架构师,期间先后主持了物流系统自动化运维平台.青龙 ...

  4. 京东资深架构师教你搭建高可用高并发系统,亿级流量核心架构文档

    由于细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容! 整理了一份亿级流量网站架构核心技术.覆盖了高可用.高并发.隔离.限流.负载均衡与反向代理.多级缓存.应用 ...

  5. 腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面

    1.引言 我们常常会听说,某个互联网应用的服务器端系统多么牛逼,比如QQ.微信.淘宝.那么,一个大型互联网应用的服务器端系统,到底牛逼在什么地方?为什么海量的用户访问,会让一个服务器端系统变得更复杂? ...

  6. 你和阿里资深架构师之间,差的不仅仅是年龄(进阶必看)

    导读:阅读本文需要有足够的时间,笔者会由浅到深带你一步一步了解一个资深架构师所要掌握的各类知识点,你也可以按照文章中所列的知识体系对比自身,对自己进行查漏补缺,觉得本文对你有帮助的话,可以点赞关注一下 ...

  7. 以朋友圈为例,腾讯资深架构师揭秘鹅厂大数据平台是怎样运营的

    导读:本文将从构成运营成本的主要运营资源(设备资源.带宽资源.专线资源)出发,以实际案例分别阐述精细化技术运营实施的要点. 需要提醒注意的是:精细化技术运营的目标是创造价值,而不是为了摧毁价值.精细化 ...

  8. 资深架构师十几年的架构干货经验总结分享!

    图片来源:pexels.com 1 架构师承担什么样的责任 记录片<黑猩猩的守护者>中珍妮·古道尔博士说过:「唯有了解,才會關心,唯有關心,才會採取行動,唯有行動,生命才有希望」,套用到架 ...

  9. 看下资深架构师平时需要解决的问题,对比你离资深架构师还有多少距离——再论技术架构的升级之路...

    我目前奋力在技术架构的路上不断前行,虽然中间遇到很多障碍,目前自己感觉,勉强能达到架构师的级别,所以自己感觉还有底气写这篇文章. 之前,我写过篇博文,架构师更多的是和人打交道,说说我见到和听说到的架构 ...

最新文章

  1. Numpy入门教程:11. 时间日期和时间增量
  2. IDEA如何将git分支代码合并到master
  3. 一套简约漂亮的响应式博客园主题皮肤分享给你们(一)
  4. 学习string,stringBuffer时遇到的问题
  5. Flutter入门:Button
  6. 怎样制作滴滴截图_滴滴老了吗?
  7. php访问url json,PHP操作URL和PHP操作json
  8. 9.20PMP每日一题
  9. Vue.js 源码分析(十七) 指令篇 v-if、v-else-if和v-else 指令详解
  10. 因为链接服务器 IP 的 OLE DB 访问接口 SQLNCLI 无法启动分布式事务
  11. 简单粗暴的流水灯仿真和代码
  12. Linux上配置Gaussian的方法
  13. 一文详解高功率音频放大器的设计准则与诀窍
  14. 利用计算机名称共享打印机步骤,如何连接共享打印机汇总教程
  15. R语言基础—学习笔记 lecture01
  16. 腾讯金融级数据库TDSQL的架构与应用
  17. mysql基础文档_mysql基础
  18. 福州三中 计算机竞赛,福建福州三中喜获信息学竞赛NOIP2020全省人数第1!总计35人获奖...
  19. 关于Scaner和BufferReader
  20. 新智元【Yoshua Bengio 亲自解答】机器学习 81 个问题及答案(最全收录)

热门文章

  1. Docker框架的使用系列教程(一)
  2. 8s保留cpu设置_使用资源设置控制CPU资源
  3. php 实现二叉树的最大深度_python实现二叉树的遍历以及其他基本操作
  4. python 输出文件中返回码为200的接口的平均响应时间_Django查看响应时间问题
  5. 33 计算机维修,33.计算机硬件检测维修与数据恢复竞赛规程(修改)全解.doc
  6. mysql设置最大查询时间_mysql如何限制sql查询时间
  7. mysql核心内幕_MySQL核心内幕
  8. 阅读替换净化规则_强力推荐一个开源阅读软件
  9. SpringMVC用注解写第一个程序HelloSpringMVC
  10. NYOJ-过河问题(贪心)