成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

我已經受夠了“系統異常”!

系統 其他OS
給系統異常后面帶了個 flag 標識,當出現問題時,根據標識就能快速定位日志來排查問題了,對于有完善日志系統(如 ELK)的項目來說已經大大改善了程序員們的生存狀況。

作為用戶,你有沒有這樣的經驗:用個軟件,隔三岔五彈個框:系統異常!

作為程序員,你有沒有這樣的經驗:

運營同學又屁顛屁顛跑來求助:“用戶不能下單了!”

“報什么錯?”

“系統異常!”

無論作為用戶還是程序員,一見到“系統異常”四個大字,我整個人都不好了。

它除了告訴我系統出問題了,沒有任何有價值的信息。

這往往是程序員一天苦逼生活的開始。

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

先看看系統負載,嗯,沒問題。

再看看錯誤日志,一大堆日志滾來滾去,也看不出所以然。

于是我們不得不求助運營同學:“去要一下用戶手機號或者賬號,手機型號、版本,最好能錄個頻!”

等了半天,運營妹妹終于搞來了這些信息,于是我們又一頓各種查日志,然后盯著代碼一行一行找,最終發現了 bug 所在。

為何會有“系統異常”?

喜歡將對外錯誤信息一股腦寫成“系統異常”的,一般處于以下幾種原因:

  1. 剛入行的小白,尚未深入體驗程序員的苦難生活。
  2. “敏感信息”信徒,對他們來說,任何系統錯誤信息都屬于敏感信息,需要“包裝”一下。
  3. 高敏行業,公司強制要求。

我見過一些系統是這樣處理的:

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

意思是,該系統的所有 throws 都被轉成“系統異常”!

關鍵還連個日志都不記錄!

后續的開發人員為了方便定位錯誤,便在業務層代碼里面各種 log,業務代碼慘不忍睹。

“系統異常”們的改進

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

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

給系統異常后面帶了個 flag 標識,當出現問題時,根據標識就能快速定位日志來排查問題了,對于有完善日志系統(如 ELK)的項目來說已經大大改善了程序員們的生存狀況。

但上面的代碼有什么問題呢?

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

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

余額不足,很常見的場景,但用戶看到的是這樣的提示:“系統異常(1877618)”。

此時,我不知道用戶和程序員有沒有崩潰,至少你的老板是崩潰的。

“錯誤碼”們橫空出現

“系統異常”們搞出的事情令人猿共憤,如今這些信徒已經不多了,要么迫于壓力改邪歸正了,要么被主管開除殆盡了。

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

配置文件:

// 全局:定義統一的錯誤碼和錯誤文字
const OK = 200
const SYS_ERR = 500
const NOT_FOUND = 404
const NOT_ENOUGH = 405

const map = {
    200: "OK",
    500: "系統錯誤",
    404: "未找到資源",
    405: "余額不足",
}

// 錯誤碼轉文字
function error(code) {
    return map[code]
}

業務層代碼:

if (balance < amount) {
    // 該自定義異常類僅允許傳入錯誤碼,內部根據 error() 函數轉文字
    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)})
    }
}

這種錯誤處理原則是通過錯誤碼統一整個項目的 code 和 message,開發人員不能在程序中自己定義錯誤描述。

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

“錯誤碼”們主要的擔心是:如果讓開發人員自己在代碼里面定義錯誤描述,會導致“哈莫雷特”問題,即每個人的描述可能都不一樣,而且有可能會導致敏感信息泄露。

相對于“系統異常”們,“錯誤碼”們已經有了長足的進步,大家終于知道系統發生了什么樣的錯誤,老板們也不用擔心因客戶卡余額不足導致的“系統異常”砸了品牌形象了。

從此人猿共歡了!

從此人猿共歡了?

用戶購買 500 元商品時提示“卡余額不足”,但更好的提示應該是“卡余額不足,當前可用余額 420.00”。

當根據 userId 查不到用戶信息時,應該提示“用戶不存在”,但不能保證開發人員因不想定義新 code 而直接使用 404(未找到資源)。

錯誤碼機制的問題是其文字提示過于籠統,導致在某些錯誤場景下丟失重要價值信息(進而導致問題排查上的困難,問題遲遲得不到解決),另一些場景下則帶來不好的用戶體驗。

對于開發人員來說,它會帶來兩種效果:一些開發人員不想新定義一大堆錯誤碼,于是將就著使用現有的錯誤碼,導致錯誤提示不倫不類;另外一些開發人員則傾向于定義大量的錯誤碼,幾乎每處異常都定義一個新錯誤碼(理由是每處異常文字提示都不一樣),最終導致錯誤碼失控。

