0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Rust代碼啟發(fā)之返回值異常錯(cuò)誤處理

工程師鄧生 ? 來源:CrackingOysters ? 作者:CrackingOysters ? 2022-09-22 09:24 ? 次閱讀

編寫程序,錯(cuò)誤處理是不可避免的。

程序員總是偏向正常的情況,而容易忽略有錯(cuò)誤的情況。

返回值錯(cuò)誤處理

最開始,錯(cuò)誤是通過返回值來表示,比如非零表示錯(cuò)誤,0表示成功。而處理錯(cuò)誤的代碼類似

poYBAGMruXSAOsonAABERI2P8U0826.jpg

這樣的代碼,錯(cuò)誤處理代碼和業(yè)務(wù)邏輯交織在一起,也容易忽略處理錯(cuò)誤。以及把返回值只用于錯(cuò)誤返回,有點(diǎn)浪費(fèi)的感覺。因?yàn)楹芏鄷r(shí)候把計(jì)算結(jié)果作為返回值,更符合思考的邏輯。

異常錯(cuò)誤處理

后面出現(xiàn)了異常的方式,在出錯(cuò)的時(shí)候,拋出異常。異常一層一層往上拋,如果沒有處理異常,那么程序就會(huì)被terminate. 比如C++Java采用這種方式。

使用異常的代碼類似

poYBAGMruYaARsVnAAAmqblhFCU436.jpg

看起來錯(cuò)誤處理代碼與業(yè)務(wù)邏輯分開,比較清晰。但有如下的不足,

錯(cuò)誤處理也容易被忽略,不寫相應(yīng)的catch,

上層調(diào)用者在嵌套很多層的時(shí)候,很難知道底層是否會(huì)拋異常。

這么寫代碼,在catch的地方,分不清楚是在哪里出了錯(cuò)誤,step1?step3?

注:python把異常還用于程序控制流改變,如StopInteractionException用于跳出循環(huán)。

Java里異常還分checked exception和unchecked exception。checked exception是必須要處理的異常,從而可以避免被忽略。但checked exception有其局限性,比如添加新的checked exception,會(huì)改變接口簽名,變得不能向前兼容。

綜上,我們需要一種錯(cuò)誤處理

避免無意識(shí)地忽略。

可閱讀性強(qiáng)。

其中返回值和異常都可能會(huì)被無意識(shí)忽略??勺x性,異常好于返回值,且避免占用了返回值。而不可忽略的Java checked exception有它自己的問題。

就沒有其他更好的方式了嗎?Rust給出了它的答案,使用Result 類型。

什么是Result和類型?

Result的完整形態(tài)是Result,其中T和E是泛型參數(shù)。不懂泛型不重要,這里跟泛型沒有關(guān)系。我們要知道的是Result是兩個(gè)類型的集合:

一個(gè)是沒有錯(cuò)誤時(shí)的計(jì)算結(jié)果

一個(gè)是出錯(cuò)時(shí),要返回的錯(cuò)誤

第一點(diǎn),我們可以看到,現(xiàn)在返回值可以用于返回函數(shù)計(jì)算的結(jié)果了,沒有被錯(cuò)誤占領(lǐng)。

第二點(diǎn),因?yàn)榉祷氐闹涤植皇怯?jì)算結(jié)果,所以程序員不能直接使用返回值,需要先檢查具體的類型,沒有出錯(cuò)時(shí),才能使用計(jì)算結(jié)果。這樣又避免了無意識(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)

可以避免無意識(shí)地忽略錯(cuò)誤,那么可讀性呢?

因?yàn)榉祷刂挡皇怯?jì)算結(jié)果,需要檢查一下才能繼續(xù)下一步,這不就跟錯(cuò)誤返回值一樣了嗎?

注:先把話說明,沒有錯(cuò)誤處理的代碼是可讀性最好的。因?yàn)橹挥衕appy path,第一步,第二步等等。但我們討論在可能出錯(cuò)的時(shí)候的可讀性。

Result和類型的代碼可以是

