寫出優雅的代碼

《代碼整潔之道》一書中說:當你的代碼在做 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) {
    //業務邏輯代碼
}

關於如何寫出優雅的代碼,還有很多的小細節可以補充,這裏就不一一列舉了,感興趣的可以自己查閱。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章