功能Java示例 第2部分–讲故事
這是稱為“ Functional Java by Example”的系列文章的第2部分。
我在本系列的每個部分中發(fā)展的示例是某種“提要處理程序”,用于處理文檔。 在上一部分中,我從一些原始代碼開始,并應用了一些重構來描述“什么”而不是“如何”。
為了幫助代碼向前發(fā)展,我們需要先講一個故事 。 那就是這部分的地方。
如果您是第一次來這里,最好從頭開始閱讀。 它有助于了解我們從何處開始以及如何在整個系列中繼續(xù)前進。
這些都是這些部分:
- 第1部分–從命令式到聲明式
- 第2部分–講故事
- 第3部分–不要使用異常來控制流程
- 第4部分–首選不變性
- 第5部分–將I / O移到外部
- 第6部分–用作參數(shù)
- 第7部分–將失敗也視為數(shù)據(jù)
- 第8部分–更多純函數(shù)
我將在每篇文章發(fā)表時更新鏈接。 如果您通過內(nèi)容聯(lián)合組織來閱讀本文,請查看我博客上的原始文章。
每次代碼也被推送到這個GitHub項目 。
作為參考,我們現(xiàn)在以以下代碼為起點:
class FeedHandler {Webservice webserviceDocumentDb documentDbvoid handle(List<Doc> changes) {changes.findAll { doc -> doc.type == 'important' }.each { doc ->try {def resource = webservice.create(doc)doc.apiId = resource.iddoc.status = 'processed'} catch (e) {doc.status = 'failed'doc.error = e.message}documentDb.update(doc)}} }大聲讀出
當我最初開始使用Spock作為測試框架時,由于它是多年前Grails默認提供的,因此它的眾多功能和易用性給我留下了深刻的印象(至今仍是)。
你知道什么是假人,存根和間諜吧? Mockito擁有它們,Powermock擁有它們以及基本上所有其他嚴肅的(單元)測試框架。 Mock的概念并不難掌握(您可以在此處閱讀全部內(nèi)容),但是Spock具有一種特殊的方式來描述其(與預期的)模擬交互。
關于“基于交互的測試”的一章非常出色,它解釋了如何使用代碼示例來記錄這些交互。
“模擬”子章開頭為:
模擬是描述(強制)規(guī)范下的對象與其協(xié)作者之間的交互的行為。 這是一個例子:
def "should send messages to all subscribers"() {when:publisher.send("hello")then:1 * subscriber.receive("hello")1 * subscriber2.receive("hello") }如果您不熟悉Spock,Groovy或僅具有上述寫作風格,請不要擔心!
以上Spock文檔的作者也認識到,并不是每個人都會立即理解這里發(fā)生的事情。
他們會提供一些建議,并繼續(xù)提供文檔:
大聲朗讀 :“當發(fā)布者發(fā)送“ hello”消息時,兩個訂戶都應該只收到一次該消息。”
我的重點是“大聲朗讀”,因為我認為這很重要。 這里不討論Spock的更多詳細信息,而是我自己在日常編碼中要牢記的建議本身。
- 當我編寫一段代碼時,我可以大聲讀出嗎?
- 當其他人讀取我的代碼時,他/她可以大聲讀出嗎?
這里的“大聲” 與音量無關,而是可以用一種簡潔易懂的方式描述“這里正在發(fā)生什么”。 這使得對代碼的推理變得容易。
高級與低級
請考慮以下情形:
在不知名的城市中開車游覽了幾個小時以找到劇院后,汽車導航系統(tǒng)出現(xiàn)故障后,您最終決定停下來詢問方向。
您在行人附近停下。
您:
先生,您碰巧知道如何從這里到達劇院
行人:
當然,這很容易。 開始了:
檢查窗戶以確保您具有良好的可見性。 檢查后視鏡以確保它們正確對齊,從而為您提供正確的道路視野。
調(diào)整座椅,使雙腳舒適地到達兩個踏板。
關閉窗口。
重置轉向燈。
開始駕駛前,請松開駐車制動器。
啊,我看你有自動檔。 請將變速桿置于“驅動器”中。
慢慢踩剎車,并監(jiān)控儀表盤儀表。
繼續(xù)前進,提高速度,監(jiān)控車速表,將RPM保持在2000附近
大約120碼后,在開始轉向左車道之前,請先與您的方向燈指示至少兩秒鐘。
緩慢將汽車移至另一車道。 稍微將您的手放在方向盤上,以改變車道。 車輪只需要很小的移動即可; 因為大多數(shù)現(xiàn)代汽車都裝有動力轉向系統(tǒng)。 更改車道大約需要一到三秒鐘。 減少一點,您做得太快了; 再也沒有,你做得太慢了。
再走X步…
祝好運!
或者,考慮對話將像這樣的替代宇宙:
您:
先生,您是否會知道如何從這里到達劇院?
行人:
當然,這很容易。 開始了:
左轉,過橋。 在你的右邊。
祝好運!
最后一種情況是輕而易舉:明確說明要做什么和去哪里!
但是,第一種情況是細節(jié)纏身 -有關駕駛汽車本身的低級細節(jié) -即使我們不希望在現(xiàn)實生活中得到這樣的指導,我們?nèi)匀粫帉戇@樣的軟件。
告訴我一些正確的內(nèi)容。 如果我需要具體信息,我會要求它。
(順便說一句wikihow.com:如何駕駛汽車,請捐贈上述說明中的一些。如果您確實需要學習駕駛,它有很多資源!)
在正確的級別上講內(nèi)容,不僅意味著使用正確命名的類和方法,而且還意味著在它們中使用正確的種類 。
讓我們再次看一下我們的代碼:
class FeedHandler {Webservice webserviceDocumentDb documentDbvoid handle(List<Doc> changes) {changes.findAll { doc -> doc.type == 'important' }.each { doc ->try {def resource = webservice.create(doc)doc.apiId = resource.iddoc.status = 'processed'} catch (e) {doc.status = 'failed'doc.error = e.message}documentDb.update(doc)}} }故事
我們?nèi)绾卧诖a中結合“大聲讀出”和“高級還是低級”?
我們的單handle方法當前顯示為什么樣?
查找type -property等于字符串"important"所有文檔。
呼叫create的webservice與文檔,返回的資源。
如果我們有資源,請將資源的id分配給文檔的apiId屬性。
將文檔的status屬性設置為字符串"processed" 。
如果發(fā)生異常,請將文檔的status屬性設置為字符串"failed" 。 將文檔的status屬性設置為來自異常的message 。
最后,使用documentDb在documentDb上調(diào)用update 。
基本上,這只是重復代碼語句!
什么故事,我想告訴取而代之的,是以下情況:
通過Web服務“創(chuàng)建資源”來處理“重要”文檔。
每次成功時,將兩者關聯(lián)在一起并“將文檔標記為已處理”,否則將其標記為“失敗”。
讀得很好,你不覺得嗎?
實際上,我們可以通過在IDE中使用幾種“提取方法”重構并為提取的方法選擇一些好的名稱來實現(xiàn)這一目標。
在上面的故事中,雙引號短語是我想在高層看到的重要部分。
“重要”
我為什么要關心文檔使用什么屬性來確定其重要性? 現(xiàn)在是字符串"important" ,表示“嘿,我很重要!” 但是如果條件變得更加復雜怎么辦?
將doc.type == 'important'提取到其自身的方法isImportant 。
changes.findAll { doc -> isImportant(doc) }// ...private boolean isImportant(doc) {doc.type == 'important'}“創(chuàng)造資源”
我為什么在這里關心如何在Web服務中調(diào)用什么方法? 我只想創(chuàng)建一個資源。
將與Web服務的所有事務都提取到它自己的方法(稱為createResource 。
def resource = createResource(doc)// ...private Resource createResource(doc) {webservice.create(doc)}“更新為已處理”
提取將資源/文檔/將狀態(tài)設置為其自己的方法(稱為updateToProcessed 。
updateToProcessed(doc, resource)// ...private void updateToProcessed(doc, resource) {doc.apiId = resource.iddoc.status = 'processed'}“更新失敗”
不在乎細節(jié)。 提取到updateToFailed 。
updateToFailed(doc, e)// ...private void updateToFailed(doc, e) {doc.status = 'failed'doc.error = e.message}看來我們最后只剩下了documentDb.update(doc) 。
這是在數(shù)據(jù)庫中存儲已處理/失敗文檔的一部分,我已經(jīng)在最高級別上進行了描述。
我將其放在每個剛剛創(chuàng)建的updateTo*方法中-較低的級別。
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)}那么,提取出細節(jié)之后,有什么變化?
void handle(List<Doc> changes) {changes.findAll { doc -> isImportant(doc) }.each { doc ->try {def resource = createResource(doc)updateToProcessed(doc, resource)} catch (e) {updateToFailed(doc, e)}}}任何人(例如,同事,您未來的自我)都會“一口氣”地讀一讀,將會了解30,000英尺的行程。
如果您需要任何這些步驟的詳細信息,只需深入了解該方法即可。
能夠寫聲明式的東西(本系列的前一部分)并在正確的水平上講故事(本部分)還將有助于在第3部分及以后的部分中更輕松地進行將來的更改。
現(xiàn)在就這樣
作為參考,這是重構代碼的完整版本。
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)}}翻譯自: https://www.javacodegeeks.com/2017/11/functional-java-example-part-2-tell-story.html
總結
以上是生活随笔為你收集整理的功能Java示例 第2部分–讲故事的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小米米家便携电热杯 2 开售:350mL
- 下一篇: 索尼宣布米哈游旗下游戏《崩坏:星穹铁道》