pYYBAGMruZqAb4TrAAAkG9L3Hh0254.jpg

哇咔咔,這看上去可讀性很差那。實(shí)話說,這么寫的代碼的確沒有什么可讀性。

但Rust提供了另外一個(gè)寫法,如下

let res = step1()?;let res= step2()?;let res = step3()?;

這個(gè)寫法看起來很像異常的情況。業(yè)務(wù)邏輯和錯(cuò)誤處理沒有交織在一起。

眼尖的讀者會(huì)發(fā)現(xiàn)每個(gè)函數(shù)都有個(gè)問號(hào)?。而錯(cuò)誤處理就藏在?后面。

問號(hào)的存在,讓Rust自動(dòng)幫你檢查返回值,在出錯(cuò)的時(shí)候直接返回錯(cuò)誤,不再繼續(xù)往下走了。問號(hào)可以展開為如下的形式(簡(jiǎn)化版本,方便理解,實(shí)際版本請(qǐng)看官方文檔),

pYYBAGMrubWAA8t2AAAb_QfNdaI791.jpg

到這里,我們可以看到Rust的創(chuàng)新點(diǎn)在于將錯(cuò)誤與計(jì)算結(jié)果放在了返回值,而不是單純地返回錯(cuò)誤,或者返回計(jì)算結(jié)果和從第三個(gè)路徑返回異常。并且提供了問號(hào)和組合子來簡(jiǎn)寫錯(cuò)誤處理。所以同時(shí)提供了避免無意識(shí)忽略錯(cuò)誤和提供可讀性。

但錯(cuò)誤處理遠(yuǎn)遠(yuǎn)不止這點(diǎn)內(nèi)容。在我寫了GitHub的webhook微服務(wù) https://github.com/Celthi/github-webhook-gateway 以后,我發(fā)現(xiàn)寫了一大坨下面的代碼

poYBAGMrugGABAnhAAD-8POIEvE692.jpg

寫成這樣,說明我對(duì)Rust的錯(cuò)誤處理仍然沒有理解到位,于是我試著重構(gòu)這段代碼,并提了個(gè)問題How reduce the nested if and indents?

經(jīng)過重構(gòu)以后,我發(fā)現(xiàn)了如下的一些情況

有時(shí)候只想處理成功的情況,我稱之為“最大努力做事”。所以代碼邏輯是這樣

poYBAGMruhSAQQ8nAAAoKm9J6kw469.jpg

這也是我自己代碼那么多縮進(jìn)的原因。它可以通過如下方式來改善,

方式一、首先先把代碼段提到一個(gè)單獨(dú)的函數(shù)post_sending_task(),然后將返回值改成Result,所以調(diào)用的地方代碼是

let _ = best_delivery(); //這里使用使用_,說明我們不關(guān)心失敗的情況

在這個(gè)best_delivery()里面,我們就可以使用問號(hào)表達(dá)式了。

方式二、使用組合子,如將Option轉(zhuǎn)換成Result,從而可以使用問號(hào),如

let res = get_something().ok_or_else(|| err)?;

這里ok_or_else是option上的組合子。什么是組合子,簡(jiǎn)單理解是將東西組合在一起的函數(shù)。至于”子“,一種稱謂罷了,要說相似的話,第一反應(yīng)類似套接字里面的”字“的功能。

方式三、提前返回。通過反轉(zhuǎn)if的條件,提前返回,比如,

poYBAGMrukGAEExGAAAUCHOhdbI596.jpg

提前返回沒有問號(hào)那么可讀性強(qiáng),但是減少了縮進(jìn)的層數(shù)。

方式四、如果獲取結(jié)果的同時(shí)必須處理錯(cuò)誤的情況,那么使用下面的形式,
poYBAGMruliAas22AAAiGpGUc8s587.jpg

注意,問號(hào)表達(dá)式是適合于獲取結(jié)果且不處理錯(cuò)誤,直接往上拋。

經(jīng)過這四個(gè)個(gè)方式的改善,我的代碼可讀性提高了,變成了

