自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

我已經(jīng)受夠了“系統(tǒng)異常”!

系統(tǒng) 其他OS
給系統(tǒng)異常后面帶了個(gè) flag 標(biāo)識(shí),當(dāng)出現(xiàn)問(wèn)題時(shí),根據(jù)標(biāo)識(shí)就能快速定位日志來(lái)排查問(wèn)題了,對(duì)于有完善日志系統(tǒng)(如 ELK)的項(xiàng)目來(lái)說(shuō)已經(jīng)大大改善了程序員們的生存狀況。

作為用戶,你有沒(méi)有這樣的經(jīng)驗(yàn):用個(gè)軟件,隔三岔五彈個(gè)框:系統(tǒng)異常!

作為程序員,你有沒(méi)有這樣的經(jīng)驗(yàn):

運(yùn)營(yíng)同學(xué)又屁顛屁顛跑來(lái)求助:“用戶不能下單了!”

“報(bào)什么錯(cuò)?”

“系統(tǒng)異常!”

無(wú)論作為用戶還是程序員,一見(jiàn)到“系統(tǒng)異?!彼膫€(gè)大字,我整個(gè)人都不好了。

它除了告訴我系統(tǒng)出問(wèn)題了,沒(méi)有任何有價(jià)值的信息。

這往往是程序員一天苦逼生活的開(kāi)始。

我們獲取不到任何有價(jià)值的信息,只能到處抓蝦。

先看看系統(tǒng)負(fù)載,嗯,沒(méi)問(wèn)題。

再看看錯(cuò)誤日志,一大堆日志滾來(lái)滾去,也看不出所以然。

于是我們不得不求助運(yùn)營(yíng)同學(xué):“去要一下用戶手機(jī)號(hào)或者賬號(hào),手機(jī)型號(hào)、版本,最好能錄個(gè)頻!”

等了半天,運(yùn)營(yíng)妹妹終于搞來(lái)了這些信息,于是我們又一頓各種查日志,然后盯著代碼一行一行找,最終發(fā)現(xiàn)了 bug 所在。

為何會(huì)有“系統(tǒng)異?!保?/h2>

喜歡將對(duì)外錯(cuò)誤信息一股腦寫(xiě)成“系統(tǒng)異?!钡?,一般處于以下幾種原因:

  1. 剛?cè)胄械男“?,尚未深入體驗(yàn)程序員的苦難生活。
  2. “敏感信息”信徒,對(duì)他們來(lái)說(shuō),任何系統(tǒng)錯(cuò)誤信息都屬于敏感信息,需要“包裝”一下。
  3. 高敏行業(yè),公司強(qiáng)制要求。

我見(jiàn)過(guò)一些系統(tǒng)是這樣處理的:

class BaseController {
    errorHandler(err) {
        this.response.sendJSON({code: 500, message: '系統(tǒng)異常'})
    }
}

意思是,該系統(tǒng)的所有 throws 都被轉(zhuǎn)成“系統(tǒng)異?!?!

關(guān)鍵還連個(gè)日志都不記錄!

后續(xù)的開(kāi)發(fā)人員為了方便定位錯(cuò)誤,便在業(yè)務(wù)層代碼里面各種 log,業(yè)務(wù)代碼慘不忍睹。

“系統(tǒng)異?!眰兊母倪M(jìn)

上面那種極端的代碼是比較少見(jiàn)的,一般遇到更多的是這樣:

class BaseController {
    errorHandler(err) {
        // 生成異常標(biāo)識(shí)并記錄日志
        let flag = random()
        log(err, flag)
        this.response.sendJSON({"code": 500, "message": `系統(tǒng)異常(${flag})`})
    }
}

給系統(tǒng)異常后面帶了個(gè) flag 標(biāo)識(shí),當(dāng)出現(xiàn)問(wèn)題時(shí),根據(jù)標(biāo)識(shí)就能快速定位日志來(lái)排查問(wèn)題了,對(duì)于有完善日志系統(tǒng)(如 ELK)的項(xiàng)目來(lái)說(shuō)已經(jīng)大大改善了程序員們的生存狀況。

