2011-08-31 42 views
21

我想就我碰到的技術提出一些建議。通過查看代碼片段可以很容易理解,但我在下面的段落中進一步記錄了它。關於嵌套Java嘗試/最後代碼三明治的建議


使用「代碼三明治」成語在處理資源管理中是司空見慣的。習慣了C++的RAII習慣用法,我切換到了Java,發現我的異常安全的資源管理導致了深度嵌套的代碼,在這個代碼中,我很難掌握控制流程。

顯然(java data access: is this good style of java data access code, or is it too much try finally?,Java io ugly try-finally block和更多)我並不孤單。

我嘗試了不同的解決方案來應對這樣的:

  1. 明確維護程序狀態:resource1aquiredfileopened ...,和清理條件:if (resource1acquired) resource1.cleanup() ......但我在順明確複製程序狀態變量 - 運行時知道狀態,我不想關心它。

  2. 包裹在每一個功能嵌套塊 - 結果更難遵循控制流程,併爲真正尷尬的函數名稱:runResource1Acquired(r1)runFileOpened(r1, file),...

最後我來到了一個成語


取而代之的是:

也(概念)的一些 research paper on code sandwiches支持0

使用助手構造,您可能會得到一個更線性的構造,其中補償代碼緊挨着始發者。

class Compensation { 
    public void compensate(){}; 
} 
compensations = new Stack<Compensation>(); 

和嵌套代碼變爲線性:

try { 
    connection = DBusConnection.SessionBus(); // may throw, needs cleanup 
    compensations.push(new Compensation(){ public void compensate() { 
     connection.disconnect(); 
    }); 

    connection.export("/MyObject", myObject); // may throw, needs cleanup 
    compensations.push(new Compensation(){ public void compensate() { 
     connection.unExport("/MyObject"); 
    }); 

    // unfolded try{}finally{} code 

} finally { 
    while(!compensations.empty()) 
     compensations.pop().compensate(); 
} 

我很高興:無論多少異常路徑,控制流程保持線性的,並且清理代碼是目視下向發端的代碼。最重要的是,它不需要人爲限制的closeQuietly方法,這使得它更加靈活(即不僅僅是Closeable對象,還有Disconnectable,Rollbackable以及其他任何其他)。

但是...

我在其他地方沒有發現這種技術。所以這裏的問題是:


該技術是否有效?你看到了什麼錯誤?

非常感謝。

+1

你似乎沒有被處理的情況下一個可以從一個compensation()方法中拋出異常。這將阻止後續的補償運行。 –

+0

@Kevin:確實 - 這裏有太多的C++習慣用法:析構函數不能拋出,我堅持這個習慣用法。 「補償」實現可以處理異常。 – xtofl

+1

@xtofl - 與你使用'try-finally'完全不一樣 - 你可以在代碼本身採用這個習慣用法,而不用爲'finally'塊打擾。此外,許多'Compensators'自然會拋出異常(例如,用於關閉數據庫資源的'SQLException');每個實現**都有**來單獨捕獲和吞下所有異常,而不是你的'finally'塊在一個地方一致地處理它。這對他們來說不是可選的(運行時異常會違反他們的規範,並且可能隨時發生),所以您只需鼓勵複製粘貼。 –

回答

2

很好。

無重大投訴,小事把我的頭頂部:

  • 一點點的性能負擔
  • 你需要做一些事情final的補償看到他們。也許這可以防止一些使用案例
  • 您應該在補償期間捕獲異常並繼續運行補償無論如何
  • (位牽強),由於編程錯誤,您可能會在運行時意外清空補償隊列。無論如何,OTOH編程錯誤都可能發生。並與您的薪酬隊列,你會得到「有條件的終極塊」。
  • 不要推這太多。在一個單一的方法中,它似乎沒問題(但是你可能不需要太多的try/finally塊),但是不要在補償隊列上下調用堆棧。
  • 它延遲對「最外層最終」的補償,這可能是需要儘早清理的問題
  • 只有當您需要僅用於finally塊的try塊時纔有意義。如果你有一個catch塊,你可以直接在那裏添加最後一個。
+0

謝謝。你是什​​麼意思「有條件的最終塊」? – xtofl

+0

那麼,你可以說'如果(某事)componensation.push(extraCompensation)'。 – Thilo

0

你知不知道你真的需要這個複雜性。當您嘗試取消導出未導出的內容時會發生什麼?

// one try finally to rule them all. 
try { 
    connection = DBusConnection.SessionBus(); // may throw, needs cleanup 
    connection.export("/MyObject", myObject); // may throw, needs cleanup 
    // more things which could throw an exception 

} finally { 
    // unwind more things which could have thrown an exception 

    try { connection.unExport("/MyObject"); } catch(Exception ignored) { } 
    if (connection != null) connection.disconnect(); 
} 

使用的輔助方法,你可以做

unExport(connection, "/MyObject"); 
disconnect(connection); 

我還以爲斷開意味着你不需要不導出連接正在使用的資源。

+1

然後他可能會運行unExport,即使導出沒有運行。不在上面的簡單例子中,但你明白了。 (可以由許多布爾值處理)。 – Thilo

+0

事實上:這個解決方案只有在增加的複雜性真正增加了代碼可讀性的情況下才有用。也許這個例子不夠特別:)。 – xtofl

+0

@Thilo,「然後他可能會運行unExport,即使導出沒有運行。」你知道這甚至是一個問題嗎? –

3

我喜歡這個方法,但是看到了一些限制。

第一個是在原始的情況下,在最初的finally塊中拋出不會影響後面的塊。在您的演示中,拋出未執行的動作將會停止斷開連接補償的發生。

其次,它的語言由於Java的匿名類的醜陋而變得複雜,包括需要引入一堆'final'變量,以便它們可以被補償者看到。這不是你的錯,但我想知道治療是否比疾病更糟糕。

但總的來說,我喜歡這個方法,它很可愛。

1

您應該研究一下首次在Java 7中引入的試用資源。這應該會減少必要的嵌套。

+1

我做到了。他們將資源限制爲「可關閉」的實現者,這是非常嚴格的imho。 (而且我還沒有使用Java 7,但這是一個小問題:) – xtofl

+0

你可能會在DBus的bug跟蹤器中提交一個bug。 Imho沒有理由說這個類沒有實現Closeable。 – soc

3

我看到它的方式,你想要的是交易。您的補償是交易,實施有點不同。我假設你沒有使用JPA資源或支持事務和回滾的任何其他資源,因爲只需使用JTA(Java事務API)就相當容易。另外,我認爲你的資源不是由你開發的,因爲你可以讓他們從JTA實現正確的接口並使用它們進行交易。

所以,我喜歡你的方法,但是我會做的是隱藏從客戶端彈出和補償的複雜性。此外,您可以透明地傳遞事務。

因此(注意,提前難看代碼):

public class Transaction { 
    private Stack<Compensation> compensations = new Stack<Compensation>(); 

    public Transaction addCompensation(Compensation compensation) { 
     this.compensations.add(compensation); 
    } 

    public void rollback() { 
     while(!compensations.empty()) 
     compensations.pop().compensate(); 
    } 
} 
+0

...醜陋的代碼?我可以做更醜陋的:)這至少是一種很好的方式,可以將這種技術包裝到可重用的類中。 – xtofl

