c# - student - vs2015 resharper




反轉“if”語句以減少嵌套 (16)

C中有多個返回點是一個問題(C ++程度較低),因為它們迫使您在每個返回點之前複製清理代碼。 隨著垃圾收集, try | finally構建和using塊,真的沒有理由為什麼你應該害怕它們。

最終歸結為你和你的同事們更容易閱讀。

當我在代碼上運行ReSharper時,例如:

    if (some condition)
    {
        Some code...            
    }

ReSharper給了我上面的警告(反轉“如果”的聲明,以減少嵌套),並提出以下更正:

   if (!some condition) return;
   Some code...

我想明白為什麼這樣更好。 我一直認為在一個方法中使用“return”是有問題的,有點像“goto”。


它不僅美觀 ,而且還降低了方法內的最大嵌套水平 。 這通常被認為是一個加號,因為它使得方法更容易理解(事實上, 許多 靜態 分析 工具提供了一個衡量代碼質量的指標之一)。

另一方面,這也使得你的方法有多個出口點,而另一些人認為是不可行的。

就我個人而言,我同意ReSharper和第一個小組(用一種有例外的語言,我覺得討論“多個退出點”是愚蠢的;幾乎任何東西都可以拋出,所以在所有方法中有許多潛在的退出點)。

關於性能 :在每種語言中,兩個版本應該是等價的(如果不是在IL級別,那麼肯定在抖動通過代碼之後)。 理論上這取決於編譯器,但是實際上今天任何廣泛使用的編譯器都能處理比這更先進的代碼優化案例。


在性能方面,兩種方法之間沒有明顯的差異。

但是編碼不僅僅是性能。 清晰度和可維護性也非常重要。 而且,在這種不影響性能的情況下,這是唯一重要的事情。

對於哪一種方法更可取,有各種各樣的思想流派。

一種觀點是另一種觀點:第二種方法降低了嵌套層次,這提高了代碼的清晰度。 這是一種必然的風格:當你什麼也沒有做,你不如早日回來。

另一種觀點,從更實用的角度來看,一種方法應該只有一個出口點。 一切功能語言都是一種表達。 所以如果語句必須總是有一個else子句。 否則,if表達式不會總是有一個值。 所以在功能風格上,第一種方法更自然。


在我看來,如果你只是返回void(或者你永遠不會檢查的一些無用的返回代碼),提前返回就沒有問題,並且可能會提高可讀性,因為你避免了嵌套,並且同時明確表示你的函數已經完成。

如果你實際上返回了一個returnValue,嵌套通常是一個更好的方法,因為你返回你的returnValue只是在一個地方(在結束時),它可能會讓你的代碼在大量情況下更易於維護。


守衛條款或前提條件(你可能會看到)檢查是否符合某個條件,然後中斷程序的流程。 對於那些你真的只對if語句的結果感興趣的地方來說,它們非常棒。 所以,而不是說:

if (something) {
    // a lot of indented code
}

如果滿足反轉條件,則反轉條件併中斷

if (!something) return false; // or another value to show your other code the function did not execute

// all the code from before, save a lot of tabs

return遠不如去骯髒。 它允許你傳遞一個值來顯示你的代碼的其餘部分,該函數不能運行。

您將看到可以在嵌套條件下應用的最佳示例:

if (something) {
    do-something();
    if (something-else) {
        do-another-thing();
    } else {
        do-something-else();
    }
}

VS:

if (!something) return;
do-something();

if (!something-else) return do-something-else();
do-another-thing();

你會發現很少有人認為第一個更清潔,但當然,這是完全主觀的。 一些程序員喜歡通過縮進知道什麼樣的條件,而我更願意保持方法流線性。

我不會建議precons會改變你的生活或讓你奠定,但你可能會發現你的代碼更容易閱讀。


從理論上講,如果提高分支預測命中率,反轉if會導致更好的性能。 實際上,我很難確切地知道分支預測的行為,特別是在編譯之後,所以我不會在日常開發中這樣做,除非我正在編寫彙編代碼。

更多關於分支預測這裡


我個人更喜歡只有1個退出點。 如果你保持方法簡潔明了,並且為下一個工作在你的代碼上的人提供了一個可預測的模式,那麼這很容易實現。

例如。

 bool PerformDefaultOperation()
 {
      bool succeeded = false;

      DataStructure defaultParameters;
      if ((defaultParameters = this.GetApplicationDefaults()) != null)
      {
           succeeded = this.DoSomething(defaultParameters);
      }

      return succeeded;
 }

如果您只想在函數退出之前檢查函數中某些局部變量的值,這也非常有用。 所有你需要做的就是在最後的回報上放置一個斷點,並且你保證會觸發它(除非拋出異常)。


我想補充一點,那就是那些倒掉的名字 - 守衛條款。 我可以隨時使用它。

我討厭閱讀代碼,如果在開始,有兩個代碼屏幕,沒有其他的。 只要倒過來,並返回。 這樣,沒有人會浪費時間滾動。

