2009-10-19 41 views
3

這絕對是一個語言不可知論的問題,而且這個問題已經困擾了我很長一段時間了。一個例子可能會幫助我解釋我面臨的困境:一種方法應該承擔多少責任?

讓我們說我們有一個方法負責讀取文件,用一些對象(它存儲來自文件的信息)填充集合,然後返回集合......類似如下:

public List<SomeObject> loadConfiguration(String filename); 

讓我們也說,在實施該方法時,它似乎不可行的應用程序繼續,如果集合返回的是空(0大小)。現在,問題是,是否應該在該方法內進行驗證(檢查空集合並可能隨後拋出異常)?或者,這種方法的唯一責任是執行文件的加載並忽略驗證的任務,允許在方法之外的某個階段進行驗證?

我想一般的問題是:是否更好地將驗證與實際正在執行的任務分離?一般而言,這會使事情在稍後的階段更容易改變或建立 - 就我上面的例子而言,在稍後的階段可能會出現這樣的情況,即增加不同的策略以從空的事件中恢復從'loadConfiguration'方法返回集合.....如果在方法中完成驗證(以及生成的異常),這將會很困難。

也許我在尋求一些教條式的回答時過於迂腐,反而它只是依賴於使用方法的上下文。無論如何,我會非常感興趣的是看到別人對此有何評論。

謝謝大家!

回答

7

我的建議是堅持單一職責原則,說,簡而言之,每個對象應該有1個目的。在這種情況下,如果您計算驗證方面,您的方法有3個目的,然後有4個目的。

以下是我對如何處理此問題以及如何爲將來更新提供大量靈活性的建議。

  1. 讓您LoadConfig方法

  2. 有它稱之爲一種新的方法來讀取文件。

  3. 將上一個方法的返回值傳遞給另一個將文件加載到集合中的方法。

  4. 將對象集合傳遞給某種驗證方法。

  5. 返回集合。

這是最初採取1方法,並打破它與一個調用3其他人。這應該允許你改變對別人沒有任何影響的作品。

希望這有助於

+0

我明白這樣的建議的好意,但它有煽動過度工程的風險。 – MaD70 2009-10-19 18:45:56

+0

@我同意的範圍內的MaD70;然而,如果要使用單元測試,保持小而簡單的事情將極大地幫助保持代碼的可維護性。如果它是一個微小的系統,那麼我同意你的觀點。然而,根據我的經驗,最好始終將單一責任抽象出來,否則間接錯誤的風險會增加。 – JamesEggers 2009-10-19 19:36:07

0

方法應該是高度凝聚力...這是一心一意的。所以我的意見是分開你所描述的責任。我有時候很想說...這只是一個簡短的方法,所以沒關係...然後我在1.5周後感到後悔。

0

我認爲這取決於案例:如果你可以想到一個場景,你會使用這種方法,它返回一個空列表,這將是好的,那麼我不會把驗證內部的方法。但是對於例如一種將數據插入數據庫的方法,該數據庫必須經過驗證(電子郵件地址是否正確,是否指定了名稱......),那麼應該可以將驗證代碼放入函數中並引發異常。

4

我想一般的問題是:它 更好的解耦由 方法執行實際任務從 驗證?

是的。(至少如果你真的堅持回答這樣一個普遍的問題 - 找到反例總是很容易的。)如果你將解決方案的兩個部分分開,你可以交換,放棄或重用任何一個。這是一個明顯的優點。當然,你必須注意不要通過暴露非驗證API來危害你的對象的不變式,但我認爲你知道這一點。你將不得不做一些額外的打字,但這不會傷害你。

+0

我認爲我們的觀點不一致。延遲驗證(參數,回報等)是調試惡夢的開放門戶。 – 2009-10-19 18:08:30

+0

歡迎您不同意:)跳過單元測試是打開調試噩夢的大門。如果你不刷掉設計,你會保持危險的電線封裝和外部API測試,那麼你沒有什麼可擔心的。當然,我不會爲我的屍體辯護,明確的情況下,你會盡最大努力保持加載和驗證一塊。 – zoul 2009-10-19 18:13:13

+0

我同意。一個反例:你不想受到例外的困擾,因爲程序需要以某些選項的默認值開始,其餘的很少,所以你初始化它們,當沒有時候不做任何事情一個配置文件(或者是空的)並忘記它。 – MaD70 2009-10-19 18:23:34

2