+1

我在這裏錯過的唯一的事情就是try-catch爲'compensations.pop()。compensate()處理wrap',以處理在前面幾個答案中指出的異常 – gnat

+1

好吧,我也想念getter和setter,a適當的構造函數,空參數檢查等 - 對我來說非常難看。 :)但是,是的,gnat,回滾代碼應該更加詳細,比如檢查無效或無功能補償等。如果賠償失敗,你想要做什麼完全是另一回事:你是否繼續?補償是否相互依賴?拋出一個異常(我的最愛,因爲你的應用程序狀態在這種情況下是未定義的)?你的要求可能會告訴你該怎麼做,所以把自己擊倒! :) – LeChe

3

像的Java設備A的析構函數,這將在詞法作用域的端部被調用,是一個有趣的話題;最好在語言層面解決,但語言czars並不覺得它非常有吸引力。

已經討論過施工行爲之後立即指定破壞行爲(太陽下沒有新東西)。一個例子是http://projectlombok.org/features/Cleanup.html

另一個例子,從私下討論:

{ 
    FileReader reader = new FileReader(source); 
    finally: reader.close(); // any statement 

    reader.read(); 
} 

這是通過轉化

{ 
    A 
    finally: 
     F 
    B 
} 

{ 
    A 
    try 
    { 
     B 
    } 
    finally 
    { 
     F 
    } 
} 

如果你的Java 8將關閉,我們可以在關閉中簡潔地實現此功能:

auto_scope 
#{ 
    A; 
    on_exit #{ F; } 
    B; 
} 

然而,關閉,大多數資源庫將提供自己的自動淨化設備,客戶並不需要自己來處理它

File.open(fileName) #{ 

    read... 

}; // auto close 
+0

lombok ...看起來像一些std :: boost for java :)謝謝你的提示! – xtofl