但上面的代碼有什么問(wèn)題呢?

試想某支付邏輯有如下代碼:

if (balance < amount) {
    throw new NotEnoughException('卡余額不足')
}

余額不足,很常見(jiàn)的場(chǎng)景,但用戶看到的是這樣的提示:“系統(tǒng)異常(1877618)”。

此時(shí),我不知道用戶和程序員有沒(méi)有崩潰,至少你的老板是崩潰的。

“錯(cuò)誤碼”們橫空出現(xiàn)

“系統(tǒng)異?!眰兏愠龅氖虑榱钊嗽彻矐?,如今這些信徒已經(jīng)不多了,要么迫于壓力改邪歸正了,要么被主管開(kāi)除殆盡了。

如今,你更可能遇到的是這樣的代碼:

配置文件:

// 全局:定義統(tǒng)一的錯(cuò)誤碼和錯(cuò)誤文字
const OK = 200
const SYS_ERR = 500
const NOT_FOUND = 404
const NOT_ENOUGH = 405

const map = {
    200: "OK",
    500: "系統(tǒng)錯(cuò)誤",
    404: "未找到資源",
    405: "余額不足",
}

// 錯(cuò)誤碼轉(zhuǎn)文字
function error(code) {
    return map[code]
}

業(yè)務(wù)層代碼:

if (balance < amount) {
    // 該自定義異常類僅允許傳入錯(cuò)誤碼,內(nèi)部根據(jù) error() 函數(shù)轉(zhuǎn)文字
    throw new MyException(NOT_ENOUGH)
}

控制器:

class BaseController {
    errorHandler(err) {
        log(err)
        this.response.sendJSON({"code": err.code, "message": err.message})
        // 或者:this.response.sendJSON({"code": err.code, "message": error(err.code)})
    }
}

這種錯(cuò)誤處理原則是通過(guò)錯(cuò)誤碼統(tǒng)一整個(gè)項(xiàng)目的 code 和 message,開(kāi)發(fā)人員不能在程序中自己定義錯(cuò)誤描述。

我稱這類程序員為”錯(cuò)誤碼“信徒。

“錯(cuò)誤碼”們主要的擔(dān)心是:如果讓開(kāi)發(fā)人員自己在代碼里面定義錯(cuò)誤描述,會(huì)導(dǎo)致“哈莫雷特”問(wèn)題,即每個(gè)人的描述可能都不一樣,而且有可能會(huì)導(dǎo)致敏感信息泄露。

相對(duì)于“系統(tǒng)異?!眰?,“錯(cuò)誤碼”們已經(jīng)有了長(zhǎng)足的進(jìn)步,大家終于知道系統(tǒng)發(fā)生了什么樣的錯(cuò)誤,老板們也不用擔(dān)心因客戶卡余額不足導(dǎo)致的“系統(tǒng)異?!痹伊似放菩蜗罅?。

從此人猿共歡了!

從此人猿共歡了?

用戶購(gòu)買 500 元商品時(shí)提示“卡余額不足”,但更好的提示應(yīng)該是“卡余額不足,當(dāng)前可用余額 420.00”。

當(dāng)根據(jù) userId 查不到用戶信息時(shí),應(yīng)該提示“用戶不存在”,但不能保證開(kāi)發(fā)人員因不想定義新 code 而直接使用 404(未找到資源)。

錯(cuò)誤碼機(jī)制的問(wèn)題是其文字提示過(guò)于籠統(tǒng),導(dǎo)致在某些錯(cuò)誤場(chǎng)景下丟失重要價(jià)值信息(進(jìn)而導(dǎo)致問(wèn)題排查上的困難,問(wèn)題遲遲得不到解決),另一些場(chǎng)景下則帶來(lái)不好的用戶體驗(yàn)。

