《代碼整潔之道》一書中說:當你的代碼在做 Code Review 時,審查者要是憤怒地吼道:“What the fuck, is this shit?”、“Dude, What the fuck!”等言辭激烈的詞語,那說明你寫的代碼是 Bad Code,如果審查者只是漫不經心的吐出幾個:“What the fuck?”,那說明你寫的是 Good Code。衡量代碼質量的唯一標準就是每分鐘罵出“WTF”的頻率。
代碼不規範會帶來很多負面影響,比如團隊之間如果代碼不統一規範,對審查閱讀都會不方便,後期的維護複雜度也會增加,降低團隊效率。另外,不規範的代碼可能在不小心中埋下bug,比如參數、異常處理、日誌等不規範都會讓你背鍋。優雅的代碼讀起來讓人賞心悅目、通俗易懂,對後期重構也大有幫助。比如如下一段代碼,看的你感覺如何?
if(db.Next()) {
return true;
} else {
return false;
}
這段代碼不是我爲了文章而編出來的,而是在工作中經常看到,如果這段代碼改成如下,效果顯而易見。
return db.Next();
關於如何寫出優雅的代碼,我有以下看法:
1、代碼格式化
如果不格式化,很多字符符號之間連空格都沒有,好不容易看到一個空格確是連續三四個空格。格式化代碼使閱讀更加方便,後期修改代碼更容易發現修改處。
記住:代碼要格式化!
2、讓判斷條件做真正的選擇
if ("0000".equals(retCode)) {
sendMsg("0000", "success", result);
} else {
sendMsg("0000", "fail", result);
}
如上一段代碼,if/else裏面調用的方法一樣,真正不一樣的僅僅一個參數,我們可以簡單優化爲如下格式:
String msg = "0000".equals(retCode) ? "success" : "fail";
sendMsg("0000", msg, result);
這段代碼中,條件判斷真正有影響的區別是獲取消息內容,而不是發送消息方法,所以我們要讓判斷條件條件做真正的選擇,將獲取消息內容和發送消息方法分開。
這樣消除了代碼中的冗餘,代碼也更容易理解,同時未來也更容易擴展。如果未來retCode變量有更多的值返回需要判斷,我們只需要調整消息獲取相關代碼即可,這種情況下封裝一下消息獲取的相關代碼更好,如下:
sendMsg("0000", getMsg(retCode), result);
3、判斷條件的簡而言之
這是一個長長的判斷條件:
if ("DropGroup".equals(operationName)
|| "CancelUserGroup".equals(operationName)
|| "QFUserGroup".equals(operationName)
|| "CancelQFUserGroup".equals(operationName)
|| "QZUserGroup".equals(operationName)
|| "CancelQZUserGroup".equals(operationName)
|| "SQUserGroup".equals(operationName)) {
//業務邏輯代碼
}
隨着業務的增長,這段代碼裏的判斷條件可能還會增加,代碼越來越長。很多人坦然面對這段代碼,可能是顯示器足夠大。如果一個方法裏僅有該段if代碼尚能接受,但是當一段代碼中突然冒出一個十幾行的if判斷,真的很不優雅。這段代碼可以優化如下:
private boolean shouldExecute(String operationName) {
return "DropGroup".equals(operationName)
|| "CancelUserGroup".equals(operationName)
|| "QFUserGroup".equals(operationName)
|| "CancelQFUserGroup".equals(operationName)
|| "QZUserGroup".equals(operationName)
|| "CancelQZUserGroup".equals(operationName)
|| "SQUserGroup".equals(operationName);
}
然後調用
if (shouldExecute(operationName)) {
//業務邏輯代碼
}
現在,雖然條件依然還是很多,但比起原來龐大的函數,至少它已經被控制在一個相對較小的函數裏了。更重要的是,通過函數名,我們終於有機會告訴世人這段代碼判斷的是什麼了。
其實這段代碼還可以進一步進行優化,如下:
private boolean shouldExecute(String operationName) {
String[] executeTypes = {"DropGroup",
"CancelUserGroup",
"QFUserGroup",
"CancelQFUserGroup",
"QZUserGroup",
"CancelQZUserGroup",
"SQUserGroup"};
for (String execute : executeTypes) {
if (execute.equals(operationName)) {
return true;
}
}
return false;
}
這樣以後如果我們需要新增一個type,只需要在executeTypes數組中新增一個就可以了。而且將type列表變成聲明式,進一步提高了代碼的可讀性。
通過以上列子,我們可以得出兩個可以使代碼更加優雅的小方法:
判斷條件不准許多個條件的組合(一般不能出現3個條件及其以上),如果有的話建議提煉爲一個方法;
參數、方法、變量等命名至關重要,成功的命名簡單易懂,甚至註釋都不需要;
4、copy帶來的重複
這是一段你需要來找茬的代碼:
if (1 == insertFlag) {
retList.insert(i, newCatalog);
} else {
retList.add(newCatalog);
}
if (1 == insertFlag) {
retList.insert(m, newCatalog);
} else {
retList.add(newCatalog);
}
if (1 == insertFlag) {
retList.insert(j, newPrivNode);
} else {
retList.add(newPrivNode);
}
以上3段代碼,除了用到的變量不同之外,其它完全相同!
程序員特別擅長複製粘貼,以上這段代碼第二個人複製第一個人寫的,然後代碼裏重複代碼特別多,後期修改和重構極易出現錯誤。複製一時爽,一直複製一時爽,但是缺少了自己的想法,這就是爲什麼有人說寫代碼是個體力活。比如以上代碼,提出一個新的方法就可以了。
private void addNode(List<Node> retList, int insertFlag, int pos, Node node) {
if (1 == insertFlag) {
retList.insert(pos, Node);
} else {
retList.add(node);
}
}
於是,原來那三段代碼變成了三個調用:
addNode(retList, insertFlag, i, newCatalog);
addNode(retList, insertFlag, m, newCatalog);
addNode(retList, insertFlag, j, newPrivNode);
重複,是最爲常見的壞味道。上面這種重複實際上是非常容易發現的,也是很容易修改。
5、堆代碼堆出了一座山
接到需求分析後,開始梭哈代碼,第一行開始梭哈,梭哈第100行的時候還是在一個方法裏,這樣代碼一個方法功能不夠明確,看起來眼花繚亂。比如如下一段代碼:
//業務代碼片段A(假設有30行代碼)--分析此人是否滿足條件
Person person = new Person();
person.setIdNo(xx);
person.setAddress(xx);
person.setWork(xx);
person.setPhone(xx);
person.setQQ(xx);
person.setBirthday(xx);
person.setCity(xx);
person.setMale(xx);
person.setProvince(xx);
personDAO.insert(person);
//業務代碼片段B(假設有30行代碼)--返回相關信息
如上,有的人真的會直接寫在一個方法裏,這個方法大概就有80行左右的代碼了。如果還有其他業務,這個代碼裏不停添加,最後會出現要給小山峯一般的代碼。優雅如下:
private void insertPerson() {
Person person = new Person();
person.setIdNo(xx);
person.setAddress(xx);
person.setWork(xx);
person.setPhone(xx);
person.setQQ(xx);
person.setBirthday(xx);
person.setCity(xx);
person.setMale(xx);
person.setProvince(xx);
personDAO.insert(person);
}
private Person addPersonInfo() {
checkInsertPerson();//分析是否滿足條件
Person person = insertPerson();//插入
return personInfo(person);//組合其他信息
}
談論乾淨代碼時,我們總會說,函數應該只做一件事,檢查條件歸檢查條件,增加歸增加。函數做的事越多,就會越冗長,也就越難發現不同函數的相似之處。爲了一個問題,要在不同的地方改來改去也就難以避免了。面對長長的函數,不要再無動於衷了,不要繼續往裏塞着“新”代碼。
6、不要隨意定義一個變量
不少人在分析業務的時候就考慮到了需要哪些變量,於是在方法的開頭,就依次把需要的變量定義一番,如下:
private void addFruits(List<Fruit> fruits, String type) {
Apple apple = createApple();
Banana banana = createBanana();
if ("fruit".equals(type)) {
fruits.add(apple);
fruits.add(banana);
}
}
這段代碼可以優雅如下:
private void addFruits(List<Fruit> fruits, String type) {
if ("fruit".equals(type)) {
fruits.add(createApple());
fruits.add(createBanana());
}
}
省出了變量的聲明,如果你追求簡單易懂的變量聲明,這種方式會幫你節省一定的時間。關於變量我建議兩點:在需要的地方纔聲明,不要開始就聲明出來;適當的時候不用聲明出來,何必要給自己增加麻煩。
另外增加一個常見的代碼:
String gender;
if ("F".equals(sex)) {
gender = "女";
} else {
gender = "男";
}
這樣的代碼真的特別容易見到,可以優雅如下:
String gender = "F".equals(sex) ? "女":"男";
7、封裝你的固定值
在我們的代碼中,經常會出現一些狀態判斷,如下:
if ("DONE".equals(status) {
//業務邏輯代碼
}
這樣的代碼,哪一天萬一DONE改了個值,或者由於業務改變,狀態DONE的時候還增加一個其他判斷,所有的地方都要改,那你就有大事情要做了。
首先我們可以用函數封裝固定值,如:
private String getDoneStatus() {
return "DONE";
}
if (getDoneStatus().equals(status) {
//業務邏輯代碼
}
還有一種方法,既然DONE是固定值,我們判斷的就是這個狀態,索性直接封裝,後面如果這個狀態統一增加其他條件也比較方便,如:
private boolean isDoneStatus(String status) {
return "DONE".equals(status);
}
if ( isDoneStatus(status) {
//業務邏輯代碼
}
關於如何寫出優雅的代碼,還有很多的小細節可以補充,這裏就不一一列舉了,感興趣的可以自己查閱。