程式碼榮辱觀-以運用風格為榮,以隨意編碼為恥
編寫程式碼的八榮八恥
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?
總結
寫有技術追求的程式碼
相關閱讀
編寫程式碼的「八榮八恥」- 以使用者易用為榮,以複雜歧義為恥
編寫程式碼的「八榮八恥」- 以開關上線為榮,以自信編碼為恥
編寫程式碼的「八榮八恥」(上篇)
&n