對(duì)于開(kāi)發(fā)人員來(lái)說(shuō),它會(huì)帶來(lái)兩種效果:一些開(kāi)發(fā)人員不想新定義一大堆錯(cuò)誤碼,于是將就著使用現(xiàn)有的錯(cuò)誤碼,導(dǎo)致錯(cuò)誤提示不倫不類;另外一些開(kāi)發(fā)人員則傾向于定義大量的錯(cuò)誤碼,幾乎每處異常都定義一個(gè)新錯(cuò)誤碼(理由是每處異常文字提示都不一樣),最終導(dǎo)致錯(cuò)誤碼失控。

“錯(cuò)誤碼”們的改進(jìn)

改進(jìn)其實(shí)很簡(jiǎn)單,就是允許異常類傳入自定義描述:

// 增加了可選參數(shù) message,允許傳入自定義描述
class MyException(code, message = '') {
    ...
}

期望程序中有如下調(diào)用:

if (balance < amount) {
    throw new MyException(NOT_ENOUGH, '卡余額不足,當(dāng)前可用余額' + balance)
}

但你會(huì)驚奇地發(fā)現(xiàn),大部分地方仍舊是這樣調(diào)的:

if (balance < amount) {
    throw new MyException(NOT_ENOUGH)
}

“錯(cuò)誤碼”們忽略了很重要的心理學(xué)上的問(wèn)題。

人都是有惰性的,如果你提供了偷懶的途徑,他沒(méi)有理由不偷懶。

反“錯(cuò)誤碼”們:追求自由

和“系統(tǒng)異?!眰円约啊板e(cuò)誤碼”們力求嚴(yán)格限制系統(tǒng)輸出不同,“自由派”追求極致的自由,code 和 message 都不用約束,開(kāi)發(fā)人員想怎么寫(xiě)就怎么寫(xiě)。

所以你可能在多個(gè)地方看到“卡余額不足”的錯(cuò)誤,但每個(gè)的錯(cuò)誤碼都不同(可能是不同的人寫(xiě)的,也可能是同一個(gè)開(kāi)發(fā)人員在不同時(shí)期寫(xiě)的,甚至是同一個(gè)人在同一天寫(xiě)的,寫(xiě)的時(shí)候完全看心情)。

自由派的做法對(duì)于錯(cuò)誤提示是有好處的,開(kāi)發(fā)人員可以盡情地定制個(gè)性化的提示內(nèi)容,當(dāng)系統(tǒng)出現(xiàn)異常時(shí)能根據(jù)現(xiàn)場(chǎng)提示很快定位錯(cuò)誤所在。不過(guò)由于錯(cuò)誤碼是隨性寫(xiě)的,對(duì)于依賴錯(cuò)誤碼的調(diào)用方(系統(tǒng))并不友好。一些系統(tǒng)需要依據(jù) API 返回的錯(cuò)誤碼做一些特殊邏輯處理,當(dāng)調(diào)用方認(rèn)為 405 表示余額不足,然而過(guò)幾天又來(lái)個(gè) 503 的余額不足時(shí),調(diào)用方程序員的內(nèi)心肯定是崩潰的。

中庸之道

本人的異常處理原則是:強(qiáng)制固定 code、自定義 message。

要想設(shè)計(jì)出“人猿共歡”的異常處理機(jī)制,必須先搞清楚誰(shuí)需要用到這些信息。

異常信息的第一使用者是人,這里包括使用者(用戶)和異常處理者(運(yùn)營(yíng)人員、程序員)。

細(xì)分一下,異常又分為業(yè)務(wù)異常和系統(tǒng) bug。

業(yè)務(wù)異常是指業(yè)務(wù)流程中的異常場(chǎng)景,如支付時(shí)卡余額不足導(dǎo)致無(wú)法支付、用券時(shí)發(fā)現(xiàn)券不符合使用條件、用戶執(zhí)行了某個(gè)未授權(quán)的操作等。這類異常的觸發(fā)者是用戶自己(而不是系統(tǒng)),信息受眾是用戶。所以業(yè)務(wù)異常的信息提示必須注重用戶體驗(yàn),優(yōu)秀的提示文字至少要做到以下幾點(diǎn):

  1. 尊重用戶,不要讓用戶感覺(jué)受到冒犯或戲謔(請(qǐng)慎用自認(rèn)為很“幽默”的話語(yǔ));
  2. 清晰,應(yīng)包含觸發(fā)異常的關(guān)鍵信息(如當(dāng)余額不足時(shí)應(yīng)提示當(dāng)前余額是多少);
  3. 具備指引性,用戶看了之后清楚該怎么做;