pYYBAGMrunCAFYXtAADRtwuGU7w403.jpg

錯(cuò)誤處理與日志、錯(cuò)誤報(bào)告

錯(cuò)誤處理的時(shí)候,通常要寫日志。但是錯(cuò)誤處理和日志是兩碼事。不是所有的錯(cuò)誤處理都要寫日志,而且不同的錯(cuò)誤,寫到的日志級(jí)別是不一樣的,如調(diào)試,信息,錯(cuò)誤,嚴(yán)重等等級(jí)別。

錯(cuò)誤處理是處理出錯(cuò)的情況,而日志是記錄感興趣的信息。它們有重合,但是關(guān)注點(diǎn)不一樣。以后再寫文章。

錯(cuò)誤報(bào)告(error report)跟錯(cuò)誤處理也是兩碼事,雖然經(jīng)常關(guān)聯(lián)在一起,也留作以后再寫文章。





審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • JAVA
    +關(guān)注

    關(guān)注

    19

    文章

    2973

    瀏覽量

    104910
  • python
    +關(guān)注

    關(guān)注

    56

    文章

    4801

    瀏覽量

    84867
  • Rust
    +關(guān)注

    關(guān)注

    1

    文章

    229

    瀏覽量

    6626

原文標(biāo)題:Rust代碼啟發(fā)之錯(cuò)誤處理

