某大廠前端負(fù)責(zé)人上周在內(nèi)部技術(shù)會(huì)上摔了鍵盤——不是 metaphor,是真的摔了。導(dǎo)火索是一份接口文檔:32個(gè)字段里17個(gè)是null,5個(gè)命名風(fēng)格混用,還有3個(gè)字段類型和文檔對(duì)不上。后端團(tuán)隊(duì)回復(fù):"能用就行,別挑了。"
這不是技術(shù)債,這是協(xié)作癌。
REST API 從2000年 Fielding 的博士論文走到今天,成了"能跑就行"的遮羞布。我見過最荒唐的案例:某電商平臺(tái)的庫(kù)存接口,查詢時(shí)返回字符串"0",扣減時(shí)要求傳數(shù)字0,同步時(shí)又變成布爾值false。前端為了兼容,寫了47行類型轉(zhuǎn)換代碼,注釋寫著「后端說下周改,2021年寫的」。
坑一:把"資源"當(dāng)成"數(shù)據(jù)庫(kù)表"原樣輸出
REST 的核心是資源抽象,不是 SQL 結(jié)果集直出。我見過一個(gè)用戶詳情接口返回了127個(gè)字段,包含密碼哈希、內(nèi)部審計(jì)標(biāo)記、甚至上游系統(tǒng)的原始錯(cuò)誤日志。前端只需要昵稱和頭像,卻要在內(nèi)存里扛著整個(gè)數(shù)據(jù)倉(cāng)庫(kù)。
GraphQL 當(dāng)年能火,不是因?yàn)榧夹g(shù)多先進(jìn),是前端被這種"全量返回"逼瘋了。但大多數(shù)團(tuán)隊(duì)沒條件切 GraphQL,于是前端開始自己做字段過濾——在客戶端寫死白名單,或者更常見的,忍著。
正確的做法是分版本、分場(chǎng)景設(shè)計(jì)端點(diǎn)。/users/me 給 C 端,/users/admin/{id} 給運(yùn)營(yíng)后臺(tái),/users/internal/sync 給內(nèi)部系統(tǒng)。三個(gè)端點(diǎn),三套字段,而不是一個(gè)端點(diǎn)試圖討好所有人。
坑二:HTTP 狀態(tài)碼當(dāng)擺設(shè),200 里藏錯(cuò)誤
這是行業(yè)通病。某支付接口,銀行卡余額不足返回 HTTP 200,body 里嵌套 code: "INSUFFICIENT_BALANCE"。前端攔截器以為成功了,直接跳轉(zhuǎn)成功頁(yè),用戶看著"支付成功"的動(dòng)畫一臉懵。
HTTP 協(xié)議花了三十年定義狀態(tài)碼,不是讓你當(dāng)裝飾品的。4xx 是客戶端錯(cuò),5xx 是服務(wù)端錯(cuò),2xx 是成功。業(yè)務(wù)錯(cuò)誤可以用 422(Unprocessable Entity)或 409(Conflict),別躲在 200 里玩捉迷藏。
更隱蔽的問題是緩存。CDN 和瀏覽器會(huì)緩存 200 響應(yīng),但不會(huì)緩存 4xx/5xx。你把錯(cuò)誤包在 200 里,等于告訴全世界"這個(gè)結(jié)果可以存起來(lái)",下次用戶看到的可能是一周前的錯(cuò)誤信息。
坑三:分頁(yè)設(shè)計(jì)得像隨機(jī)數(shù)生成器
offset/limit 是最常見的分頁(yè),也是最容易出事的。數(shù)據(jù)在翻頁(yè)過程中被插入或刪除,用戶會(huì)看到重復(fù)項(xiàng)或跳過項(xiàng)。更糟的是深分頁(yè):offset=100000 時(shí),數(shù)據(jù)庫(kù)要做全表掃描排序,查詢時(shí)間從 10ms 變成 3s。
游標(biāo)分頁(yè)(cursor-based)能解決一致性,但實(shí)現(xiàn)復(fù)雜。我見過一個(gè)折中方案:列表頁(yè)用 offset 保證簡(jiǎn)單,搜索/篩選結(jié)果用游標(biāo)保證準(zhǔn)確。關(guān)鍵是文檔里要說清楚,而不是讓前端猜"這個(gè)接口能不能支持深翻頁(yè)"。
還有一個(gè)細(xì)節(jié):總頁(yè)數(shù)。大數(shù)據(jù)場(chǎng)景下,count(*) 是性能殺手。有些接口干脆不返回總數(shù),前端就不知道怎么渲染分頁(yè)器。折中方案是返回"是否有下一頁(yè)"的布爾值,或者預(yù)估數(shù)量帶誤差范圍。
坑四:API 版本管理靠"約定俗成"
/v1/users 改成 /v2/users,舊版本什么時(shí)候下線?我見過一個(gè)產(chǎn)品同時(shí)跑著 v1 到 v4,因?yàn)?不知道誰(shuí)在調(diào)用"。最后靠日志分析+郵件轟炸,花了三個(gè)月才敢 deprecate v1。
版本號(hào)放在 URL 還是 Header,業(yè)界沒統(tǒng)一標(biāo)準(zhǔn)。但比位置更重要的是生命周期:每個(gè)版本明確的 EOL(End of Life)日期,提前 6 個(gè)月通知,埋點(diǎn)監(jiān)控調(diào)用量。這些流程比技術(shù)實(shí)現(xiàn)難十倍,卻是協(xié)作的基礎(chǔ)。
Breaking change 的定義也要明確。加字段通常安全,刪字段、改類型、改枚舉值都是 breaking。某次一個(gè)狀態(tài)碼從 "PENDING" 改成 "PENDING_PAYMENT",前端枚舉匹配失敗,整個(gè)訂單流程癱瘓——這在語(yǔ)義上算"兼容改動(dòng)"還是"破壞性更新"?
坑五:文檔和代碼是兩部科幻小說
Swagger/OpenAPI 自動(dòng)生成解決了"文檔過時(shí)"問題,但帶來(lái)了新問題:自動(dòng)生成的文檔往往暴露內(nèi)部實(shí)現(xiàn)細(xì)節(jié),字段描述是"created_at: 創(chuàng)建時(shí)間"這種廢話。真正需要說明的——這個(gè)字段什么場(chǎng)景會(huì)為空、枚舉值的業(yè)務(wù)含義、并發(fā)時(shí)的行為——全靠口頭傳承。
更隱蔽的是示例數(shù)據(jù)。文檔里的示例是理想情況,生產(chǎn)環(huán)境可能是 edge case 的集合。我見過一個(gè)地址字段,示例是"北京市海淀區(qū)",實(shí)際出現(xiàn)過空字符串、null、"暫未填寫"、JSON 對(duì)象、甚至 HTML 片段——因?yàn)樯嫌蜗到y(tǒng)對(duì)接了五個(gè)不同的渠道。
前端防御性編程的成本,往往比后端寫文檔高一個(gè)數(shù)量級(jí)。每個(gè)字段都要做類型檢查、空值處理、長(zhǎng)度截?cái)唷SS 過濾,這些代碼不會(huì)出現(xiàn)在需求文檔里,但會(huì)出現(xiàn)在代碼評(píng)審的吐槽里。
坑六:錯(cuò)誤信息寫給機(jī)器看,不是給人看
"Error code: 0x3F2A" 這種錯(cuò)誤,前端沒法展示給用戶,只能寫死映射表。映射表和后端代碼不同步,用戶看到的提示就莫名其妙。
好的錯(cuò)誤響應(yīng)應(yīng)該包含三層:機(jī)器識(shí)別的 code(用于日志和監(jiān)控)、前端可用的 message key(用于國(guó)際化映射)、可選的 detail(用于調(diào)試)。某國(guó)際化產(chǎn)品做得漂亮:錯(cuò)誤響應(yīng)帶 locale 參數(shù),后端直接返回翻譯后的文案,前端零成本。
但別走另一個(gè)極端:把堆棧 trace 直接拋給前端。這等于告訴攻擊者你的技術(shù)棧和代碼結(jié)構(gòu)。平衡點(diǎn)是內(nèi)部錯(cuò)誤碼+用戶友好文案+日志關(guān)聯(lián) ID,三方各取所需。
坑七:把 API 設(shè)計(jì)當(dāng)成"后端自己的事"
這是最深層的問題。很多團(tuán)隊(duì)的分工是:后端定接口,前端適配。結(jié)果是接口設(shè)計(jì)只考慮數(shù)據(jù)庫(kù)表結(jié)構(gòu),不考慮 UI 的加載順序、狀態(tài)流轉(zhuǎn)、緩存策略。
一個(gè)典型場(chǎng)景:商品詳情頁(yè)需要基礎(chǔ)信息、價(jià)格、庫(kù)存、評(píng)價(jià)、推薦商品。后端給了五個(gè)獨(dú)立接口,每個(gè) 50ms,前端串行調(diào)用就是 250ms,并行調(diào)用要處理五個(gè) loading 狀態(tài)。其實(shí)應(yīng)該有一個(gè)聚合接口,或者至少讓 BFF(Backend for Frontend)層來(lái)做編排。
BFF 不是技術(shù)問題,是組織問題。誰(shuí)維護(hù) BFF?前端寫 Node 還是后端寫?接口變更時(shí)誰(shuí)來(lái)同步?這些問題不解決,API 設(shè)計(jì)永遠(yuǎn)停留在"能跑就行"的層面。
那篇摔鍵盤的會(huì)議紀(jì)要最后寫了什么?
不是技術(shù)規(guī)范,是協(xié)作流程:每個(gè)新接口必須前端負(fù)責(zé)人簽字,重大變更提前兩周通知,每月一次"接口債務(wù)"復(fù)盤。三個(gè)月后,他們的 API 投訴工單下降了 60%。
但最諷刺的反饋來(lái)自一位老后端:「我現(xiàn)在設(shè)計(jì)接口會(huì)先想,這個(gè)字段前端怎么用。以前覺得這是浪費(fèi)時(shí)間,現(xiàn)在發(fā)現(xiàn)想清楚了,返工更少。」
REST 本身沒罪,罪的是把它當(dāng)成"數(shù)據(jù)庫(kù) HTTP 化"的偷懶。你的團(tuán)隊(duì)接口文檔最近一次被前端主動(dòng)點(diǎn)贊,是什么時(shí)候?
特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺(tái)“網(wǎng)易號(hào)”用戶上傳并發(fā)布,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.