第二類異常是系統(tǒng) bug,如接口超時(shí)、非預(yù)期參數(shù)導(dǎo)致程序崩潰、代碼邏輯 bug 等。該類異常的觸發(fā)者是系統(tǒng)(或者說(shuō)開(kāi)發(fā)系統(tǒng)的程序員),信息受眾是程序員。所以 bug 類型異常的信息提示必須對(duì)程序員友好,讓程序員看到錯(cuò)誤提示后能夠快速定位到問(wèn)題的原因、代碼所在的位置。

我們說(shuō)異常,一般就是指 bug 型異常,這類異常占程序員的精力也是最多的,也最值得優(yōu)化處理機(jī)制。

bug 型異常具有如下特征:

  1. 不可控性。沒(méi)有程序員會(huì)主動(dòng)去寫(xiě) bug,但沒(méi)有哪個(gè)系統(tǒng)完全沒(méi)有 bug。我們無(wú)法預(yù)知 bug 到底來(lái)自哪里、會(huì)有什么樣的提示信息;
  2. 定位困難。當(dāng)系統(tǒng)提示“余額不足”時(shí),我們很快知道是用戶卡沒(méi)錢了,但當(dāng)系統(tǒng)提示“參數(shù)類型錯(cuò)誤”時(shí),我們往往只能一臉懵逼;
  3. 可能涉及敏感信息。如 SQL 操作錯(cuò)誤時(shí)可能會(huì)將整個(gè) SQL 語(yǔ)句暴露給外界;

因而優(yōu)秀的 bug 型異常處理機(jī)制應(yīng)做到:

  1. 提示信息對(duì)程序員友好;
  2. 記錄函數(shù)調(diào)用棧信息;
  3. 脫敏;

提示信息對(duì)程序員友好,可能意味著對(duì)用戶并不友好,一些程序員正是據(jù)此以“用戶體驗(yàn)”之名將 bug 提示信息轉(zhuǎn)換成了“對(duì)用戶友好”的提示文案,結(jié)果是所有人看了都云里霧里。

我的觀點(diǎn)是:bug 型異常壓根不用考慮用戶體驗(yàn)。

為啥?

因?yàn)橄到y(tǒng)出 bug 本身已經(jīng)是非常糟糕的用戶體驗(yàn)了,用戶不會(huì)因諸如“哎呀,系統(tǒng)開(kāi)小差了”之類的廢話就變得好受些,用戶真正關(guān)心的是盡快能正常下單。

此時(shí)的當(dāng)務(wù)之急是快速修復(fù) bug,所以提示文案的定位功能就非常重要,一段純技術(shù)性的文字,對(duì)于用戶來(lái)說(shuō)可能是天書(shū),但對(duì)于程序員很實(shí)用。

然而,這不意味著給到用戶端的錯(cuò)誤提示就可以為所欲為。如果我們?yōu)榱朔奖愣ㄎ槐銓⒄麄€(gè)程序調(diào)用棧 alert 出來(lái),雖然可能并不會(huì)進(jìn)一步拉低用戶體驗(yàn),但至少給人的感覺(jué)是不專業(yè),而且過(guò)多的信息也意味著很容易暴露敏感信息(如程序路徑、軟件版本、SQL 語(yǔ)句),如果對(duì)方是個(gè)黑客,你只能自祈多福了。

另外要注重脫敏。大部分框架在數(shù)據(jù)庫(kù)操作失敗時(shí),其 message 信息中都會(huì)包含諸如 SQL 語(yǔ)句之類的敏感信息,這類信息不可暴露到外面。

綜上,我們可以采取文案+日志的策略,文案中包含關(guān)鍵信息,日志中包含詳細(xì)信息(包括調(diào)用棧信息)。

