不想Go 錯(cuò)誤處理太臃腫,可以參考這個(gè)代碼設(shè)計(jì)
最近寫(xiě)了個(gè)程序,因?yàn)槭羌被睿菜茮](méi)有不急的...),所以這個(gè)程序又是我東拷一段,西粘一塊拼出來(lái)的。代碼寫(xiě)完了后,感覺(jué)這代碼屎一樣,都快把自己看哭了。真的是在心里邊寫(xiě)別罵,先是罵以前做這個(gè)項(xiàng)目的人蠢,項(xiàng)目搞的跟屎一樣,后來(lái)代碼跑起來(lái)了,順利交工后,變成了罵我自己蠢,這么寫(xiě)又不是不能用!
又不是不能用
不過(guò)在這個(gè)過(guò)程中,先不提項(xiàng)目里的業(yè)務(wù)邏輯、接口設(shè)計(jì)合不合理的事兒,這個(gè)我覺(jué)得在時(shí)間緊,加上人員更迭快的時(shí)候,正常人都會(huì)能粘就粘,不行了就再包一層,別改出線上問(wèn)題了就行。有一點(diǎn)我把自己蠢哭的是,Go 的這個(gè)錯(cuò)誤處理也太TM蠢了,一個(gè)程序我寫(xiě)了七八個(gè)錯(cuò)誤判斷,我給你們用偽代碼描述一下:
err, file := 接收傳文件(文件)
if err != nil {
記日志
返回錯(cuò)誤碼相應(yīng)
}
err, fh := 打開(kāi)上傳文件(file)
if err != nil {
記日志
返回錯(cuò)誤碼相應(yīng)
}
err, data := 把文件里的行記錄解析/轉(zhuǎn)換一下(row)
if err != nil {
記日志
返回錯(cuò)誤碼相應(yīng)
}
err, data3 := 調(diào)一下第三方接口拿數(shù)據(jù)
if err != nil {
記日志
返回錯(cuò)誤碼相應(yīng)
}
err, data2 := 調(diào)一下內(nèi)部其他服務(wù)拿數(shù)據(jù)
if err != nil {
記日志
返回錯(cuò)誤碼相應(yīng)
}
err := 寫(xiě)庫(kù)
if err != nil {
記日志
返回錯(cuò)誤碼相應(yīng)
}
上面這個(gè)例子毫不夸張,我相信各位在自己的項(xiàng)目里一定見(jiàn)過(guò),如果你是做業(yè)務(wù)開(kāi)發(fā)的會(huì)更常見(jiàn)。
這里有人肯定會(huì)問(wèn),Go的錯(cuò)誤處理就這樣你難道第一天見(jiàn)嗎,還能被蠢哭。誒,這不是降本提效后人員少了一半,我們這幫級(jí)別沒(méi)混上去的虛線Leader,這不又開(kāi)始自己寫(xiě)代碼了嘛,以前蠢又蠢不到自己。再加上以前的系統(tǒng)、項(xiàng)目分層、服務(wù)隔離整的還湊活,不會(huì)像上面這樣,在控制層調(diào)這么多業(yè)務(wù)對(duì)象,把蠢瓜代碼集中在了一起…… 官感馬上不一樣了。
于是乎我就在思考,有沒(méi)有什么設(shè)計(jì)模式什么的,能把這些東西隱藏下去,應(yīng)該有吧,沒(méi)有什么是包一層代碼解決不了的吧,實(shí)在不行就包兩層……誒,咋一不小心把設(shè)計(jì)模式的精髓給說(shuō)出來(lái)了。
Go 優(yōu)雅處理錯(cuò)誤的幾種方案
我這幾天在網(wǎng)上看了不少說(shuō),Go 錯(cuò)誤處理的,但基本上都是說(shuō)怎么自定義包裝 error 、傳遞error 之類(lèi)的,講怎么在寫(xiě) Go 代碼時(shí)能更優(yōu)雅更好看的文章比較少,寫(xiě)的最好的是左耳朵耗子老師在自己博客里介紹的兩種方式。
一種是用函數(shù)式編程的 Closure 把相同的 if err !=nil 之類(lèi)的代碼抽象出來(lái)重新定義一個(gè)函數(shù),但是這種方式會(huì)導(dǎo)致新的問(wèn)題--在每個(gè)函數(shù)里都需要引入內(nèi)部函數(shù)和一個(gè) error 變量,所以咱就不多說(shuō)了,有興趣的可以去原博文查看。
這里直接介紹另外一種更好的,對(duì)項(xiàng)目侵入不是很大的方案給大家。在 Go 語(yǔ)言官方庫(kù) bufio? 中 Scanner對(duì)象的錯(cuò)處理的實(shí)現(xiàn)方式可以給我們一點(diǎn)啟發(fā),它大概是這么實(shí)現(xiàn)的。
scanner := bufio.NewScanner(input)
for scanner.Scan() {
token := scanner.Text()
// process token
}
if err := scanner.Err(); err != nil {
// process the error
}
上面的代碼我們可以看到,scanner?在操作底層的I/O的時(shí)候,那個(gè)for-loop中沒(méi)有任何的 if err !=nil? 的情況,退出循環(huán)后有一個(gè) scanner.Err() 的檢查。看來(lái)使用了結(jié)構(gòu)體的方式。
我們來(lái)看一下 Scanner類(lèi)型的定義:
type Scanner struct {
r io.Reader
...//其他字段省略
err error
}
這個(gè)類(lèi)型內(nèi)部持有一個(gè)error 在迭代執(zhí)行 Scan 方法時(shí),遇到錯(cuò)誤后會(huì)往這個(gè) error 中記錄錯(cuò)誤。
func (s *Scanner) Scan() bool {
...// 其余代碼省略
for {
if err != nil {
s.setErr(err)
return false
}
}
func (s *Scanner) Err() error {
if s.err == io.EOF {
return nil
}
return s.err
}
所以我們可以參考這個(gè)思路繼續(xù)搞下去。比如來(lái)一個(gè)讀取業(yè)務(wù)對(duì)象的
上面這個(gè)示例相信大家很容易看懂,不過(guò),其使用場(chǎng)景也就只能在對(duì)于同一個(gè)業(yè)務(wù)對(duì)象的不斷操作下可以簡(jiǎn)化錯(cuò)誤處理,對(duì)于多個(gè)業(yè)務(wù)對(duì)象的話,還是得需要各種 if err != nil的方式。
那有什么辦法呢,咱們之前說(shuō)過(guò)一次:沒(méi)有什么是包一層代碼解決不了的吧,實(shí)在不行就包兩層。那么接下來(lái)我們?cè)僮鲆粚影b,以下是我對(duì)解決這個(gè)問(wèn)題的一點(diǎn)點(diǎn)理解,會(huì)借鑒一點(diǎn)DDD中分層的概念解決這個(gè)事情。
更容易落地的方案
剛才那個(gè)例子的問(wèn)題是只適合減少單個(gè)業(yè)務(wù)對(duì)象邏輯操作中的 if err != nill 判斷,那么針對(duì)這塊呢,咱們可以把涉及多個(gè)業(yè)務(wù)對(duì)象的操作放在一個(gè)應(yīng)用服務(wù)里,把剛才在業(yè)務(wù)對(duì)象做的錯(cuò)誤處理判斷拿到應(yīng)用服務(wù)里,這樣業(yè)務(wù)對(duì)象里,比如Model之類(lèi)的下層模塊里,就還能按照正常的流程寫(xiě)代碼了,不用每個(gè)方法開(kāi)頭都要先判斷一下。
這里提前說(shuō)一下,在一些架構(gòu)設(shè)計(jì)里會(huì)分應(yīng)用服務(wù)和領(lǐng)域服務(wù),這兩者的概念完全不一樣,應(yīng)用服務(wù)是面向產(chǎn)品需求的用例實(shí)現(xiàn)的,負(fù)責(zé)業(yè)務(wù)用例流的任務(wù)協(xié)調(diào),就是我們實(shí)現(xiàn)API時(shí),往往會(huì)控制層調(diào)應(yīng)用服務(wù),多個(gè)不同的業(yè)務(wù)對(duì)象可以放到一個(gè)應(yīng)用服務(wù)里。而領(lǐng)域服務(wù)是專(zhuān)一給一個(gè)領(lǐng)域的,這塊我就不多解釋了,DDD這些我也是看了幾本書(shū),看過(guò)COLA框架的實(shí)現(xiàn),還在似懂非懂的水平。
總之記住一點(diǎn),通過(guò)應(yīng)用服務(wù)可以協(xié)調(diào)多個(gè)業(yè)務(wù)對(duì)象執(zhí)行任務(wù),同時(shí)我們上面業(yè)務(wù)對(duì)象加的那些錯(cuò)誤處理抽離到應(yīng)用服務(wù)層里,讓業(yè)務(wù)對(duì)象更專(zhuān)注自己的職責(zé)。這樣的話,你的服務(wù)層代碼,可能就得變成了這樣
然后我們的控制層呢,調(diào)用應(yīng)用服務(wù)層拿到結(jié)果,并且在這個(gè)時(shí)候判斷整個(gè)需求任務(wù)執(zhí)行的過(guò)程中有沒(méi)有錯(cuò)誤,有的話記錄錯(cuò)誤,返回錯(cuò)誤響應(yīng)給客戶(hù)端。
Go 錯(cuò)誤處理的基礎(chǔ)
之前分享過(guò)一篇文章??關(guān)于Go程序錯(cuò)誤處理的一些建議??說(shuō)的是我們應(yīng)該怎么用好 Go 的error 接口,自定義錯(cuò)誤,包裝整個(gè)錯(cuò)誤鏈等相關(guān)的技能。跟本文的內(nèi)容關(guān)聯(lián)起來(lái)看,可能會(huì)對(duì)錯(cuò)誤處理有個(gè)更全局的理解,在這里也推薦給大家。
總結(jié)
今天給大家分享了一些在讓Go代碼的錯(cuò)誤處理更優(yōu)雅上,我學(xué)到和?想到的一些東西。其實(shí)大家可以發(fā)現(xiàn),我們是把多個(gè) if err != nil 分散到了多個(gè)方法里,這樣代碼最起碼從感官上看起來(lái)比在一個(gè)方法里寫(xiě)七八個(gè)錯(cuò)誤判斷更好一點(diǎn)。