http://c2.com/cgi/wiki?GuardClause


我認為這取決於你喜歡什麼,如上所述,沒有一般的協議afaik。 為了減少煩惱,你可以減少這種警告“提示”


方法中的回報不一定是壞的。 如果代碼的意圖更清晰,最好立即返回。 例如:

double getPayAmount() {
    double result;
    if (_isDead) result = deadAmount();
    else {
        if (_isSeparated) result = separatedAmount();
        else {
            if (_isRetired) result = retiredAmount();
            else result = normalPayAmount();
        };
    }
     return result;
};

在這種情況下,如果_isDead為真,我們可以立即退出該方法。 而是用這種方式來構建它可能會更好:

double getPayAmount() {
    if (_isDead)      return deadAmount();
    if (_isSeparated) return separatedAmount();
    if (_isRetired)   return retiredAmount();

    return normalPayAmount();
};   

我從重構目錄中選擇了這個代碼。 這個特定的重構稱為:用嵌套條件替換嵌套條件。


這是一個宗教論點,但我同意ReSharper,你應該更喜歡少嵌套。 我相信這超過了從一個函數有多個返迴路徑的負面影響。

少嵌套的關鍵原因是提高代碼的可讀性和可維護性 。 請記住,許多其他開發人員將來需要閱讀您的代碼,而使用較少縮進的代碼通常更容易閱讀。

先決條件是一個很好的例子,可以在函數啟動的早期返回。 為什麼其他功能的可讀性會受到先決條件檢查的影響?

至於從方法中多次返回的否定信息 - 調試器現在非常強大,而且很容易找出特定函數返回的位置和時間。

在一個函數中有多個返回不會影響維護程序員的工作。

糟糕的代碼可讀性會。


這是一個意見的問題。

我通常的做法是避免單行ifs,並返回一個方法的中間。

在你的方法中你不需要像這樣的行,但是在你的方法的頂部檢查一堆假設還有一些東西需要說明,而且如果它們全都通過了,你只能做實際的工作。


這種編碼有幾個優點,但對我來說,最重要的是,如果你能快速返回,你可以提高你的應用程序的速度。 IE我知道,因為Precondition X,我可以很快返回一個錯誤。 這首先擺脫了錯誤的情況,並降低了代碼的複雜性。 在很多情況下,因為CPU管道現在可以更清潔,它可以停止管道崩潰或開關。 其次,如果你在一個循環中,快速斷開或返回可以為你節省大量的CPU。 一些程序員使用循環不變式來做這種快速的退出,但是在這裡你可能會破壞你的CPU管道,甚至造成內存尋道問題,這意味著CPU需要從外部緩存中加載。 但基本上我認為你應該做你想做的事情,那就是結束循環或者函數不是創建一個複雜的代碼路徑來實現一些抽象的正確代碼的概念。 如果你有唯一的工具是錘子,那麼一切都像一個釘子。


這簡直是有爭議的。 程序員之間並沒有就提前回報問題達成一致。 就我所知,這總是主觀的。

有可能做出一個表現的論點,因為最好是有條件寫出來,因為他們往往是真實的; 也可以認為它更清楚。 另一方面,它確實創建了嵌套測試。

我不認為你會得出這個問題的結論性答案。


避免多個退出點可能會導致性能提升。 我不確定C#,但在C ++中,命名返回值優化(Copy Elision,ISO C ++ '03 12.8 / 15)取決於具有單個退出點。 這種優化避免了複製構造你的返回值(在你的具體例子中並不重要)。 這可能會導致在緊密循環中的性能顯著提高,因為每次調用函數時都要保存一個構造函數和一個析構函數。

但是,對於保存附加的構造函數和析構函數調用的情況, if塊引入(如其他人所指出的),則99%的情況不值得丟失可讀性。


那裡已經有了很多有洞察力的答案,但是我仍然會指出一個稍微不同的情況:不是先決條件,而是應該放在函數的頂部,想一步一步的初始化,必須檢查每一步成功,然後繼續下一步。 在這種情況下,你不能檢查頂部的所有內容。

當我使用Steinberg的ASIOSDK編寫ASIO主機應用程序時,我發現我的代碼真的不可讀,就像我遵循嵌套範例一樣。 它像八層深,我看不出有一個設計缺陷,如上面安德魯·布洛克所說。 當然,我可以將一些內部代碼打包到另一個函數中,然後在其中嵌套剩餘的關鍵字以使其更具可讀性,但這對我來說似乎相當隨意。

通過用守衛子句取代嵌套,我甚至發現了一個關於清理代碼的錯誤概念,這個錯誤應該在函數內部早些時候發生,而不是在最後。 嵌套分支,我永遠不會看到,你甚至可以說他們導致了我的誤解。

所以這可能是另一種情況,倒轉ifs可以有助於更清晰的代碼。





resharper