大部分的 DB 庫(kù)拋出的異常都有共同基類(如 DBException),我們可以針對(duì)這類異常做脫敏處理。

這也告訴我們另一件事:當(dāng)我們自己開(kāi)發(fā)公共庫(kù)時(shí),最好為該庫(kù)定義一個(gè)統(tǒng)一基類異常,這樣當(dāng)使用者想要特殊處理該庫(kù)拋出的所有異常時(shí)不至于狗咬刺猬無(wú)處下牙了。

另外,有些團(tuán)隊(duì)并不想記錄業(yè)務(wù)型異常的調(diào)用棧信息(“卡余額不足”時(shí),調(diào)用棧信息并無(wú)多大意義)。我們可以在框架層面定義個(gè)業(yè)務(wù)異?;悾築usinessException,異常處理時(shí)不記錄該類型的調(diào)用棧信息。

異常信息的另一個(gè)使用者是系統(tǒng)。包括其他服務(wù)、前端 js 腳本等。

我見(jiàn)過(guò)類似這樣的代碼:

try {
    ...
} catch (e) {
    switch (e.message) {
        case'用戶不存在':
            ...
        case ...
    }
}

如果某個(gè)后端程序員哪天心血來(lái)潮將“用戶不存在”改成“用戶信息不存在”,系統(tǒng)就崩了。

寫(xiě)出如此脆弱系統(tǒng)的程序員應(yīng)該被釘?shù)?1024 號(hào)恥辱柱上!

不過(guò),在釘釘子之前,我們應(yīng)該傾聽(tīng)一下他那痛苦的心聲:接口返回的錯(cuò)誤碼實(shí)在是雜亂無(wú)章,光“用戶不存在”的錯(cuò)誤碼就有八個(gè),說(shuō)不定未來(lái)還會(huì)增加。為“系統(tǒng)穩(wěn)定性”考慮,最終選擇匹配 message。

好吧,應(yīng)該將后端程序員一起釘上去!

系統(tǒng)只會(huì),也只應(yīng)該關(guān)注錯(cuò)誤碼。所以和 message 的隨意性不同,code 應(yīng)具備相當(dāng)?shù)姆€(wěn)定性。

同一個(gè)系統(tǒng),如果 406 表示“用戶不存在”,就絕不應(yīng)該再用其他值(如 604)表示相同的含義。

另外,“code 面向系統(tǒng)”這一特點(diǎn)也要求 code 定義的是某一類異常(而不是某一個(gè)異常)。例如“訂單創(chuàng)建失敗”是一類異常,在業(yè)務(wù)代碼中針對(duì)不同的失敗原因有不同的 message,但其 code 都是一樣的。

然而人類對(duì)數(shù)字并不敏感,要不同的程序員都保證寫(xiě) throw new Exception('用戶不存在', 406)(而不是寫(xiě)throw new Exception('用戶不存在', 604))是不可能的。

所以需要將數(shù)字文本化,也就是定義錯(cuò)誤碼常量:

const USER_NOT_EXISTS = 406

代碼中只能使用錯(cuò)誤碼常量:

throw new Exception('用戶不存在', USER_NOT_EXISTS)

禁止使用字面量。

不過(guò)上面這段 throw 并不理想,首先默認(rèn)類型 Exception 并不具備業(yè)務(wù)語(yǔ)義,另外開(kāi)發(fā)人員如果硬是用數(shù)字字面量誰(shuí)也沒(méi)辦法。更可取的方式是針對(duì)每種類型異常定義單獨(dú)的異常類,該異常類僅允許傳入 message,類內(nèi)部自行綁定 code:

// 用戶不存在
class UserNotExistsException extends Exception { 
    constructor(message) {
        super(message)
        
        this.code = ErrCode.USER_NOT_EXISTS
    }
}

使用:

if (!User.find(uid)) {
    // 此寫(xiě)法更具表達(dá)性,而且開(kāi)發(fā)人員無(wú)需關(guān)注錯(cuò)誤碼
    throw new UserNotExistsException(`用戶不存在(uid:${uid})`)
}

異常捕獲機(jī)制代碼示例

