c# - student - vs2015 resharper




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

它不僅是美學的 ,而且還降低了方法內的最大嵌套層次 。 這通常被認為是一個加號,因為它使得方法更容易理解(事實上, many static analysis tools提供了這種作為代碼質量指標之一的度量)。

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

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

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

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

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

ReSharper給了我上述警告(反轉“如果”聲明以減少嵌套),並建議進行以下更正:

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

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


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

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

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

其中一種觀點是其他人提到的:第二種方法降低了嵌套層次,從而提高了代碼的清晰度。 這是一種必然的風格:當你沒有什麼可以做的時候,你最好早點回來。

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


在語言支持異常之前,只有在函數結束時才返回的想法回來了。 它使程序能夠依靠能夠將清理代碼放在方法的末尾,然後確保它會被調用,並且其他程序員不會隱藏導致清理代碼被跳過的方法中的返回。 跳過的清理代碼可能導致內存或資源洩漏。

但是,在支持異常的語言中,它不提供這樣的保證。 在支持異常的語言中,任何語句或表達式的執行都會導致導致方法結束的控制流。 這意味著清理必須通過使用finally或using關鍵字來完成。

無論如何,我想說的是,我認為很多人引用'方法末尾的唯一回報'而不理解為什麼這是一件好事,而減少嵌套以提高可讀性可能是一個更好的目標。


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

最終歸結於你和你的同事發現更容易閱讀。


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

更多關於分支預測的here


性能分為兩部分。 軟件在生產時你有性能,但你也希望在開發和調試時具有性能。 開發人員想要的最後一件事是“等待”一些微不足道的事情。 最後,啟用優化編譯會產生類似的代碼。 因此,了解這兩種情況下的小技巧是很好的。

問題中的案例很清楚,ReSharper是正確的。 您不是嵌套if語句,而是在代碼中創建新範圍,而是在方法開始時設置明確的規則。 它增加了可讀性,維護起來更容易,並減少了人們篩選要查找的規則數量。


我想補充一點,那些倒過來的名字是 - Guard條款。 我可以隨時使用它。

我討厭閱讀代碼,如果在開始時有兩個代碼屏幕而沒有其他代碼。 只需倒置,然後返回。 這樣,沒有人會浪費時間滾動。

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


我的想法是,“在功能中間”的回歸不應該如此“主觀”。 原因很簡單,拿這個代碼:

    function do_something( data ){

      if (!is_valid_data( data )) 
            return false;


       do_something_that_take_an_hour( data );

       istance = new object_with_very_painful_constructor( data );

          if ( istance is not valid ) {
               error_message( );
                return ;

          }
       connect_to_database ( );
       get_some_other_data( );
       return;
    }

也許第一個“回歸”它不是那麼直觀,但這真的是節省。 關於清潔法規有太多的“想法”,只是需要更多的實踐來失去他們的“主觀”不良想法。


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

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();
};   

我從重構目錄中選擇了這些代碼。 調用此特定的重構:使用Guard子句替換嵌套條件。


正如其他人所說,不應該有性能問題,但還有其他方面的考慮。 除了這些有效的擔憂之外,在某些情況下,這也可能會讓你陷入陷阱。 假設你正在處理一個double而不是:

public void myfunction(double exampleParam){
    if(exampleParam > 0){
        //Body will *not* be executed if Double.IsNan(exampleParam)
    }
}

看似相同的反演相反:

public void myfunction(double exampleParam){
    if(exampleParam <= 0)
        return;
    //Body *will* be executed if Double.IsNan(exampleParam)
}

所以在某些情況下, if可能不是這樣的if ,看起來是正確的。


這僅僅是有爭議的。 在程序員之間沒有關於提前回報問題的協議。 就我所知,它總是主觀的。

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

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


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

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

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

至於關於從方法返回多次的消極因素 - 調試器現在非常強大,而且很容易找出某個特定函數返回的位置和時間。

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

代碼可讀性差。


這當然是主觀的,但我認為它強烈地改善了兩點:

  • 現在顯而易見,如果condition成立,你的功能就沒有什麼可做的了。
  • 它保持了嵌套級別。 嵌套對可讀性的影響比您想像的要多。

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


這裡有幾個優點,但如果方法非常冗長,多個返回點也是不可讀的 。 這就是說,如果你打算使用多個返回點,只要確保你的方法很短,否則多個返回點的可讀性可能會丟失。


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

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







resharper