编写代码的八荣八耻
1. 产品命名:以简单有趣为荣,以平庸难记为耻。
2. 单个函数:以短小精悍为荣,以冗长费神为耻。
3. 代码维护:以持续重构为荣,以停滞不前为耻。
4. 编程风格:以运用风格为荣,以随意编码为耻。
5. 程序设计:以开关上线为荣,以自信编码为耻。
6. 接口定义:以用户易用为荣,以复杂歧义为耻。
7. 断言分支:以实时报警为荣,以忽略分支为耻。
8. 监控报警:以定时调整为荣,以放弃维护为耻。
5Why分析
(一)
Q: 谁需要学习编写代码的八荣八耻?
A: 项目中的开发人员、项目经理、架构师
(二)
Q: 为什么学习编写代码的八荣八耻?
A: 可以作为实际代码编写和review(复查)的指导规范
(三)
Q: 什么人什么时候需要review代码?
A:
对开发人员来说,需要在时间允许的条件下定期的review自己和别人的代码,加深对项目的整体理解。对自己的成长做总结。如果过了一段时间,还看到自己之前的代码,觉得写的很好的话,就需要质疑自己的成长,更努力的学习了。
对于项目经理和架构师来说,鼓励所有上线的功能都每周抽出时间来做个组内review。或者定期抽取一些模块做review。鼓励大家重构代码。在review过程中,作为领导者需要对大家有输出,对代码怎么写是更好的有一些理论基础。这时候就需要使用编写代码的八荣八耻作为review的指导规范。
(四)
Q: 怎么用作review的指导规范?
A: 八荣八耻中不但介绍了每个条目的意义,而且有通俗易懂的代码实例便于和实际中的代码在头脑中做对比。文中明确的指出了哪些写法是鼓励的、哪些是不鼓励的,是基于什么理由不鼓励这样做。
(五)
Q: 编写代码的八荣八耻对于高可用有什么意义?
A: 我利用美团的内部运维平台对自己参与过的项目可用性做过统计。将影响可用性的case(具体事件)分成:开发因素和设计因素。开发因素包括系统bug、开发不规范、上线不规范、监控报警不及时(影响可用性的恢复时长)等由于具体开发者在设计阶段覆盖不到的阶段发生的。设计因素包括机器故障、网络中断、异常流量、中间件故障等可以通过设计做容灾的。结果95%以上的可用性问题都是开发因素造成的。编写代码的八荣八耻是对避免开发因素产生可用性问题的指导规范。
编程风格:以运用风格为荣,以随意编码为耻
引子
在工作中,经常发现有些程序员用面向对象的语言写出了面向过程的代码而自己并没有感觉到:
前面提到有个java软件工程师,叫Margaret。她对工作有三个要求:钱多、有趣、离家近。HR想针对这些要求和她具体沟通,问她最低标准是什么。每一项最低要求回复一个星级。
星级 | 钱多 | 有趣 | 离家近 | |
---|---|---|---|---|
☆ | 1 | 年薪10万 | 出差 旅游占工时1% | 40公里 |
☆☆ | 2 | 年薪20万 | 出差 旅游占工时10% | 20公里 |
☆☆☆ | 3 | 年薪50万 | 出差 旅游占工时20% | 10公里 |
☆☆☆☆ | 4 | 年薪100万 | 出差 旅游占工时50% | 2公里 |
☆☆☆☆☆ | 5 | 年薪500万 | 出差 旅游占工时80% | 1公里 |
Margaret在外地,所以用了一个常用的数据交换格式json给HR回复如下:
代码语言:javascript复制{"moreMoney”:4,"moreFun”:2,"closerToHome”:3}
拿到这个回复时面向过程的解析方式是这样写的:
代码语言:javascript复制Map json = (HashMap) JSONUtils.parse("{"moreMoney":4,"moreFun":2,"closerToHome":3}");
int moreMoney = (int)json.get("moreMoney");int moreFun = (int)json.get("moreFun");int closerToHome = (int)json.get("closerToHome");
接收方将接收到的数据转成了json,代码里一堆get完成了功能。为什么说这是面向过程的呢?map是一种数据结构,没有直接的业务意义。功能实现了,表达的意义却不清晰。
这段代码更好的一个实现方式是将接收的数据结构定义成一个对象,在java里可以使用jackson等工具直接将json转成有业务含义的对象。
代码语言:javascript复制ObjectMapper objectMapper = new ObjectMapper();
Requirement requirement = objectMapper.readValue("{"moreMoney":4,"moreFun":2,"closerToHome":3}",JavaSoftwareEngineerMargaretRequirement.class);
这样做,HR拿到的requirement不是一列列数字,需要自己对核对每一项都是什么意思。而是一个有完整语义的对象,利于理解。而以这种思路来进行编写的代码我经常称他们叫面向对象风格的代码。
WHY
来看一段写赤壁山旅行的文章:
今天有幸登上赤壁山,看到这山上的景物,不禁想起了当前战场上厮杀的场面。想起当年要不是周瑜运气好,大冬天刮起了东风,恐怕吴国就被曹操灭了。
再来看唐代诗人杜牧经过赤壁山这个著名的古战场,有感于三国时代的英雄成败而写下的《赤壁》:
折戟沉沙铁未销,自将磨洗认前朝。
东风不与周郎便,铜雀春深锁二乔。
前两句意思是在沙子底下找到一只断戟,磨洗之后发现“made in 赤壁”。读者读了这两句不禁会联想起当前战场上厮杀的场面吧。
后两句意思是要不是周瑜运气好,大冬天刮起了东风。那孙权的老婆大乔和周瑜的老婆小乔这两位绝世美女都要被曹操这个色老头关进铜雀台了。因为曹操久仰大小乔的美貌,提前为二人修筑铜雀台,作为打败吴国的战利品。
杜牧只字未提战场和如果没有东风的运气,将会亡国的下场。但是读者却能心领神会,印象深刻。这是因为杜牧采用了以小见大的风格手法。
代码与代码的区别如同文章与文章的区别。能否让读者以更短的时间、更轻松的读懂?代码是给人整体感还是恶心感?这些都决定了代码的可维护性。而它和系统可用性、稳定性的最直接关系在工作中非常常见:“爷爷的!这是谁写的代码这么烂?忍不了了,老子不干了。”而这个代码的作者之所以离职也是因为忍受不了自己的烂代码。频繁的人员更替,新接手人员要有学习的成本。成本就包括要踩坑来加深对系统的理解。
HOW
除了开头提到的面向对象的风格,编写java代码时下面三种风格也很常见。
1.fluent风格
fluent风格的代码常以Builder结尾。比如StringBuilder就是典型的fluent风格。定义一个人的对象,这个对象使用fluent风格代码这么写:
代码语言:javascript复制public class Person { private String name; private int armCount=2;//胳膊数默认为2 private int legCount=2;//腿数默认为2
public static Person builder() { return new Person.Builder(); }
public Person setName(String name) { this.name = name; return this; }
public Person armCount(int armCount) { this.armCount = armCount; return this; }
public void legCount(int legCount) { this.legCount = legCount; }}
如上,就是每次给对象赋属性的时候同时返回对象本身。这样调用的时候:
代码语言:javascript复制Person.builder().name("Jane").armCount(2).legCount(2);
这样写的好处是比每个属性都用一句set简洁。在属性多的时候,用构造函数。调用时容易表达不清楚属性的含义。方法名起到了解释的作用。现在流行的做法是代码即注释,注释不用在每个方法都写。这时候能表达自身意义的代码就更加重要。注意:我们也可以保留setXXX、getXXX的命名规范,因为jackson等序列化反序列化的组件会根据set、get方法对参数赋值,上面的明明风格在序列化时会有问题。
当然,这个类也可以直接用lombok注解得到。
代码语言:javascript复制@Data@Buildrpublic class Person { private String name; private int armCount=2;//胳膊数默认为2 private int legCount=2;//腿数默认为2}
2.lambda函数式编程风格
lambda函数式编程比传统的命令式编程更加简洁。比如:现在有一群人。
代码语言:javascript复制List<Person> personList = Lists.newArrayList();
personList.add(Person.builder().name("Jane"));personList.add(Person.builder().name("Joe").armCount(1));personList.add(Person.builder().name("Stark").legCount(1));
要找出所有的残疾人:
代码语言:javascript复制List<Person> disabledPersonList = Lists.newArrayList();for(Person person : personList) { if(person.legCount()!=2 || person.armCount()!=2) { disabledPersonList.add(person); }}
或者使用lambda函数式编程:
代码语言:javascript复制List<Person> disabledPersonList = personList.stream().filter(person -> person.legCount()!=2 || person.armCount()!=2).collect(Collectors.toList());
3.设计模式风格
1995年,GoF(Gang of Four,四人帮)合作出版了《设计模式:可复用面向对象软件的基础》一书,共收录了23中设计模式,人称“GoF设计模式”。这23种设计模式的本质是面向对象设计原则的实际运用,是一种最佳实践。
在各种java源码中,经常看到以设计模式命名的类名和方法名。在我们日常编码中,设计模式也非常实用。设计模式风格的例子请参考:平时代码中用不到设计模式?Are you kidding me?
总结
写有技术追求的代码