“錯誤碼”們的改進

改進其實很簡單,就是允許異常類傳入自定義描述:

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

期望程序中有如下調用:

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

但你會驚奇地發現,大部分地方仍舊是這樣調的:

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

“錯誤碼”們忽略了很重要的心理學上的問題。

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

反“錯誤碼”們:追求自由

和“系統異常”們以及“錯誤碼”們力求嚴格限制系統輸出不同,“自由派”追求極致的自由,code 和 message 都不用約束,開發人員想怎么寫就怎么寫。

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

自由派的做法對于錯誤提示是有好處的,開發人員可以盡情地定制個性化的提示內容,當系統出現異常時能根據現場提示很快定位錯誤所在。不過由于錯誤碼是隨性寫的,對于依賴錯誤碼的調用方(系統)并不友好。一些系統需要依據 API 返回的錯誤碼做一些特殊邏輯處理,當調用方認為 405 表示余額不足,然而過幾天又來個 503 的余額不足時,調用方程序員的內心肯定是崩潰的。

中庸之道

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

要想設計出“人猿共歡”的異常處理機制,必須先搞清楚誰需要用到這些信息。

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

細分一下,異常又分為業務異常和系統 bug。

業務異常是指業務流程中的異常場景,如支付時卡余額不足導致無法支付、用券時發現券不符合使用條件、用戶執行了某個未授權的操作等。這類異常的觸發者是用戶自己(而不是系統),信息受眾是用戶。所以業務異常的信息提示必須注重用戶體驗,優秀的提示文字至少要做到以下幾點:

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

第二類異常是系統 bug,如接口超時、非預期參數導致程序崩潰、代碼邏輯 bug 等。該類異常的觸發者是系統(或者說開發系統的程序員),信息受眾是程序員。所以 bug 類型異常的信息提示必須對程序員友好,讓程序員看到錯誤提示后能夠快速定位到問題的原因、代碼所在的位置。

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

bug 型異常具有如下特征:

  1. 不可控性。沒有程序員會主動去寫 bug,但沒有哪個系統完全沒有 bug。我們無法預知 bug 到底來自哪里、會有什么樣的提示信息;
  2. 定位困難。當系統提示“余額不足”時,我們很快知道是用戶卡沒錢了,但當系統提示“參數類型錯誤”時,我們往往只能一臉懵逼;
  3. 可能涉及敏感信息。如 SQL 操作錯誤時可能會將整個 SQL 語句暴露給外界;

因而優秀的 bug 型異常處理機制應做到:

  1. 提示信息對程序員友好;
  2. 記錄函數調用棧信息;
  3. 脫敏;

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

我的觀點是:bug 型異常壓根不用考慮用戶體驗。

為啥?

因為系統出 bug 本身已經是非常糟糕的用戶體驗了,用戶不會因諸如“哎呀,系統開小差了”之類的廢話就變得好受些,用戶真正關心的是盡快能正常下單。

此時的當務之急是快速修復 bug,所以提示文案的定位功能就非常重要,一段純技術性的文字,對于用戶來說可能是天書,但對于程序員很實用。

然而,這不意味著給到用戶端的錯誤提示就可以為所欲為。如果我們為了方便定位便將整個程序調用棧 alert 出來,雖然可能并不會進一步拉低用戶體驗,但至少給人的感覺是不專業,而且過多的信息也意味著很容易暴露敏感信息(如程序路徑、軟件版本、SQL 語句),如果對方是個黑客,你只能自祈多福了。

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

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

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

這也告訴我們另一件事:當我們自己開發公共庫時,最好為該庫定義一個統一基類異常,這樣當使用者想要特殊處理該庫拋出的所有異常時不至于狗咬刺猬無處下牙了。

另外,有些團隊并不想記錄業務型異常的調用棧信息(“卡余額不足”時,調用棧信息并無多大意義)。我們可以在框架層面定義個業務異常基類:BusinessException,異常處理時不記錄該類型的調用棧信息。

異常信息的另一個使用者是系統。包括其他服務、前端 js 腳本等。

我見過類似這樣的代碼:

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

如果某個后端程序員哪天心血來潮將“用戶不存在”改成“用戶信息不存在”,系統就崩了。

寫出如此脆弱系統的程序員應該被釘到 1024 號恥辱柱上!