文章出處:【微信號(hào):Rust語言中文社區(qū),微信公眾號(hào):Rust語言中文社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    Rust語言中錯(cuò)誤處理的機(jī)制

    Rust語言中,錯(cuò)誤處理是一項(xiàng)非常重要的任務(wù)。由于Rust語言采用靜態(tài)類型檢查,在編譯時(shí)就能發(fā)現(xiàn)很多潛在的錯(cuò)誤,這使得程序員能夠更加自信和高效地開發(fā)程序。然而,即使我們?cè)诰幾g時(shí)盡可能
    的頭像 發(fā)表于 09-19 14:54 ?1457次閱讀

    如何處理STM32的HAL庫函數(shù)返回異常問題?

    (1)官方提供的例程里面,例如返回的結(jié)果不是 HAL_OK 的結(jié)果,一般直接跳轉(zhuǎn)到 錯(cuò)誤處理的函數(shù)里面了。這樣寫的目的是給開發(fā)者根據(jù)實(shí)際情況自己寫異常處理
    發(fā)表于 04-17 06:39

    嵌入式C編程常用的異常錯(cuò)誤處理

    嵌入式C編程中,異常錯(cuò)誤處理是確保系統(tǒng)穩(wěn)定性和可靠性的重要部分。以下是一些常見的異常錯(cuò)誤處理方法及其詳細(xì)說明和示例: 1. 斷言 (Assertions) 斷言用于在開發(fā)階段捕獲程
    發(fā)表于 08-06 14:32

    WebApi接口返回值的四種類型

    Webapi的接口返回值主要有四種類型 void無返回值 IHttpActionResult HttpResponseMessage 自定義類型 void無返回值 大家都知道void聲明的是一個(gè)無
    發(fā)表于 11-27 14:52 ?1.3w次閱讀

    C語言程序開發(fā)中關(guān)于函數(shù)返回值的問題

    C語言函數(shù)可以通過返回值表示輸出結(jié)果,例如 log() 函數(shù)的返回值會(huì)根據(jù)不同的輸入,返回不同的。再比如,我們定義一個(gè)函數(shù) myopen(),用于打開某個(gè)文件,那么,這個(gè)函數(shù)要么能夠
    發(fā)表于 09-06 10:01 ?964次閱讀

    return-函數(shù)的返回值是什么

    return關(guān)鍵字后接變量名或表達(dá)式可以將函數(shù)的計(jì)算結(jié)果返回到調(diào)用處。變量或表達(dá)式等同于接收果汁、豆?jié){的杯子。如果函數(shù)沒有返回值,return可以省略不寫。沒有返回值的意思是程序執(zhí)行完畢之后,不需要給調(diào)用函數(shù)處提供數(shù)據(jù)。
    的頭像 發(fā)表于 02-23 10:52 ?1253次閱讀
    return-函數(shù)的<b class='flag-5'>返回值</b>是什么

    什么是函數(shù)的返回值?

    函數(shù)的返回值是函數(shù)被調(diào)用后,執(zhí)行所調(diào)用函數(shù)內(nèi)代碼后所得出的結(jié)果,并且將返回給主函數(shù)的
    的頭像 發(fā)表于 04-04 17:21 ?5058次閱讀

    rust語言基礎(chǔ)學(xué)習(xí): rust中的錯(cuò)誤處理

    錯(cuò)誤是軟件中不可避免的,所以 Rust 有一些處理出錯(cuò)情況的特性。在許多情況下,Rust 要求你承認(rèn)錯(cuò)誤的可能性,并在你的
    的頭像 發(fā)表于 05-22 16:28 ?2154次閱讀

    ARM異常返回值的合法有哪些?各返回值分別代表什么?

    ARM異常返回值的合法有哪些?各返回值分別代表什么? ARM異常返回值的合法
    的頭像 發(fā)表于 10-19 16:36 ?948次閱讀

    C語言中的錯(cuò)誤處理機(jī)制解析

    C 語言不提供對(duì)錯(cuò)誤處理的直接支持,但是作為一種系統(tǒng)編程語言,它以返回值的形式允許您訪問底層數(shù)據(jù)。
    的頭像 發(fā)表于 02-26 11:19 ?535次閱讀

    閉包在錯(cuò)誤處理中的應(yīng)用模式探索

    通過在函數(shù)和方法中返回錯(cuò)誤對(duì)象作為它們的唯一或最后一個(gè)返回值——如果返回 nil,則沒有錯(cuò)誤發(fā)生——并且主調(diào)(calling)函數(shù)總是應(yīng)該檢
    的頭像 發(fā)表于 03-15 09:57 ?458次閱讀

    一站式統(tǒng)一返回值封裝、異常處理、異常錯(cuò)誤碼解決方案—最強(qiáng)的Sping Boot接口優(yōu)雅響應(yīng)處理

    1. 前言 統(tǒng)一返回值封裝、統(tǒng)一異常處理異常錯(cuò)誤碼體系的意義在于提高代碼的可維護(hù)性和可讀性,使
    的頭像 發(fā)表于 06-20 15:42 ?596次閱讀

    HTTP相關(guān)返回值異常如何解決(上篇)

    ? 今天我們講講HTTP相關(guān)返回值異常如何解決(實(shí)例持續(xù)更新中) HTTP介紹 HTTP(超文本傳輸協(xié)議,Hypertext Transfer Protocol)是用于在網(wǎng)絡(luò)上進(jìn)行數(shù)據(jù)交換的應(yīng)用層
    的頭像 發(fā)表于 10-20 16:40 ?382次閱讀
    HTTP相關(guān)<b class='flag-5'>返回值</b><b class='flag-5'>異常</b>如何解決(上篇)

    socket編程中的錯(cuò)誤處理技巧

    錯(cuò)誤處理能夠確保程序在遇到異常情況時(shí)不會(huì)崩潰,而是能夠優(yōu)雅地處理問題。 提升用戶體驗(yàn) :通過適當(dāng)?shù)?b class='flag-5'>錯(cuò)誤處理,可以給用戶提供清晰的錯(cuò)誤信息
    的頭像 發(fā)表于 11-01 17:47 ?903次閱讀

    C語言如何處理函數(shù)的返回值

    當(dāng)你在函數(shù)的最后寫上 return 0 的時(shí)候,它是如何返回給調(diào)用函數(shù)的? 比如 test 函數(shù),為了待會(huì)更好的看懂匯編代碼,我寫成了 return 1234。 處理函數(shù)的返回值,是不
    的頭像 發(fā)表于 01-16 09:21 ?33次閱讀