2017-10-12 43 views
3

我有一個問題困擾了我一段時間,關於優選的流量控制方法。Java通過拋出異常來控制流量

我經常會遇到的情況,在這裏我不得不決定根據返回值是什麼做的是一個方法nullnot null

所以,我有兩個選擇,我知道如何對付它:

檢查空:

public class BasedOnNullFlowControl { 

    public String process(String login) { 
     String redirectUri = getRedirectUri(login); 
     if (redirectUri != null) { 
      return redirectUri; 
     } else { 
      return "Your login is taken"; 
     } 
    } 

    private String getRedirectUri(String login) { 
     Optional<Login> loginFromDb = checkLoginExists(login); 
     if (loginFromDb.isPresent()) { 
      return null; 
     } else { 
      return "some-redirect-url"; 
     } 
    } 

    private Optional<Login> checkLoginExists(String login) { 
     return Optional.empty(); //assume this value comes from some API 
    } 

    private class Login {} 
} 

或中斷與異常流量:

public class ExceptionFlowControl { 

    public String process(String login) { 
     return getRedirectUri(login); 
    } 

    private String getRedirectUri(String login) { 
     Optional<Login> loginFromDb = checkLoginExists(login); 

     loginFromDb.ifPresent(l -> { 
      throw new LoginExistsException(); 
     }); 
     return "some-redirect-url"; 
    } 

    private Optional<Login> checkLoginExists(String login) { 
     return Optional.empty(); 
    } 

    private class LoginExistsException extends RuntimeException { 
    } 

    private class Login {} 
} 

我知道,那例外只能在特殊情況下使用,但我的情況也不例外,仍然第二種方案看起來對我好,因爲我可以添加一些異常處理程序(如在春季),並將其轉化爲一些不錯Http狀態碼到底。控制器方法process沒有被幾十個空檢查污染。

你能否聰明的人請告知應該使用哪一種解決方案?或者也許還有第三個?

+0

請注意,如果您使用'if(Optional.isPresent())',那麼您只需用'Optional'替換'null',但不能以任何明智的方式。 – Kayaman

+0

現在看看你的問題引起的混亂。 – Kayaman

+1

謝謝大家的意見,現在我相信,我的問題沒有單一的答案,可能一切取決於誰在做代碼審查:)我只是想知道是否通過異常來控制應用程序流總是很糟糕練習,即使有時它看起來更簡單,更清潔的方式 – nibsa

回答

1

例外的方式允許代碼各部分之間較小的依賴性,所以第一種方式是相當差。有了它,你需要通過裝載有特殊含義(null == login taken)均來自checkLoginExists()process的方式null值。

但是這不是getRedirectUri()作業拋出異常,它應該在checkLoginExists()完成。我懷疑getRedirectUri()是否負責檢查登錄是否存在。更不用說使用nullOptional只能給你一個成功/失敗的二元概念。如果登錄存在但被鎖定,該怎麼辦?因此,您必須禁止登錄以及創建具有相同名稱的新用戶。您可能需要重定向到其他頁面以表明登錄已被鎖定。

不過,這是基於意見的,高度依賴於具體的情況,並沒有一個明確的規則可以遵循。

編輯(現在,這一切喧譁是在用):這是一個被廣泛接受的意見是正常流量控制不應有例外來完成。然而,正常的定義並不那麼容易定義。你不會寫代碼像

int[] array = ... 
int i = 0; 
try { 
    while(true) { 
     array[i]++; 
     i++; 
    } 
} catch(ArrayIndexOutOfBoundsException e) { 
    // Loop finished 
} 

但是,如果你沒有使用像上面這樣一個荒謬的簡單的例子,它進入灰色地帶。

還要注意,異常與應用程序錯誤不一樣。嘗試通過驗證一切來解決異常是不可能的(或者至少是不明智的)。如果我們考慮一個新用戶正在嘗試註冊用戶名的情況,並且我們希望防止重複的用戶名。您可以檢查重複並顯示錯誤消息,但仍有可能發生兩個併發請求嘗試註冊相同的用戶名。在這一點上,其中一個會得到一個異常,因爲數據庫將不允許重複(如果你的系統是合理的話)。

