多个退货单
我曾經聽說過,過去人們為使方法具有單個出口點而奮斗。 我知道這是一種過時的方法,從未認為它特別值得注意。 但是最近,我與一些仍堅持該想法的開發人員進行了聯系(最后一次是在這里 ),這讓我開始思考。
因此,我第一次真正坐下來比較了這兩種方法。
總覽
文章的第一部分將針對多個return語句重復參數。 它還將確定干凈代碼在評估這些論點中的關鍵作用。 第二部分將對得益于早日返回的情況進行分類。
為了不總是寫“帶有多個return語句的方法”,我將這種方法稱為通過模式構造方法的方法。 盡管這可能有些過頭,但肯定會更簡潔。
討論
我正在討論一個方法是否應該始終運行到最后一行,從那里返回結果,還是可以有多個return語句并“盡早返回”。
這當然不是新的討論。 參見,例如Wikipedia , Hacker Chick或StackOverflow 。
結構化程序設計
單個return語句是可取的想法源于1960年代開發的結構化編程范式。 關于子例程,它提倡它們具有單個入口和單個出口點。 盡管現代編程語言可以保證前者,但出于某些原因,后者有些過時了。
單個出口點解決的主要問題是內存或資源泄漏。 當方法內部某處的return語句阻止執行位于其末尾的某些清除代碼時,就會發生這種情況。 如今,其中大部分由語言運行時處理(例如,垃圾回收),并且可以使用try-catch-finally編寫顯式清除塊。 因此,現在的討論主要圍繞可讀性。
可讀性
堅持單個return語句可能導致嵌套增加,并需要其他變量(例如,中斷循環)。 另一方面,使方法從多個點返回可能會導致其控制流程混亂,從而使其難以維護。 重要的是要注意,這兩個方面在代碼的整體質量方面有很大的不同。
考慮一種遵循簡潔的編碼準則的方法:它簡短且具有明確的名稱和意圖揭示結構。 通過引入更多的嵌套和更多的變量,在可讀性方面的相對損失非常明顯,并且可能使干凈的結構混亂。 但是由于該方法的簡潔性和形式使其易于理解,因此忽略任何返回聲明的風險不大。 因此,即使存在不止一個,控制流程仍然顯而易見。
與較長的方法(可能是復雜或優化算法的一部分)進行對比。 現在情況逆轉了。 該方法已經包含許多變量,并且可能包含一些嵌套級別。 引入更多內容在可讀性方面幾乎沒有相對成本。 但是,忽視多個回報之一從而誤解控制流程的風險是非常現實的。
因此,問題在于方法是否簡短易讀。 如果是這樣,通常使用多個return語句是一種改進。 如果不是,則最好使用單個return語句。
其他因素
但是,可讀性可能不是唯一的因素。
討論的另一方面可以是日志記錄。 如果要記錄返回值但不求助于面向方面的編程,則必須在方法的出口點手動插入記錄語句。 使用多個return語句執行此操作很繁瑣,而忘記一個則很容易。
同樣,如果要在從方法返回之前聲明結果的某些屬性,則可能希望使用單個退出點。
多個退貨報表的情況
在幾種情況下,一種方法可以從多個返回語句中獲利。 我試圖在這里對它們進行分類,但沒有聲稱有完整的列表。 (如果您遇到另一種重復出現的情況,請發表評論,我將在此附上。)
每種情況都會附帶一個代碼示例。 請注意,縮短了這些內容可以使觀點更清楚,并且可以通過多種方式進行改進。
由JDHancock在CC-BY 2.0下發布
警衛條款
保護子句位于方法的開頭。 他們檢查其參數,并在某些特殊情況下立即返回結果。
防范條款無效或空集合
private Set<T> intersection(Collection<T> first, Collection<T> second) {// intersection with an empty collection is emptyif (isNullOrEmpty(first) || isNullOrEmpty(second))return new HashSet<>();return first.stream().filter(second::contains).collect(Collectors.toSet()); }從一開始就排除邊緣情況有幾個優點:
- 它將特殊情況和常規情況的處理完全分開,從而提高了可讀性
- 它提供了用于其他檢查的默認位置,從而保持了可讀性
- 這使得實施常規案例的錯誤更少
- 它可能會提高那些特殊情況下的性能(盡管這很少相關)
基本上,適用于該模式的所有方法都將從其使用中受益。
值得一提的是后衛條款的支持者是馬丁·福勒(Martin Fowler),盡管我會在分支的邊緣考慮他的例子 (見下文)。
分枝
某些方法的職責要求分支到幾個通常專用的子例程之一。 通常最好將這些子例程本身實現為方法。 然后,原始方法僅負責評估某些條件并調用正確的例程。
委托專門方法
public Offer makeOffer(Customer customer) {boolean isSucker = isSucker(customer);boolean canAffordLawSuit = customer.canAfford(legalDepartment.estimateLawSuitCost());if (isSucker) {if (canAffordLawSuit)return getBigBucksButStayLegal(customer);elsereturn takeToTheCleaners(customer);} else {if (canAffordLawSuit)return getRid(customer);elsereturn getSomeMoney(customer);} }(我知道我可以省略所有else行。有一天,我可能會寫一篇帖子解釋為什么在這種情況下我不這樣做。)
與結果變量和單個返回相比,使用多個return語句具有多個優點:
- 該方法更清楚地表達了其打算跳轉到子例程并僅返回其結果的意圖
- 在任何理智的語言中,如果分支未涵蓋所有可能性,則該方法不會編譯(在Java中,如果未將變量初始化為默認值,也可以通過一次返回來實現)
- 結果沒有額外的變量,幾乎可以覆蓋整個方法
- 被調用方法的結果在返回之前是無法操縱的(在Java中,如果變量是final并且其類是不可變的,也可以通過單次返回來實現;但是,這對于讀者而言并不明顯)
- 如果將switch語句用于具有穿透性的語言(例如Java),則立即返回語句可在每種情況下節省一行,因為不需要break ,這減少了樣板并提高了可讀性
此模式僅應應用于除分支以外無所作為的方法。 分支機構涵蓋所有可能性尤其重要。 這意味著分支語句下沒有代碼。 如果有的話,將需要花費更多的精力來推理通過該方法的所有路徑。 如果一種方法滿足這些條件,那么它將很小且具有凝聚力,這很容易理解。
級聯檢查
有時,一種方法的行為主要由多個檢查組成,其中每個檢查的結果可能使進一步檢查變得不必要。 在這種情況下,最好盡快返回(也許在每次檢查之后)。
尋找錨定父級時進行級聯檢查
private Element getAnchorAncestor(Node node) {// if there is no node, there can be no anchor,// so return nullif (node == null)return null;// only elements can be anchors,// so if the node is no element, recurse to its parentboolean nodeIsNoElement = !(node instanceof Element);if (nodeIsNoElement)return getAnchorAncestor(node.getParentNode());// since the node is an element, it might be an anchorElement element = (Element) node;boolean isAnchor = element.getTagName().equalsIgnoreCase("a");if (isAnchor)return element;// if the element is no anchor, recurse to its parentreturn getAnchorAncestor(element.getParentNode()); }其他示例是Java中equals或compareTo的常規實現。 它們通常還包含一系列檢查,其中每個檢查都可以確定方法的結果。 如果是,則立即返回該值,否則該方法將繼續進行下一個檢查。
與單個return語句相比,此模式不需要您跳過箍以防止更深的縮進。 它還使直接添加新的檢查和在檢查并返回塊之前放置注釋成為可能。
與分支一樣,多個return語句應僅應用于短而幾乎沒有其他作用的方法。 級聯檢查應該是它們的主要內容,或者更好的是它們的唯一內容(除了輸入驗證之外)。 如果檢查或返回值的計算需要兩到三行以上,則應將其重構為單獨的方法。
正在搜尋
在存在數據結構的地方,可以找到具有特殊條件的項目。 搜索它們的方法通常看起來很相似。 如果這種方法遇到了要搜索的項目,則通常最容易立即返回它。
立即返回找到的元素
private <T> T findFirstIncreaseElement(Iterable<T> items, Comparator<? super T> comparator) {T lastItem = null;for (T currentItem : items) {boolean increase = increase(lastItem, currentItem, comparator);lastItem = currentItem;if (increase) {return currentItem;}}return null; }與單個return語句相比,這使我們免于尋找擺脫循環的方法。 這具有以下優點:
- 沒有其他布爾變量可以打破循環
- 循環沒有其他條件,它很容易被忽略(尤其是在for循環中),因此會引發錯誤
- 最后兩點使循環更容易理解
- 結果很可能沒有其他變量,幾乎涵蓋了整個方法
像大多數使用多個return語句的模式一樣,這也需要干凈的代碼。 該方法應該很小,除了搜索外別無其他責任。 非平凡的檢查和結果計算應具有自己的方法。
反射
我們已經看到了支持和反對多個return語句的參數,以及干凈代碼所起的關鍵作用。 分類應有助于識別重復出現的情況,在這種情況下,一種方法將從早期返回中受益。
翻譯自: https://www.javacodegeeks.com/2015/01/multiple-return-statements.html
總結
- 上一篇: Linux的软件(linux下的软件)
- 下一篇: 建立代理,而不是框架