你說你 200,但我知道你快崩潰了
夜已深,桌上的燈還亮著。你坐在螢幕前,滑著那些 commit 記錄,畫面一行一行閃爍著變更的痕跡。世界安靜下來時,反而讓人開始聽見自己的內心。
那是一段日子,你明明撐得很辛苦,卻還是對朋友說:「我很好。」
就像有的 API,明明出錯了,卻還是包裝成一個 200 OK。你開始意識到,有些錯誤不是程式的錯,而是我們不願意讓人知道「其實我現在沒那麼好」。
但 API 和人都一樣。你不說,別人只會以為你真的沒事。錯了,就該勇敢說出口。這不只是對外部的交代,更是對自己的一種體貼。
🧳 RESTful 派的觀點:讓 HTTP status 說話
WebAPI 出錯時,是否必須透過 HTTP Status 反映執行結果,例如:找不到時吐 404、系統出錯時回應 500?
在這個 API 世界裡,RESTful 就像是遵守交通號誌的好市民:綠燈行,紅燈停,該錯就錯,該成功就成功,乾淨俐落。
🎯 HTTP Status 是最基本的語言禮儀
RESTful 設計精神強調:HTTP Status Code 本來就是用來傳達狀態的。
- 2xx:成功(200 OK、201 Created)
- 4xx:客戶端出包(400 Bad Request、404 Not Found)
- 5xx:伺服器出事(500 Internal Server Error)
這樣的分類看似老派,但其實好處多多:
- Client 好判斷:前端不用解 body、比對內層 status,只要看到 4xx、5xx 就知道出事了。
- SRE 好監控:非 2xx 就能被 ELK、Prometheus、Sentry 抓去泡茶討論,不用猜是不是回傳格式又包了什麼玄機。
- 開發者少吵架:看到 500 沒有人會問「那這樣是成功還是失敗?」
🧑🎨 UI/UX 也愛它
想像一下,如果你網址 key 錯,API 回你 200,然後 body 寫著 “code”: “NotFound”,前端要怎麼辦?
沒錯──要嘛自己寫判斷邏輯,要嘛默默顯示個空畫面。
但如果回的是 404,前端可以設個全域攔截:
「找不到頁面,點我回首頁 👈」
這種 UX 等級提升,不靠神祕狀態碼,只靠 HTTP status 就能搞定,何樂不為?
🔐 資安與錯誤處理:不藏也不亂講
開發階段我們希望看到詳細錯誤(例如連 DB 失敗、NullReferenceException 等),
但上線之後,就該轉成一個簡單的 traceId 回傳給前端,背後配上完整的 log 留給內部查詢。
這不只是乾淨,更是保命:
- 避免洩漏機敏錯誤資訊(例如連線字串、例外堆疊)
- 符合弱點掃描規範(掃描工具看到 raw exception 會狂吼)
- 配上 CI/CD 來避免環境變數搞錯、設定混亂,讓 API 更穩健。
🔧 營運排錯更順手
當你拿到一個 traceId,可以馬上去 ELK、AppInsights、Datadog 查 log,
再配合 F12 DevTools 抓 500 response,幾秒鐘就能定位是哪個 API 死給你看。
甚至還可以統計一週內 4xx/5xx 的出現比例,用來評估系統穩定度或團隊 KPI!
(這時候你會慶幸沒用那個萬能 200,把錯誤全部藏進地毯底下,這讓我想到勿言推理的那個司機,想想就毛骨悚然)
🧰 開發與介接工具天然支援
如果你照規矩使用 HTTP status,Swagger/OpenAPI 就會自動幫你生成正確的文件:
- 200:正常資料長什麼樣
- 400:驗證錯誤長什麼樣
- 500:系統爆炸長什麼樣
但如果你所有錯誤都回 200,那你就要手動說明:「雖然 status 是 200,但請你一定要看 body 裡的 code 值喔,不然會以為成功」(然後下一位工程師就會大崩潰)
🗂️ Cache、框架與第三方工具也都照這套走
多數 cache 工具(如 Workbox)只會快取 200,如果錯誤也用 200,那錯誤畫面就會被 cache。多數前端框架(如 Axios、Retrofit)根據 HTTP status 自動分類成功/失敗,你用萬用 200 就是在逼他們多寫一次邏輯。
🤔 為什麼有人選擇「統一包成 200」?
1️⃣ 為了防爆網,大家都說自己沒事
有些系統是這麼設計的:
「只要我回 200,就不會觸發前端爆紅訊息,也不會讓使用者嚇到,Peace ✌️」
這種策略常見於:
UI 太脆弱,看到 500 就原地崩潰
一些第三方整合,只認得 status code 是 200 才願意處理資料
老舊系統改不了,靠 body 裡的 “code”: “E101” 來區分成功或失敗
這就像你不管點什麼,餐廳都回你「已送餐」,但送來的是空盤,還要你自己看盤底貼紙:「喔,原來是 ‘Out of stock’ 啊。」
2️⃣ 統一格式,逼大家都照 body 的規則走
有些團隊想要「格式一致性」,乾脆規定所有 API:
「無論發生什麼事,我都回 200,成功或失敗都藏在 response.body.code 裡,自己去判斷。」
這看似合理──但實際執行起來會發現:
前端 A 會用 if 判斷 “code”
前端 B 忘記判斷,直接把錯誤當成功顯示
前端 C 想做 retry,卻不知道這其實是 fatal error
然後你作為後端工程師,只能靜靜看著三個版本出現三種行為
這就像是學校規定「不管考什麼試都只發 A」,但老師會在試卷右下角註記「其實你不及格」,然後希望學生自己發現。
⚠️ 統一 200 帶來的副作用
🔍 監控工具看不到災情
ELK、Sentry、Prometheus 都愛聽 status code。你全都回 200,對這些監控工具來說,世界一片祥和,花都開好了,只有你自己在深夜默默重啟 service。
🔄 API 行為變得難預測
有些 API 回 200 是成功,有些回 200 是失敗,全靠 body 判斷。
你會開始聽到這種對話:
「欸這 API 是回 200 就算成功嗎?」
「要看,它是回 200,但裡面 code 要是 ‘0000’ 才算。」
「那哪支 API 不是?」
「…不知道,你看文件好了(雖然沒寫)。」
📄 文件工具無從支援
Swagger、Postman 想幫你產生 API 文件,卻不知道錯誤長怎樣。你全回 200,它只會幫你畫一個:
200 OK – 一切看起來都好像沒問題
但實際上裡面藏著 “code”: “E500”,然後你又要手動去補文件、補說明、補註解──人生突然變得很文書化。
🛠️ 要自己造太多輪子
像錯誤導頁、API 快取、重新嘗試(retry)、提示框設計……這些原本都可以靠 HTTP status 處理,現在通通要自己寫一套解析邏輯,保證工作量翻倍、bug 數翻三倍。
🔐 資安風險也不小
萬一你在 “message” 裡偷偷塞了 Exception message、stack trace,還真的全被前端顯示出去──恭喜你,不只用了錯的 status,還送出了一份免費的漏洞說明書給攻擊者。
其實最安全的做法是:只回一個 traceId,把細節寫進 log 裡就好,留給內部查。
☘️ 結語:別讓錯誤淹沒在成功之中
在那趟旅行的最後一天,你坐在窗邊,喝著那杯其實還不錯的熱巧克力,心裡忽然有些釋然。
你想到那些沒說出口的「我其實不好」,那些錯誤被靜靜包成 200 的回應,還有那些被誤以為成功、實則在等待被理解的訊號。
程式世界也是如此。
回傳 404,並不代表我們失敗,只是誠實地說出:「我現在找不到。」
回傳 500,不是崩潰,而是請求:「我需要一點時間,請你等等我。」
這比默默回個 200 然後什麼都沒發生來得有力量,也更值得被信任。
我們在設計系統的同時,其實也在學著怎麼好好地對話——對使用者、對隊友,也對自己。
所以別讓錯誤藏在看似一切正常的外殼裡。勇敢讓它們說話,
因為一個願意說實話的 API,就像一個願意承認脆弱的大人,
雖然不完美,卻可靠且溫柔。