驗證是重要的,但沒有什麼真的例外關於例外。你需要優雅地處理它們,所以爲什麼麻煩證實,如果你最終不得不處理異常。如果我們拿的東西,我的意見,你需要避免重複和罵人的話作爲用戶名寫了一個例子情況下,你可能會想出這樣的事情(我們假設這是getRedirectUri()

if(checkForCurseWords(username)) 
    return "login not allowed"; 

try { // We can also omit the try-catch and let the exception bubble upwards 
    createLogin(username); 
} catch(DuplicateException e) { 
    return "login exists"; 
} catch(Exception e) { 
    return "problem with login"; 
} 
return "main page"; 
+0

我不同意:異常應該僅用於表示需要修復的應用程序錯誤,您應該在執行邏輯之前通過執行必要的驗證來避免異常。 –

+0

我完全不同意那種不靈活的方法。在這種情況下,驗證已完成,但使用異常來指示驗證失敗。 – Kayaman

+0

驗證失敗是...正常的邏輯流程,顯然不應該通過拋出異常來處理,當你寫這幾千個小小的kities剛剛死亡。 –

-1

這取決於:

  • 如果缺乏值(或空)是你的邏輯正常的和預期的情況,那麼你不應該拋出異常,但只是服務於它相應。考慮使用空對象模式 - 而不是返回null返回一些代表「無價值」對象的對象,那麼你可以省略對null的檢查,因爲你的邏輯應該相應地處理這樣的空對象(通常你不需要甚至可以在你的邏輯中編寫單行代碼來提供這種類型的對象)。

  • 如果情況是,你真的不能用它進行,你應該拋出異常。應用中的異常應用於指示錯誤。如果您可以預先避免異常(例如,使用file.exists()來檢查用戶是否指定了正確的文件名),那麼您應該這樣做(除非它確實超出了正常的邏輯流程並且您無法繼續)。

在你的榜樣,你真的不應該拋出異常,因爲這是正常的(和預期)的情況是用戶輸入不正確的登錄,這應該通過正常的邏輯流程進行處理。

在你的特殊情況,爲什麼你不只是返回重定向URI它指向了一些網頁「用戶不存在」之類的消息的?

+0

因此,根據你的建議,每當我們提交IO文件時,我們需要在我們實際執行IO之前檢查文件是否存在(如果有讀取),是否有正確的權限以及其他任何情況,而不是通過異常來處理它。 – Kayaman

+0

你也誤讀了有問題的代碼。這不是關於登錄,而是關於創建新用戶並防止重複登錄。 – Kayaman

+1

即使您在實際執行文件IO之前檢查了所有內容,它仍然可能會失敗(例如,在檢查和IO之間刪除文件)。 – lexicore

1

第一所有,如果您使用自選,你可以完全避免null檢查:

public class BasedOnNullFlowControl { 

    public String process(String login) { 
     String redirectUri = 
     return getRedirectUri(login).orElse("Your login is taken"); 
    } 

    private Optional<String> getRedirectUri(String login) { 
     return checkLoginExists(login).map(ifExists -> "some-redirect-url"); 
    } 

    private Optional<Login> checkLoginExists(String login) { 
     return Optional.empty(); //assume this value comes from some API 
    } 

    private class Login {} 
} 

接下來,你的第二個版本只用看unchecked異常漂亮所以這歸結爲檢查與unchecked異常這是一種。宗教問題。

幾年前,我與軟件合作過,前一代「開發者」遵循不受限制的異常宗教信仰。就像,「我們大多無法合理地處理異常,所以我們只是拋出未經檢查的異常,並在頂層有一些例程來捕獲它們並向用戶顯示錯誤信息對話框。」那個壯觀失敗,重構它非常困難。代碼就像是一個雷場,你永遠不知道會發生什麼或不會發生什麼。

之後,我完全切換到「檢查異常宗教」。如果你有一種情況會更好地描述爲編程錯誤(例如,通過null,其中沒有預期的空或者必須訪問必須存在的類路徑資源),那麼運行時錯誤是合適的。在其他情況下,模型和使用檢查的異常。

+0

我不同意:異常應該僅用於表示需要修復的應用程序錯誤,您應該在執行邏輯之前通過執行必要的驗證來避免異常。 –

+0

@KrzysztofCichocki如果我寫一個公共方法,通過契約不接受'null's,我通常會檢查與'Objects.requireNonNull'。如果有人用'null'調用方法,他們會得到一個NPE。在我的方法中,我無法驗證「在執行邏輯之前」,這是無稽之談,因爲邏輯已經被執行。 而且,特殊情況並不一定意味着應該修復應用程序中的錯誤。這意味着你無法或不能決定不處理這種情況。 – lexicore

+0

在這種情況下,這應該在方法doc中描述,因此調用方法的方法需要正確使用它(在調用方法之前檢查它的值)。或者如果檢查邏輯更加複雜,你應該給他一種檢查方法,例如在File類中檢查,例如'file.exists()' –

0

你會(不幸)永遠不會得到明確的答案,因爲這取決於很多因素,這些因素很多都是基於意見的。

我看不出你的兩個選項完全錯誤。使用流量控制的異常被認爲是不好practrice在Java中(但不是在其他一些語言的情況下 - 如Python),但你說的有道理,當你說

控制器方法過程不受污染幾十空 檢查。

我給你一個竅門: 當兩片的代碼之間猶豫。問問自己,第一次看到它的代碼會更容易理解。

在第二種解決方案中,清楚會話已存在會發生什麼。在六個月內,另一個develepper可以查看你的代碼,並通過搜索異常處理很容易找到你在做什麼。

鑑於我給你的技巧,我發現第二個選項更好(在我的愚見)。

希望這會有所幫助。通過使用異常

+0

我已經給他一個明確的答案。我不同意你,因爲拋出異常是最糟糕的解決方案。 –

0

控制流量總是壞主意,它被廣泛地解釋這裏: https://softwareengineering.stackexchange.com/questions/189222/are-exceptions-as-control-flow-considered-a-serious-antipattern-if-so-why

這裏:

http://wiki.c2.com/?DontUseExceptionsForFlowControl

beewten使用異常與正常邏輯線應該是清楚,所以異常只能用於真正的錯誤,而不能用於普通應用邏輯可以提供的東西。

@lexicore和@Kayaman,你現在可以讓我冷靜下來,我不在乎。 然後...檢查第二個鏈接的真正的程序員名單誰說你不應該使用正常流量控制異常。 如果你的自我因此被激怒,我真的很高興。