先總結(jié)一下中庸主義的異常捕獲機(jī)制特點(diǎn):

  1. 強(qiáng)制開(kāi)發(fā)人員自己編寫(xiě)異常描述文案;
  2. 整個(gè)項(xiàng)目強(qiáng)制使用統(tǒng)一的錯(cuò)誤碼定義;
  3. 為業(yè)務(wù)型異常定義單獨(dú)的基類;
  4. 關(guān)鍵信息脫敏處理;

統(tǒng)一錯(cuò)誤碼定義:

const OK = 200
const SYS_ERR = 500
const NOT_FOUND = 404
const NOT_ENOUGH = 405
const USER_NOT_EXISTS = 406
...

業(yè)務(wù)異?;悾?/p>

class BussinessException extends Exception {
    ...
}

異常類定義:

class UserNotExistsException extends BussinessException {
    constructor(message) {
        super(message)
        
        this.code = ErrCode.USER_NOT_EXISTS
    }
}

...

業(yè)務(wù)層使用:

...
if (!User.find(uid)) {
    throw new UserNotExistsException(`用戶不存在(uid:${uid})`)
}
...

控制器基類捕獲異常

class BaseController {
    ...
    
    errorHandler(err) {
        // 是否業(yè)務(wù)型異常
        const isBussError = err instanceof BussinessException
        // 是否數(shù)據(jù)庫(kù)異常
        const isDBError = err instanceof DBException
        // 生成用于跟蹤異常日志的隨機(jī)串
        const flag = isBussError ? '' : random()
        
        let message = err.message
        if (isDBError) {
            // 數(shù)據(jù)庫(kù)異常,脫敏
            message = `數(shù)據(jù)異常(flag:${flag})`
        } elseif (!isBussError) {
            // 非業(yè)務(wù)型異常記錄 flag 標(biāo)識(shí)
            message += `(flag:${flag})`
        }
        
        // 記錄日志(日志要記錄原始的 message)
        log(err.message, isBussError ? '' : err.stackTrace(), flag)
        
        // 返回給調(diào)用端
        this.response.sendJSON({"code": err.code, "message": message})
    }
    
    function log(message, stackTrace, flag) {
        ...
    }
    ...
}

基于約定的異常處理機(jī)制

即便框架層提供了完善的異常處理機(jī)制,你還是無(wú)法阻止開(kāi)發(fā)人員寫(xiě)這樣的代碼:

if (!User.find(uid)) {
    throw new Exception(’系統(tǒng)異?!? 500)
}

一行代碼就給你打回原形!

所以異常處理機(jī)制是基于約定的(團(tuán)隊(duì)公約)。

責(zé)任編輯:武曉燕 來(lái)源: 編碼胡同
相關(guān)推薦

2024-06-11 00:00:01

系統(tǒng)技術(shù)代碼

2011-10-25 09:24:08

2018-02-23 09:55:12

程序員壓迫Python

2025-01-22 07:00:00

C++11構(gòu)造函數(shù)C++

2023-08-29 06:50:01

Javamaven

2015-08-10 10:26:08

2011-11-08 11:22:35

技術(shù)周刊

2020-05-22 15:16:45

遠(yuǎn)程工作辦公互聯(lián)網(wǎng)

2013-03-08 09:54:25

2021-07-01 05:17:52

Windows 11操作系統(tǒng)微軟

2014-03-06 09:23:19

Git服務(wù)器Github

2012-11-12 12:03:26

臺(tái)式機(jī)Mac聯(lián)想

2020-07-06 14:40:28

攜號(hào)轉(zhuǎn)網(wǎng)運(yùn)營(yíng)商服務(wù)

2021-03-03 14:55:10

開(kāi)發(fā)MySQL代碼

2021-03-19 08:54:02

芯片Morpheus漏洞

2012-05-04 13:09:46

IBM大型機(jī)云計(jì)算

2015-11-16 09:04:19

寫(xiě)代碼程序員年齡

2015-11-17 09:47:32

代碼寫(xiě)下去

2019-03-06 15:04:35

Google安全WebView

2018-03-08 07:03:35

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)