不過,在釘釘子之前,我們應該傾聽一下他那痛苦的心聲:接口返回的錯誤碼實在是雜亂無章,光“用戶不存在”的錯誤碼就有八個,說不定未來還會增加。為“系統穩定性”考慮,最終選擇匹配 message。

好吧,應該將后端程序員一起釘上去!

系統只會,也只應該關注錯誤碼。所以和 message 的隨意性不同,code 應具備相當的穩定性。

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

另外,“code 面向系統”這一特點也要求 code 定義的是某一類異常(而不是某一個異常)。例如“訂單創建失敗”是一類異常,在業務代碼中針對不同的失敗原因有不同的 message,但其 code 都是一樣的。

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

所以需要將數字文本化,也就是定義錯誤碼常量:

const USER_NOT_EXISTS = 406

代碼中只能使用錯誤碼常量:

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

禁止使用字面量。

不過上面這段 throw 并不理想,首先默認類型 Exception 并不具備業務語義,另外開發人員如果硬是用數字字面量誰也沒辦法。更可取的方式是針對每種類型異常定義單獨的異常類,該異常類僅允許傳入 message,類內部自行綁定 code:

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

使用:

if (!User.find(uid)) {
    // 此寫法更具表達性,而且開發人員無需關注錯誤碼
    throw new UserNotExistsException(`用戶不存在(uid:${uid})`)
}

異常捕獲機制代碼示例

先總結一下中庸主義的異常捕獲機制特點:

  1. 強制開發人員自己編寫異常描述文案;
  2. 整個項目強制使用統一的錯誤碼定義;
  3. 為業務型異常定義單獨的基類;
  4. 關鍵信息脫敏處理;

統一錯誤碼定義:

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

業務異常基類:

class BussinessException extends Exception {
    ...
}

異常類定義:

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

...

業務層使用:

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

控制器基類捕獲異常

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

基于約定的異常處理機制

即便框架層提供了完善的異常處理機制,你還是無法阻止開發人員寫這樣的代碼:

if (!User.find(uid)) {
    throw new Exception(’系統異常‘, 500)
}

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

所以異常處理機制是基于約定的(團隊公約)。

責任編輯:武曉燕 來源: 編碼胡同
相關推薦

2024-06-11 00:00:01

系統技術代碼

2011-10-25 09:24:08

2018-02-23 09:55:12

程序員壓迫Python

2025-01-22 07:00:00

C++11構造函數C++

2023-08-29 06:50:01

Javamaven

2015-08-10 10:26:08

2011-11-08 11:22:35

技術周刊

2020-05-22 15:16:45

遠程工作辦公互聯網

2013-03-08 09:54:25

2021-07-01 05:17:52

Windows 11操作系統微軟

2014-03-06 09:23:19

Git服務器Github

2012-11-12 12:03:26

臺式機Mac聯想

2020-07-06 14:40:28

攜號轉網運營商服務

2021-03-03 14:55:10

開發MySQL代碼

2021-03-19 08:54:02

芯片Morpheus漏洞

2012-05-04 13:09:46

IBM大型機云計算

2015-11-16 09:04:19

寫代碼程序員年齡

2015-11-17 09:47:32

代碼寫下去

2018-03-08 07:03:35

2019-03-06 15:04:35

Google安全WebView
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 搞黄网站在线观看 | 久久精品中文字幕 | 久久国产精品视频观看 | 天天操天天摸天天爽 | 亚洲视频免费观看 | 女人夜夜春 | 免费不卡视频 | 女女百合av大片一区二区三区九县 | 天天看天天爽 | 欧美精品福利视频 | 国产欧美日韩视频 | 国产在线看片 | а_天堂中文最新版地址 | 日本一区二区三区视频在线 | 欧美性猛交一区二区三区精品 | 国产一区免费 | 一区二区三区四区在线视频 | 亚洲精品乱码久久久久久按摩观 | 中文字幕韩在线第一页 | 韩三级在线观看 | 亚洲一区二区在线播放 | av手机在线播放 | 国产一区二区三区久久久久久久久 | 日韩精品视频在线 | 亚洲一区二区三区免费在线观看 | 一级片在线视频 | 91精品国产综合久久久久久漫画 | 亚洲一区免费在线 | 中文字幕乱码一区二区三区 | 日韩人体在线 | 高清国产午夜精品久久久久久 | 欧美日韩一区精品 | 亚洲国产成人精品在线 | 不卡av电影在线播放 | 色爱综合网 | 91大神在线资源观看无广告 | 欧美成人一区二区三区片免费 | 密乳av| 中文在线播放 | 日韩一级 | 国产一二三区免费视频 |