編寫代碼的八榮八恥
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回復如下:
{"moreMoney”:4,"moreFun”:2,"closerToHome”:3}
拿到這個回復時面向過程的解析方式是這樣寫的:
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轉成有業務含義的對象。
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風格代碼這么寫:
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; } }
如上,就是每次給對象賦屬性的時候同時返回對象本身。這樣調用的時候:
Person.builder().name("Jane").armCount(2).legCount(2);
這樣寫的好處是比每個屬性都用一句set簡潔。在屬性多的時候,用構造函數。調用時容易表達不清楚屬性的含義。方法名起到了解釋的作用。現在流行的做法是代碼即注釋,注釋不用在每個方法都寫。這時候能表達自身意義的代碼就更加重要。注意:我們也可以保留setXXX、getXXX的命名規范,因為jackson等序列化反序列化的組件會根據set、get方法對參數賦值,上面的明明風格在序列化時會有問題。
當然,這個類也可以直接用lombok注解得到。
@Data@Buildr public class Person { private String name; private int armCount=2;//胳膊數默認為2 private int legCount=2;//腿數默認為2 }
2.lambda函數式編程風格
lambda函數式編程比傳統的命令式編程更加簡潔。比如:現在有一群人。
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));
要找出所有的殘疾人:
List<Person> disabledPersonList = Lists.newArrayList(); for(Person person : personList) { if(person.legCount()!=2 || person.armCount()!=2) { disabledPersonList.add(person); } }
或者使用lambda函數式編程:
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?
總結
寫有技術追求的代碼
相關閱讀