爲了將問題轉移到更基本的問題,每種方法應該儘可能少的。因此,在你的例子中,應該有一個方法讀取文件,一種方法從文件中提取必要的數據,另一種方法將數據寫入集合,另一種方法調用這些方法。驗證可以採用單獨的方法,也可以採用其他方法,具體取決於最有意義的位置。

private byte[] ReadFile(string fileSpec) 
    { 
     // code to read in file, and return contents 
    } 
    private FileData GetFileData(string fileContents) 
    { 
     // code to create FileData struct from file contents 
    } 
    private void FileDataCollection: Collection<FileData> { } 

    public void DoItAll (string fileSpec, FileDataCollection filDtaCol) 
    { 
     filDtaCol.Add(GetFileData(ReadFile(fileSpec))); 
    } 

每個方法添加驗證,驗證適當

+0

「基本」方法應儘可能少,但一種方法可以聚合3/4其他方法。 – 2009-10-19 18:10:15

2

我將一個問題回答你的問題:你想爲你的方法的產品不同的驗證方法?

這與「構造函數」問題相同:在構建過程中引發異常還是初始化無效對象,然後調用'init'方法會更好......您肯定會在此提出爭論!

一般來說,我會建議儘快進行驗證:這就是所謂的Fail Fast,它主張儘快發現問題比延遲檢測更好,因爲診斷是立竿見影的,而以後您將不得不恢復整個流程......

如果你不確定,這樣想:你真的想每次載入文件時寫3行嗎? (加載,解析,驗證)那麼,這違反了DRY原則。

所以,去敏捷有:

  • 寫你的方法與驗證:它負責裝載有效配置(1)
  • 如果你需要一些參數化,加上它,然後(如一個'檢查'參數,默認值保留舊的行爲當然)

(1)當然,我不主張一個方法一次完成所有這一切......它是一個組織事情:在封面下此方法應該調用專用方法來組織代碼:)

+0

+1,即使我們給出了相反的答案。我認爲我們兩個人的意思是一樣的:你說保持加載的驗證,但將實現拆分爲封面下的單獨方法;我說要讓它們分開,但在暴露外部API中的零件時要小心。 – zoul 2009-10-19 18:21:17

+0

「總的來說,我會推薦......」但是那裏沒有「一般的節目」只有特定的人編寫的特定程序才能解決特定的問題(不過一般人需要它,程序不能解決所有存在的問題)。看到我對zoul的回覆,其中一個例子是,如果一個空的(或不存在的)配置文件不想引發異常。 – MaD70 2009-10-22 04:57:41

+0

我沒有看到你的觀點......當然,如果有些情況下你滿足於某種情況,那麼不要把它當作是一種錯誤。但是,如果不處理任何錯誤是一種懶惰的方法,那麼必須仔細定義方法的契約,並在需要重構時根據需要進行演變。 – 2009-10-22 17:37:29

2

您正在設計一個API,不應對您的客戶端做出任何不必要的假設。一個方法應該只取得它所需要的信息,只返回所請求的信息,並且只在它無法返回一個有意義的值時纔會失敗。

所以,考慮到這一點,如果配置是可加載但空的,那麼返回一個空列表對我來說似乎是正確的。如果您的客戶在提供空列表時對應用程序的特定要求失敗,那麼它可能會這樣做,但未來的客戶可能沒有這個要求。方法本身在失敗時會失敗,比如當它無法讀取或解析文件時。

但是你可以繼續解耦你的界面。例如,爲什麼必須將配置存儲在文件中?爲什麼我不能提供URL,數據庫中的一行或包含配置數據的原始字符串?很少有方法應該將文件路徑作爲參數,因爲它將它們緊緊地綁定到本地文件系統,並使它們除了其核心邏輯之外還負責打開,讀取和關閉文件。考慮接受一個輸入流作爲替代。或者,如果您想允許精心製作替代方案 - 比如來自數據庫的數據 - 請考慮接受ConfigurationReader接口或類似方法。

0

上面沒有提到的另一種方法是支持依賴注入,並讓方法客戶端注入一個驗證器。這將允許保留「強大的」資源獲取是初始化原則,也就是說任何成功裝載的對象都已準備就緒(Matthieu提到的快速失敗也是一個概念)。

它還允許資源實現類創建它自己的低級驗證器,它依賴於資源的結構而不會不必要地暴露客戶端的實現細節,這在處理多個不同的資源提供者(如Ryan列出的)時非常有用。

相關問題