編寫(xiě)程序,錯(cuò)誤處理是不可避免的。
但程序員總是偏向正常的情況,而容易忽略有錯(cuò)誤的情況。
返回值錯(cuò)誤處理
最開(kāi)始,錯(cuò)誤是通過(guò)返回值來(lái)表示,比如非零表示錯(cuò)誤,0表示成功。而處理錯(cuò)誤的代碼類似
這樣的代碼,錯(cuò)誤處理代碼和業(yè)務(wù)邏輯交織在一起,也容易忽略處理錯(cuò)誤。以及把返回值只用于錯(cuò)誤返回,有點(diǎn)浪費(fèi)的感覺(jué)。因?yàn)楹芏鄷r(shí)候把計(jì)算結(jié)果作為返回值,更符合思考的邏輯。
異常錯(cuò)誤處理
后面出現(xiàn)了異常的方式,在出錯(cuò)的時(shí)候,拋出異常。異常一層一層往上拋,如果沒(méi)有處理異常,那么程序就會(huì)被terminate. 比如C++和Java采用這種方式。
使用異常的代碼類似
看起來(lái)錯(cuò)誤處理代碼與業(yè)務(wù)邏輯分開(kāi),比較清晰。但有如下的不足,
錯(cuò)誤處理也容易被忽略,不寫(xiě)相應(yīng)的catch,
上層調(diào)用者在嵌套很多層的時(shí)候,很難知道底層是否會(huì)拋異常。
這么寫(xiě)代碼,在catch的地方,分不清楚是在哪里出了錯(cuò)誤,step1?step3?
注:python把異常還用于程序控制流改變,如StopInteractionException用于跳出循環(huán)。
Java里異常還分checked exception和unchecked exception。checked exception是必須要處理的異常,從而可以避免被忽略。但checked exception有其局限性,比如添加新的checked exception,會(huì)改變接口簽名,變得不能向前兼容。
綜上,我們需要一種錯(cuò)誤處理
避免無(wú)意識(shí)地忽略。
可閱讀性強(qiáng)。
其中返回值和異常都可能會(huì)被無(wú)意識(shí)忽略。可讀性,異常好于返回值,且避免占用了返回值。而不可忽略的Java checked exception有它自己的問(wèn)題。
就沒(méi)有其他更好的方式了嗎?Rust給出了它的答案,使用Result 類型。
什么是Result和類型?
Result的完整形態(tài)是Result
一個(gè)是沒(méi)有錯(cuò)誤時(shí)的計(jì)算結(jié)果
一個(gè)是出錯(cuò)時(shí),要返回的錯(cuò)誤
第一點(diǎn),我們可以看到,現(xiàn)在返回值可以用于返回函數(shù)計(jì)算的結(jié)果了,沒(méi)有被錯(cuò)誤占領(lǐng)。
第二點(diǎn),因?yàn)榉祷氐闹涤植皇怯?jì)算結(jié)果,所以程序員不能直接使用返回值,需要先檢查具體的類型,沒(méi)有出錯(cuò)時(shí),才能使用計(jì)算結(jié)果。這樣又避免了無(wú)意識(shí)的忽略錯(cuò)誤。
我們可以簡(jiǎn)陋地認(rèn)為Result類型,是C++里面的tag union,即包含一個(gè)tag的union。其中tag是錯(cuò)誤標(biāo)記,如果是0表示成功,非零表示錯(cuò)誤,而union則存放著具體的錯(cuò)誤或者具體的計(jì)算結(jié)果。(很多時(shí)候Result,稱作是和類型 sum type)
可以避免無(wú)意識(shí)地忽略錯(cuò)誤,那么可讀性呢?
因?yàn)榉祷刂挡皇怯?jì)算結(jié)果,需要檢查一下才能繼續(xù)下一步,這不就跟錯(cuò)誤返回值一樣了嗎?
注:先把話說(shuō)明,沒(méi)有錯(cuò)誤處理的代碼是可讀性最好的。因?yàn)橹挥衕appy path,第一步,第二步等等。但我們討論在可能出錯(cuò)的時(shí)候的可讀性。
Result和類型的代碼可以是
哇咔咔,這看上去可讀性很差那。實(shí)話說(shuō),這么寫(xiě)的代碼的確沒(méi)有什么可讀性。
但Rust提供了另外一個(gè)寫(xiě)法,如下
let res = step1()?;let res= step2()?;let res = step3()?;
這個(gè)寫(xiě)法看起來(lái)很像異常的情況。業(yè)務(wù)邏輯和錯(cuò)誤處理沒(méi)有交織在一起。
眼尖的讀者會(huì)發(fā)現(xiàn)每個(gè)函數(shù)都有個(gè)問(wèn)號(hào)?。而錯(cuò)誤處理就藏在?后面。
問(wèn)號(hào)的存在,讓Rust自動(dòng)幫你檢查返回值,在出錯(cuò)的時(shí)候直接返回錯(cuò)誤,不再繼續(xù)往下走了。問(wèn)號(hào)可以展開(kāi)為如下的形式(簡(jiǎn)化版本,方便理解,實(shí)際版本請(qǐng)看官方文檔),
到這里,我們可以看到Rust的創(chuàng)新點(diǎn)在于將錯(cuò)誤與計(jì)算結(jié)果放在了返回值,而不是單純地返回錯(cuò)誤,或者返回計(jì)算結(jié)果和從第三個(gè)路徑返回異常。并且提供了問(wèn)號(hào)和組合子來(lái)簡(jiǎn)寫(xiě)錯(cuò)誤處理。所以同時(shí)提供了避免無(wú)意識(shí)忽略錯(cuò)誤和提供可讀性。
但錯(cuò)誤處理遠(yuǎn)遠(yuǎn)不止這點(diǎn)內(nèi)容。在我寫(xiě)了GitHub的webhook微服務(wù) https://github.com/Celthi/github-webhook-gateway 以后,我發(fā)現(xiàn)寫(xiě)了一大坨下面的代碼
寫(xiě)成這樣,說(shuō)明我對(duì)Rust的錯(cuò)誤處理仍然沒(méi)有理解到位,于是我試著重構(gòu)這段代碼,并提了個(gè)問(wèn)題How reduce the nested if and indents?
經(jīng)過(guò)重構(gòu)以后,我發(fā)現(xiàn)了如下的一些情況
有時(shí)候只想處理成功的情況,我稱之為“最大努力做事”。所以代碼邏輯是這樣
這也是我自己代碼那么多縮進(jìn)的原因。它可以通過(guò)如下方式來(lái)改善,
方式一、首先先把代碼段提到一個(gè)單獨(dú)的函數(shù)post_sending_task(),然后將返回值改成Result,所以調(diào)用的地方代碼是
let _ = best_delivery(); //這里使用使用_,說(shuō)明我們不關(guān)心失敗的情況
在這個(gè)best_delivery()里面,我們就可以使用問(wèn)號(hào)表達(dá)式了。
方式二、使用組合子,如將Option轉(zhuǎn)換成Result,從而可以使用問(wèn)號(hào),如
let res = get_something().ok_or_else(|| err)?;
這里ok_or_else是option上的組合子。什么是組合子,簡(jiǎn)單理解是將東西組合在一起的函數(shù)。至于”子“,一種稱謂罷了,要說(shuō)相似的話,第一反應(yīng)類似套接字里面的”字“的功能。
方式三、提前返回。通過(guò)反轉(zhuǎn)if的條件,提前返回,比如,
提前返回沒(méi)有問(wèn)號(hào)那么可讀性強(qiáng),但是減少了縮進(jìn)的層數(shù)。
方式四、如果獲取結(jié)果的同時(shí)必須處理錯(cuò)誤的情況,那么使用下面的形式,
注意,問(wèn)號(hào)表達(dá)式是適合于獲取結(jié)果且不處理錯(cuò)誤,直接往上拋。
經(jīng)過(guò)這四個(gè)個(gè)方式的改善,我的代碼可讀性提高了,變成了
錯(cuò)誤處理與日志、錯(cuò)誤報(bào)告
錯(cuò)誤處理的時(shí)候,通常要寫(xiě)日志。但是錯(cuò)誤處理和日志是兩碼事。不是所有的錯(cuò)誤處理都要寫(xiě)日志,而且不同的錯(cuò)誤,寫(xiě)到的日志級(jí)別是不一樣的,如調(diào)試,信息,錯(cuò)誤,嚴(yán)重等等級(jí)別。
錯(cuò)誤處理是處理出錯(cuò)的情況,而日志是記錄感興趣的信息。它們有重合,但是關(guān)注點(diǎn)不一樣。以后再寫(xiě)文章。
錯(cuò)誤報(bào)告(error report)跟錯(cuò)誤處理也是兩碼事,雖然經(jīng)常關(guān)聯(lián)在一起,也留作以后再寫(xiě)文章。
審核編輯:劉清
-
JAVA
+關(guān)注
關(guān)注
20文章
2983瀏覽量
106471 -
python
+關(guān)注
關(guān)注
56文章
4822瀏覽量
85821 -
Rust
+關(guān)注
關(guān)注
1文章
233瀏覽量
6873
原文標(biāo)題:Rust代碼啟發(fā)之錯(cuò)誤處理
文章出處:【微信號(hào):Rust語(yǔ)言中文社區(qū),微信公眾號(hào):Rust語(yǔ)言中文社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
Rust語(yǔ)言中錯(cuò)誤處理的機(jī)制
如何處理STM32的HAL庫(kù)函數(shù)返回異常問(wèn)題?
嵌入式C編程常用的異常錯(cuò)誤處理
WebApi之接口返回值的四種類型
C語(yǔ)言程序開(kāi)發(fā)中關(guān)于函數(shù)返回值的問(wèn)題
return-函數(shù)的返回值是什么

什么是函數(shù)的返回值?
rust語(yǔ)言基礎(chǔ)學(xué)習(xí): rust中的錯(cuò)誤處理
C語(yǔ)言中的錯(cuò)誤處理機(jī)制解析
閉包在錯(cuò)誤處理中的應(yīng)用模式探索
一站式統(tǒng)一返回值封裝、異常處理、異常錯(cuò)誤碼解決方案—最強(qiáng)的Sping Boot接口優(yōu)雅響應(yīng)處理器
HTTP相關(guān)返回值異常如何解決(上篇)

評(píng)論