功能Java示例 第3部分–不要使用异常来控制流程
這是稱為“ Functional Java by Example”的系列文章的第3部分。
我在本系列的每個部分中發展的示例是某種“提要處理程序”,用于處理文檔。 在前面的部分中,我從一些原始代碼開始,并應用了一些重構來描述“什么”而不是“如何”。
為了幫助代碼向前發展,我們需要擺脫良好的java.lang.Exception 。 (免責聲明:我們實際上無法擺脫它)這就是其中的內容。
如果您是第一次來這里,最好從頭開始閱讀。 它有助于了解我們從何處開始以及如何在整個系列中繼續前進。
這些都是這些部分:
- 第1部分–從命令式到聲明式
- 第2部分–講故事
- 第3部分–不要使用異常來控制流程
- 第4部分–首選不變性
- 第5部分–將I / O移到外部
- 第6部分–用作參數
- 第7部分–將失敗也視為數據
- 第8部分–更多純函數
我將在每篇文章發表時更新鏈接。 如果您通過內容聯合組織來閱讀本文,請查看我博客上的原始文章。
每次代碼也被推送到這個GitHub項目 。
快速了解異常
自Java 1.0以來,我們的java.lang.Exception一直存在-基本上在好時機和其他時候成為我們的敵人。
關于它們的討論不多,但是如果您想閱讀一些資源,這是我的最愛:
- Java異常 (JavaWorld)
- Java例外– GeeksforGeeks (geeksforgeeks.org)
- 9種處理Java中異常的最佳實踐 (stackify.com)
- 異常處理的最佳實踐 (onjava.com)
- Java異常面試問答 (journaldev.com)
- 帶有示例的Java中的異常處理 (beginnersbook.com)
- Java異常處理(Try-catch) (hackerrank.com)
- 十大Java異常處理最佳實踐– HowToDoInJava (howtodoinjava.com)
- Java中的異常處理和斷言– NTU (ntu.edu.sg)
- 異常處理:最佳實踐指南 (dzone.com)
- 在Java中處理異常的9種最佳實踐 (dzone.com)
- 修復7個常見Java異常處理錯誤 (dzone.com)
- Java慣例->已檢查異常與未檢查異常 (javapractices.com)
- Java中異常的常見錯誤 MikaelSt?ldal的技術博客 (staldal.nu)
- Java開發人員在使用異常時犯的11個錯誤 (medium.com/@rafacdelnero)
- 檢查異常是好是壞? (JavaWorld)
- 檢查異常:Java的最大錯誤 精簡Java (literatejava.com)
- 未經檢查的異常-爭議 (docs.oracle.com)
- 已檢查異常的麻煩 (artima.com)
- Java例外:您(可能)做錯了 (dzone.com)
- Java理論與實踐:異常辯論– IBM (ibm.com)
- Java的檢查異常是一個錯誤(這是我想做的 (radio-weblogs.com)
- Buggy Java代碼:Java開發人員犯的十大最常見錯誤 Toptal (toptal.com)
您已經在Java 8上了嗎? 生活變得好多了! 我… 呃…哦,等等。
- Java輸入流的錯誤處理– Javamex (javamex.com)
- 在Java流中處理檢查的異常 (oreilly.com)
- JDK 8流中的異常處理 (azul.com)
- 帶有異常的Java 8功能接口 (slieb.org)
- 重新打包流中的異常– blog @ CodeFX (blog.codefx.org)
- 如何在Java 8 Stream中處理異常? –堆棧溢出 (stackoverflow.com)
- 檢查異常和流| Benji的博客 (benjiweber.co.uk)
- 一個關于檢查異常和Java 8 Lambda表達式的 故事 (javadevguy.wordpress.com)– 很好的戰爭故事!
- hgwood / java8-streams-and-exceptions (github.com)
- …
好的,看來您不可能正確地做到這一點。
至少,閱讀上述名單后,我們現在完全取決于對速度的話題��
幸運的是,我不必再寫一篇博客文章,介紹上面的文章已經涵蓋了95%的內容,但是在這里,我將重點討論代碼中實際存在的一個Exception ��
副作用
既然您正在閱讀這篇文章,您可能會對為什么這一切都與函數式編程有關感興趣。
在以更“實用的方式”處理代碼的過程中,您可能會遇到“副作用”一詞,這是一件“壞事”。
在現實世界中, 副作用是您不希望發生的事情 ,您可能會說它等同于“例外”情況(您會例外說明),但是在函數式編程上下文中它具有更嚴格的含義。
維基百科有關副作用的文章說:
副作用(計算機科學)在計算機科學中,如果函數或表達式在返回范圍之外修改了某些狀態或與其調用函數或外界有可觀察的交互作用,則稱該函數或表達式具有副作用。 …在函數式編程中,很少使用副作用。
因此,在本系列的前兩篇文章之后,讓我們看看我們的FeedHandler代碼當前的樣子:
class FeedHandler {Webservice webserviceDocumentDb documentDbvoid handle(List<Doc> changes) {changes.findAll { doc -> isImportant(doc) }.each { doc ->try {def resource = createResource(doc)updateToProcessed(doc, resource)} catch (e) {updateToFailed(doc, e)}}}private Resource createResource(doc) {webservice.create(doc)}private boolean isImportant(doc) {doc.type == 'important'}private void updateToProcessed(doc, resource) {doc.apiId = resource.iddoc.status = 'processed'documentDb.update(doc)}private void updateToFailed(doc, e) {doc.status = 'failed'doc.error = e.messagedocumentDb.update(doc)}}在一個地方,我們嘗試捕獲異常,在那兒,我們循環瀏覽重要的文檔,并嘗試為其創建“資源”(無論是什么)。
try {def resource = createResource(doc)updateToProcessed(doc, resource) } catch (e) {updateToFailed(doc, e) }在上面的代碼中catch (e)是catch (Exception e) Groovy縮寫。
是的,這就是我們正在捕獲的通用java.lang.Exception 。 可以是任何例外,包括NPE。
如果createResource方法沒有引發異常,則將文檔(“ doc”)更新為“已處理”,否則將其更新為“失敗”。 順便說一句,即使updateToProcessed也會引發異常,但是對于當前的討論,我實際上只對成功創建資源感興趣。
因此,上面的代碼可以工作 (我已經通過單元測試來證明它:-)),但是我對try-catch語句不滿意。 我只對成功創建資源感興趣,而且很傻,我只能提出createResource要么返回成功的資源, 要么拋出異常。
拋出異常以表示出了點問題,躲開閃避,讓調用者捕獲該異常以進行處理,這是為什么發明了異常的原因呢? 而且比返回null更好嗎?
它一直在發生。 采取一些我們喜歡的框架,例如JPA規范中的 EntityManager#find :
啊! 返回null 。
返回值:
找到的實體實例;如果該實體不存在,則返回null
錯誤的例子。
函數式編程鼓勵使用無副作用的方法(或:函數),以使代碼更易于理解且更易于推理。 如果某個方法僅接受某些輸入并每次都返回相同的輸出(這使其成為一個純函數),則各種優化都可以在后臺進行,例如通過編譯器或緩存,并行化等。
我們可以再次用純函數(計算出的值)替換純函數,這稱為參考透明度 。
在上一篇文章中,我們已經將一些邏輯提取到了自己的方法中,例如下面的isImportant 。 給定相同的文檔(具有相同的 type屬性)作為輸入,我們每次都會獲得相同的 (布爾值)輸出。
boolean isImportant(doc) {doc.type == 'important' }這里沒有可觀察到的副作用,沒有全局變量被突變,沒有日志文件被更新–它只是塞進,塞出 。
因此,我要說的是,通過我們的傳統異常與外界交互的函數很少在函數式編程中使用。
我想做得更好 。 更好 。
可選救援
正如石磊韋伯表示它:
關于如何在Java中有效使用異常有不同的觀點。 有些人喜歡檢查異常,有些人則認為這是一次失敗的實驗,他們更喜歡獨占使用未檢查異常。 其他人則完全避開異常,而贊成傳遞和返回諸如Optional或Maybe之類的類型。
好的,讓我們嘗試一下Java 8的Optional以便發出是否可以創建資源的信號。
讓我們更改我們的webservice接口和createResource方法,以在Optional包裝并返回我們的資源:
//private Resource createResource(doc) { private Optional<Resource> createResource(doc) {webservice.create(doc) }讓我們更改原始的try-catch :
try {def resource = createResource(doc)updateToProcessed(doc, resource) } catch (e) {updateToFailed(doc, e) }map (處理資源)和orElseGet (處理空的可選):
createResource(doc).map { resource ->updateToProcessed(doc, resource)}.orElseGet { /* e -> */updateToFailed(doc, e)}很棒的createResource方法:返回正確結果,或者為空結果。
等一下! 唯一的例外e我們需要傳遞到updateToFailed是走了 :我們有一個空的Optional替代。 我們不能存儲的原因失敗的原因 -這是我們做的必要性。
可能是Optional只是表示“缺席”,并且是我們此處目的不正確的工具。
出色的完成
如果沒有try-catch和map-orElseGet ,我確實喜歡代碼開始更多地反映操作“流程”的方式。 不幸的是,使用Optional更適合“得到一些東西”或“什么也沒有得到”(這也建議使用map和orElseGet類的名稱),并且沒有給我們提供記錄失敗原因的機會。
還有什么方法能夠獲得成功的結果或失敗的原因,而仍然接近我們的閱讀方式呢?
Future 。 更好的是: CompletableFuture 。
CompletableFuture (CF)知道如何返回值,這類似于Optional 。 通常,CF用于將來獲取值集 ,但這不是我們想要將其用于…的原因。
從Javadoc :
……支持……在完成時觸發的行動的未來。
跳動,它可以表示“異常”完成 -給我機會對此采取行動。
讓我們更改map和orElseGet :
createResource(doc).map { resource ->updateToProcessed(doc, resource)}.orElseGet { /* e -> */updateToFailed(doc, e)}thenAccept (處理成功)并exceptionally (處理失敗):
createResource(doc).thenAccept { resource ->updateToProcessed(doc, resource)}.exceptionally { e ->updateToFailed(doc, e)}CompletableFuture#exceptionally方法接受一個帶有我們實際失敗原因的異常e的函數。
您可能會想: tomayto,tomahto。 首先,我們進行了try-catch ,現在我們進行了thenAccept-exceptionally ,那么有什么大不同?
好吧,我們顯然不能擺脫特殊情況,但我們現在正在像Functionalville的居民那樣思考:我們的方法開始成為函數 ,告訴我們有什么東西有事。
考慮到這是我們在第4部分中需要進行的少量重構,而在第5部分中,甚至更多地限制了代碼中的副作用。
現在就這樣
作為參考,這是重構代碼的完整版本。
class FeedHandler {Webservice webserviceDocumentDb documentDbvoid handle(List<Doc> changes) {changes.findAll { doc -> isImportant(doc) }.each { doc ->createResource(doc).thenAccept { resource ->updateToProcessed(doc, resource)}.exceptionally { e ->updateToFailed(doc, e)}}}private CompletableFuture<Resource> createResource(doc) {webservice.create(doc)}private boolean isImportant(doc) {doc.type == 'important'}private void updateToProcessed(doc, resource) {doc.apiId = resource.iddoc.status = 'processed'documentDb.update(doc)}private void updateToFailed(doc, e) {doc.status = 'failed'doc.error = e.messagedocumentDb.update(doc)}}-
翻譯自: https://www.javacodegeeks.com/2018/01/functional-java-example-part-3-dont-use-exceptions-control-flow.html
總結
以上是生活随笔為你收集整理的功能Java示例 第3部分–不要使用异常来控制流程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 因收到“死亡威胁”,Unity 位于得州
- 下一篇: 仰望U8应急浮水功能解密:自动触发、30