天天看點

寫給所有程式員_你的邏輯可以更簡潔易讀嗎?

1.if層次過多。

舉一個很有趣的例子,假如世界上隻有兩種烤鴨,在北京的叫做北京烤鴨,不在北京的叫做非北京烤鴨,寫爛代碼的程式員思路如下:

如果烤鴨不在地球,它一定不是北京烤鴨;

如果烤鴨不在陸地,它一定不是北京烤鴨;

如果烤鴨不在亞洲,它一定不是北京烤鴨;

如果烤鴨不在中國,它一定不是北京烤鴨;

如果烤鴨不在北京,它一定不是北京烤鴨;

否則,這是北京烤鴨。

僞代碼如下:

if(!烤鴨在地球){
        輸出非北京烤鴨。
    }else{
        if(!烤鴨在陸地){
            輸出非北京烤鴨。
        }else{
            if(!烤鴨在亞洲){
                輸出非北京烤鴨。
            }else{
                if(!烤鴨在亞洲){
                    輸出非北京烤鴨。
                }else{
                    if(!烤鴨在中國){
                        輸出非北京烤鴨。
                    }else{
                        if(!烤鴨在北京){
                            輸出非北京烤鴨。
                        }else{
                            輸出北京烤鴨。
                        }      

好把,如果真有哪個程式員是這樣寫代碼的,那麼我真的很想一巴掌把它拍到火星去。不要這樣嵌套好嗎?就算你真的是這麼想,也可以這樣寫:

if(!烤鴨在地球){
    輸出非北京烤鴨。
}else if(!烤鴨在陸地){
    輸出非北京烤鴨。
}else if(!烤鴨在亞洲){
    輸出非北京烤鴨。
}else if(!烤鴨在中國){
    輸出非北京烤鴨。
}else if(!烤鴨在北京){
    輸出非北京烤鴨。
}else{
    輸出北京烤鴨。
}      

這個時候你可以發現原來前四個條件的内容都是相同的,那麼可以這樣寫:

if(!烤鴨在地球
    ||!烤鴨在陸地
    ||!烤鴨在亞洲
    ||!烤鴨在中國
    ||!烤鴨在北京){
    輸出非北京烤鴨。
}else{
    輸出北京烤鴨。
}      

當你發現前面if的條件過分臃腫的時候,可以考慮反過來會不會更容易

最終變成:

if(烤鴨在北京){
    輸出北京烤鴨。
}else{
    輸出非北京烤鴨。
}      

可見,邏輯簡化可彌補思維的不足。

這裡有兩個公式:

公式1:

if(條件1){
    //做事情A
}else if(條件2){
    //做事情A      

可以轉換為:

if(條件1 || 條件2){
    //做事情A      
if(條件1){
    if(條件2){
        //做事情A      

可以轉換為:

if(條件1 && 條件2){
    //做事情A      

如果條件太多,且實在不知道怎麼合并,可以把條件單獨放到一個函數:

public void doSomething(){
    if(isRoastDuckNotInBeijing()){
    輸出非北京烤鴨。
    }else{
        輸出北京烤鴨。
    }
}

public boolean isRoastDuckNotInBeijing(){
    return      

注意,如果條件可讀性強:比如sex == “man”就不要把條件放到單獨函數。

2.學會使用return降低層次

public void doSomething(){
    if(firstNum!=0){
        if(secondNum!=0){
            //doSomething      

如果改為:

public void doSomething(){
    if(firstNum == 0)
        return;
    if(secondNum == 0)
        return;
    //doSomething      

看起來好多了。

3.學會使用函數分割代碼

我是在上大學的時候學會寫代碼的。有一點解不開的疑惑:為什麼要寫函數?我把代碼從頭寫到尾邏輯不是更加通順嗎?每個教代碼的老師,總是在開始的時候給我們講什麼是變量,什麼是函數,怎麼用?我好想說一句:喂,我還沒決定要用函數呢!

講這部分之前,我先講講這個為什麼要寫函數,講為什麼要寫函數前,我不得不提提二分查找法。

舉個例子:

有十六張撲克牌按照編号從小的順序排列,我讓你找出編号排名第11的,你怎麼找呢?

按照順序一本本找,你要找11次,如果我找的是最後一張,需要找16次(當然這裡我們不讨論倒着找)

使用二分查找:分成一半,前面是1-8,後面是9-16,然後再分成一半9-12和13-16,然後再分成一半9-10和11-12,然後我找到了。我找了4次。發現了嗎?速度比順序查找要快,而且我最多隻需要找4次。當查找的範圍越大,二分查找相對順序查找的優勢就越明顯,平均速度也更快。

好了,懂了二分查找,我們再來弄清楚為什麼要寫函數

舉個例子:假設我要寫100行代碼,僞代碼如下:

public void 寫100行代碼(){
    寫1-50行代碼();
    寫51-100行代碼();
}

public void 寫1-50行代碼(){
    寫1-25行代碼();
    寫26-50行代碼();
}

public void 寫51-100行代碼(){
    寫51-75行代碼();
    寫75-100行代碼();
}      

這樣如果我找寫第70行代碼寫什麼,隻要使用二分法的原理:先找寫100行代碼(),然後找寫51-100行代碼();以此類推。

是以我們寫函數的目的就在此:把代碼分割成小的部分,更容易查找。

這樣做有個前提:就是你的函數名必須有意義并且表達準确,否則就更浪費時間,如上文的寫1-50行代碼()和寫51-100行代碼()函數名調換,内容不變,識别起來難度就會變大,你先找寫1-50行代碼(),發現它其實是寫後面50行代碼,這會讓你在邏輯上糾結一段時間,是以再次強調:函數名一定要有意義。

另外還有一點,分割項目不要一次分的太細比如上面的例子,我也可以這樣寫(為了顯示效果,我沒寫省略号,就一行行寫了):

public void 寫100行代碼(){
    寫1-5行代碼();
    寫6-10行代碼();
    寫11-15行代碼();
    寫16-20行代碼();
    寫21-25行代碼();
    寫26-30行代碼();
    寫31-35行代碼();
    寫35-40行代碼();
    寫41-45行代碼();
    寫46-50行代碼();
    寫51-55行代碼();
    寫56-60行代碼();
    寫61-65行代碼();
    寫66-70行代碼();
    寫71-75行代碼();
    寫76-80行代碼();
    寫81-85行代碼();
    寫86-90行代碼();
    寫91-95行代碼();
    寫96-100行代碼();
}      

發現了嗎?這段代碼的可讀性明顯變差了。是以一般情況下這種分割函數(裡面每一句都調用其他函數),分割函數代碼不要超過10句,最好不要超過5句。

和分割函數相對應的,還有一種我稱作邏輯函數,這種函數都是做具體的工作,一般不要超過20行,如下:

public void      

特殊情況1:有一堆的參數指派/傳遞:

比如錄入個人資訊:有身高,體重,愛好,優點,缺點,性别,父名,母名…這種可能會有很多資料,可能遠遠超過20行,那麼,就把這部分代碼單獨分割:

public void outputUserInfo(){
    System.out.println(getUserInfo());
}

public UserInfo getUserInfo(){
    UserInfo userInfo  = new UserInfo();
    userInfo.setHigh(10);
    userInfo.setHeavy(1000);
    userInfo.setHobby("泡妞");
    userInfo.setAdvantage("很帥");
    userInfo.setDisadvantage("我是世界上最帥的!");
    userInfo.setFatherName("爸爸");
    userInfo.setMotherName("媽媽");
    //...此處省略萬字
    return      

大概是這樣,其實真的無法分割嗎?并沒有這樣說法。上面的内容可分為:身體資訊,偏好資訊和親友資訊,于是上面的一個UserInfo就可以分割為BodyInfo,LoveInfo和RelativesInfo,于是又可以保持在20行之内了

ヾ(◍°∇°◍)ノ゙

特殊情況2:邏輯分支,回調和異常處理

邏輯分支例子:

if(!烤鴨在地球){
    輸出非北京烤鴨。
    輸出目前地點。
}else if(!烤鴨在陸地){
    輸出非北京烤鴨。
    輸出目前地點。
}else if(!烤鴨在亞洲){
    輸出非北京烤鴨。
    輸出目前地點。
}else if(!烤鴨在中國){
    輸出非北京烤鴨。
    輸出目前地點。
}else if(!烤鴨在北京){
    輸出非北京烤鴨。
    輸出目前地點。
}else{
    輸出北京烤鴨。
}      

回調例子:

public void clickButton(){
    button.setOnClickListener(
        new OnClickListener(){
            @Overriade
            public void onClick(View v){
                輸出目前地點。
                輸出北京烤鴨。
            }
    }
   );
}      

異常處理例子:

public void methodName(){
    try{
        輸出北京烤鴨。
        輸出目前地點。
    }catch(Exception e){
        e.printStackTrace();
    }
}      

上面三種情況:選擇分支,回調和異常處理都會導緻代碼臃腫,可讀性變差,我通常都會把裡面的部分單獨列到新的:

第一種:

合成為:輸出結果(boolean isRoastDuckInBeijing,String address)

因為條件分支往往好起名字,是以我通常會這樣寫

第二種:

clickButtonEvent(){}

因為我不能保證除了輸出北京烤鴨資訊外是否會做些别的什麼,通常這部分的修改頻率較高

第三種:

public void methodNameThrowException() throw Exception{}

通常我會在原名稱後加上ThrowException提醒自己抛出異常,這樣既顯示了差別也降低層次,看起來更舒服了:

public void methodName(){
    try{
        methodNameThrowException();
    }catch(Exception e){
        e.printStackTrace();
    }
}

public void methodNameThrowException() throw      

繼續閱讀