可挑剔:從功能完整性到容錯(cuò)降級(jí)的工程實(shí)踐)
1. 從一個(gè)詞出發(fā)為什么impeccable值得單獨(dú)拿出來(lái)聊第一次看到impeccable這個(gè)詞被單獨(dú)拎出來(lái)當(dāng)作一個(gè)項(xiàng)目標(biāo)題我的反應(yīng)是愣了一下。這個(gè)詞在英文里的意思是無(wú)可挑剔的、完美的、零瑕疵的詞根來(lái)自拉丁語(yǔ)impeccabilis其中im-是否定前綴peccare是犯錯(cuò)的意思合起來(lái)就是不會(huì)犯錯(cuò)的。它跟perfect還不太一樣——perfect 強(qiáng)調(diào)的是達(dá)到了最高標(biāo)準(zhǔn)而 impeccable 強(qiáng)調(diào)的是找不到任何可以挑刺的地方是一種從審查者視角出發(fā)的判斷。把這個(gè)詞當(dāng)作一個(gè)項(xiàng)目來(lái)對(duì)待其實(shí)非常有意思。它不是一個(gè)具體的技術(shù)棧不是一個(gè)框架也不是一個(gè)工具庫(kù)而是一種質(zhì)量標(biāo)準(zhǔn)。我做了這么多年項(xiàng)目越來(lái)越覺(jué)得真正拉開(kāi)從業(yè)者差距的往往不是你會(huì)不會(huì)用某個(gè)工具而是你交付的東西能不能經(jīng)得起別人拿著放大鏡看。impeccable 這個(gè)詞恰好就是這種狀態(tài)的精準(zhǔn)概括。所以這篇內(nèi)容我想圍繞如何把一件事做到 impeccable這個(gè)核心命題展開(kāi)。它適合所有對(duì)自己交付物有要求的人——不管你是寫代碼的、做設(shè)計(jì)的、寫文檔的、做手工的還是做任何需要被別人檢驗(yàn)成果的工作。我會(huì)從什么叫真正的無(wú)可挑剔為什么大多數(shù)人做不到具體在哪些環(huán)節(jié)最容易出問(wèn)題怎么建立一套可復(fù)用的自檢機(jī)制這幾個(gè)角度把這件事拆開(kāi)講透。需要提前說(shuō)明的是impeccable 不等于過(guò)度完美主義。這兩者的區(qū)別非常大完美主義是我覺(jué)得還不夠好所以一直改impeccable 是從使用者的角度出發(fā)該覆蓋的場(chǎng)景我都覆蓋了該處理的邊界我都處理了。前者是向內(nèi)耗后者是向外負(fù)責(zé)。搞清楚這個(gè)區(qū)別是理解整篇內(nèi)容的前提。2. 拆解無(wú)可挑剔它到底由哪幾個(gè)維度構(gòu)成2.1 功能完整性主路徑之外的場(chǎng)景才是分水嶺大多數(shù)人做東西只覆蓋主路徑——也就是最理想情況下的使用流程。比如做一個(gè)表單主路徑就是用戶正常填寫、正常提交、正常收到反饋。這條路徑跑通很多人就覺(jué)得完事了。但 impeccable 的要求是主路徑只是及格線。真正決定質(zhì)量的是那些非主路徑場(chǎng)景。我習(xí)慣把它們分成幾類來(lái)逐一檢查空狀態(tài)用戶第一次進(jìn)來(lái)什么數(shù)據(jù)都沒(méi)有的時(shí)候界面/輸出是什么樣是空白一片讓人不知所措還是有清晰的引導(dǎo)異常輸入用戶輸入了超長(zhǎng)文本、特殊字符、負(fù)數(shù)、空值、格式不對(duì)的內(nèi)容系統(tǒng)怎么反應(yīng)是直接崩潰還是給出可理解的提示邊界值數(shù)量剛好為0、剛好為最大值、剛好差一個(gè)單位的時(shí)候邏輯是否還正確并發(fā)與重復(fù)操作用戶快速點(diǎn)了兩次提交、同時(shí)開(kāi)了兩個(gè)窗口操作同一份數(shù)據(jù)會(huì)發(fā)生什么中斷恢復(fù)操作到一半網(wǎng)絡(luò)斷了、頁(yè)面關(guān)了、程序崩了重新進(jìn)來(lái)能不能接上這五類場(chǎng)景我把它叫做隱性需求清單。它們不會(huì)寫在需求文檔里但每一個(gè)真實(shí)用戶遲早都會(huì)撞上。你提前處理了用戶覺(jué)得這東西真穩(wěn)你沒(méi)處理用戶覺(jué)得這東西能用但不敢依賴。impeccable 和能用之間的差距基本就藏在這張清單里。2.2 一致性同一個(gè)東西在不同地方必須長(zhǎng)得一樣、行為一樣一致性是最容易被忽視、又最容易被用戶察覺(jué)的維度。人對(duì)不一致的敏感度遠(yuǎn)超自己想象——按鈕圓角在這個(gè)頁(yè)面是4px、在那個(gè)頁(yè)面是6px用戶說(shuō)不出哪里怪但就是覺(jué)得不精致。一致性至少包含三個(gè)層面視覺(jué)一致性。顏色、間距、字號(hào)、圓角、陰影、圖標(biāo)風(fēng)格必須有一套統(tǒng)一的規(guī)則并且所有地方都遵守。我見(jiàn)過(guò)太多項(xiàng)目做著做著就就地發(fā)揮每個(gè)頁(yè)面都像不同人做的最后拼在一起就是一股廉價(jià)感。交互一致性。同樣的操作在不同地方應(yīng)該有同樣的反饋。比如刪除這個(gè)動(dòng)作如果A頁(yè)面是彈窗確認(rèn)、B頁(yè)面是直接刪、C頁(yè)面是撤銷式刪除用戶就會(huì)困惑。選一種模式然后貫徹到底。語(yǔ)義一致性。同一個(gè)概念全項(xiàng)目用同一個(gè)詞。不要這里叫用戶、那里叫賬戶、另一個(gè)地方叫會(huì)員。術(shù)語(yǔ)混亂會(huì)讓用戶懷疑這倆是不是不同的東西增加認(rèn)知負(fù)擔(dān)。我自己的做法是在項(xiàng)目一開(kāi)始就維護(hù)一份約束清單把所有視覺(jué)和交互規(guī)則寫下來(lái)。每次新增內(nèi)容先對(duì)照清單而不是憑感覺(jué)。這份清單不需要多復(fù)雜一頁(yè)紙就夠但它的存在能讓一致性從靠自覺(jué)變成有據(jù)可依。2.3 可維護(hù)性三個(gè)月后的你還能看懂現(xiàn)在的你impeccable 有一個(gè)時(shí)間維度不只是現(xiàn)在看起來(lái)沒(méi)問(wèn)題而是過(guò)一段時(shí)間回來(lái)還能不能順利接手。這個(gè)維度最考驗(yàn)人。因?yàn)閷懙臅r(shí)候你腦子里有完整上下文覺(jué)得這很明顯啊但三個(gè)月后上下文全丟了再看自己寫的東西就像看天書。判斷可維護(hù)性有個(gè)很簡(jiǎn)單的測(cè)試假設(shè)一個(gè)完全不了解這個(gè)項(xiàng)目的人來(lái)接手他能不能在不問(wèn)你的情況下搞懂每個(gè)部分的意圖要做到這一點(diǎn)核心是把決策的理由留下來(lái)。代碼里為什么用這個(gè)方案而不是那個(gè)、某個(gè)參數(shù)為什么設(shè)成這個(gè)值、某段邏輯為什么繞了一下——這些為什么如果不寫下來(lái)后來(lái)的人包括未來(lái)的你就只能靠猜。而猜錯(cuò)的代價(jià)往往是改壞東西。我習(xí)慣在關(guān)鍵位置留三類注釋一是這段在解決什么問(wèn)題二是為什么不用更直觀的方案三是如果以后要改需要注意什么。這三類信息加起來(lái)能讓接手成本降低一大半。2.4 容錯(cuò)與降級(jí)出問(wèn)題時(shí)的表現(xiàn)才見(jiàn)真章一個(gè)系統(tǒng)在一切正常時(shí)的表現(xiàn)說(shuō)明不了太多。真正體現(xiàn) impeccable 的是它在出問(wèn)題時(shí)的表現(xiàn)。理想狀態(tài)下任何環(huán)節(jié)都可能失敗數(shù)據(jù)源掛了、依賴的服務(wù)超時(shí)了、磁盤滿了、內(nèi)存不夠了。impeccable 的做法不是假設(shè)這些不會(huì)發(fā)生而是假設(shè)它們一定會(huì)發(fā)生然后設(shè)計(jì)好失敗時(shí)的行為。具體來(lái)說(shuō)要回答幾個(gè)問(wèn)題失敗時(shí)是直接報(bào)錯(cuò)崩潰還是優(yōu)雅降級(jí)到某個(gè)可用狀態(tài)錯(cuò)誤信息是給開(kāi)發(fā)者看的堆棧還是給用戶看的可理解提示失敗后能不能重試重試有沒(méi)有次數(shù)限制和退避策略部分失敗時(shí)是整體回滾還是保留已完成的部分這些問(wèn)題在順利的時(shí)候想不起來(lái)在出事的時(shí)候又來(lái)不及想。所以必須在設(shè)計(jì)階段就過(guò)一遍。我的經(jīng)驗(yàn)是把每個(gè)外部依賴都當(dāng)成隨時(shí)會(huì)掛來(lái)對(duì)待然后為每個(gè)依賴想好掛了之后怎么辦。這個(gè)習(xí)慣一旦養(yǎng)成交付物的穩(wěn)定性會(huì)有質(zhì)的提升。3. 為什么大多數(shù)人做不到 impeccable三個(gè)真實(shí)的阻力3.1 阻力一完成比完美更緊迫的現(xiàn)實(shí)壓力說(shuō)實(shí)話絕大多數(shù)項(xiàng)目不是做不到 impeccable而是沒(méi)時(shí)間做到。排期壓著、需求催著、上線時(shí)間定死了能跑通主路徑就已經(jīng)謝天謝地哪還有精力去摳邊界場(chǎng)景。這個(gè)現(xiàn)實(shí)我完全理解也不主張為了完美而無(wú)止境地拖。但我想說(shuō)的是impeccable 不等于無(wú)限投入它更多是一種優(yōu)先級(jí)意識(shí)。你不需要把所有場(chǎng)景都做到極致但你需要知道哪些場(chǎng)景是必須覆蓋的然后把有限的精力花在這些地方。我的做法是給場(chǎng)景分級(jí)P0 是不處理就會(huì)導(dǎo)致數(shù)據(jù)錯(cuò)誤或崩潰的必須做P1 是不處理會(huì)讓用戶困惑的盡量做P2 是錦上添花的有余力再做。這樣即使時(shí)間緊也能保證最關(guān)鍵的部分不掉鏈子。真正的問(wèn)題不是沒(méi)時(shí)間而是很多人根本沒(méi)意識(shí)到還有 P0 場(chǎng)景沒(méi)覆蓋。3.2 阻力二自己測(cè)自己天然存在盲區(qū)第二個(gè)阻力更隱蔽你很難發(fā)現(xiàn)自己寫的東西的問(wèn)題。因?yàn)槟愕哪X子里已經(jīng)預(yù)設(shè)了正確用法測(cè)試的時(shí)候會(huì)不自覺(jué)地按正確用法去操作自然測(cè)不出問(wèn)題。這是認(rèn)知層面的盲區(qū)靠更仔細(xì)一點(diǎn)是解決不了的。破解辦法只有一個(gè)換視角。具體有幾種可操作的方式換人測(cè)找一個(gè)完全沒(méi)參與的人來(lái)用什么都不告訴他看他在哪里卡住。他卡住的地方就是你的盲區(qū)。換心態(tài)測(cè)假裝自己是個(gè)惡意用戶專門輸入奇怪的東西、專門做反常操作看系統(tǒng)扛不扛得住。換時(shí)間測(cè)寫完先放一放隔一天再回來(lái)測(cè)。隔了時(shí)間你就沒(méi)那么熟悉了更容易發(fā)現(xiàn)問(wèn)題。換環(huán)境測(cè)在不同的設(shè)備、不同的網(wǎng)絡(luò)條件、不同的數(shù)據(jù)量下測(cè)。很多問(wèn)題只在特定環(huán)境下才暴露。我自己最常用的是換人測(cè)。每次找同事幫忙試用總能發(fā)現(xiàn)一堆我完全沒(méi)想到的問(wèn)題。這不是因?yàn)槲宜讲疃且驗(yàn)樽髡咭暯呛陀脩粢暯潜緛?lái)就是兩回事。3.3 阻力三沒(méi)有一套固定的自檢流程全靠臨場(chǎng)發(fā)揮第三個(gè)阻力是流程層面的很多人做檢查是想起來(lái)就查一下沒(méi)有固定套路。結(jié)果就是狀態(tài)好的時(shí)候查得細(xì)狀態(tài)差的時(shí)候草草了事質(zhì)量忽高忽低。impeccable 的本質(zhì)是穩(wěn)定輸出高質(zhì)量而不是偶爾超常發(fā)揮。要做到穩(wěn)定就必須把檢查變成流程而不是靠心情。流程的好處是它不依賴你當(dāng)下的狀態(tài)只要照著走就能保證一個(gè)基本的下限。下一節(jié)我會(huì)詳細(xì)講這套流程怎么建。這里先點(diǎn)明一個(gè)原則流程要短、要具體、要能勾選。太長(zhǎng)的清單沒(méi)人會(huì)認(rèn)真執(zhí)行太抽象的條目沒(méi)法判斷到底做沒(méi)做。好的自檢清單應(yīng)該是每一條都能明確回答是/否的。4. 建立一套可復(fù)用的自檢機(jī)制我的實(shí)操流程4.1 交付前的三遍過(guò)法我在每個(gè)東西交付前都會(huì)強(qiáng)制自己過(guò)三遍每一遍的關(guān)注點(diǎn)完全不同。這個(gè)方法我用了很多年效果很穩(wěn)。第一遍功能過(guò)。只關(guān)注該有的功能是不是都有、該走的路是不是都通。這一遍不看細(xì)節(jié)就看整體是否完整。對(duì)照最初的需求清單一項(xiàng)一項(xiàng)打勾。有遺漏的記下來(lái)。第二遍邊界過(guò)。這一遍專門找茬。把前面提到的隱性需求清單拿出來(lái)逐條驗(yàn)證空狀態(tài)什么樣異常輸入怎么處理邊界值對(duì)不對(duì)重復(fù)操作會(huì)怎樣中斷了能不能恢復(fù)這一遍往往能揪出最多問(wèn)題。第三遍體驗(yàn)過(guò)。這一遍把自己當(dāng)成第一次使用的用戶從頭到尾走一遍感受每一步是否順暢、提示是否清楚、反饋是否及時(shí)。這一遍關(guān)注的是感受不是功能。三遍分開(kāi)過(guò)的原因是如果混在一起注意力會(huì)被分散很容易顧此失彼。分開(kāi)之后每一遍都有明確的焦點(diǎn)效率反而更高。4.2 一份可以直接抄的自檢清單下面這份清單是我自己長(zhǎng)期用的覆蓋了大部分場(chǎng)景。你可以根據(jù)自己的領(lǐng)域增刪但建議保留結(jié)構(gòu)。檢查維度具體檢查項(xiàng)判斷標(biāo)準(zhǔn)功能完整性主流程是否跑通無(wú)阻斷性錯(cuò)誤功能完整性需求清單是否全部覆蓋逐項(xiàng)打勾無(wú)遺漏邊界處理空狀態(tài)是否有引導(dǎo)不是空白一片邊界處理異常輸入是否有提示提示可理解、不崩潰邊界處理邊界值是否正確0、最大值、臨界值驗(yàn)證通過(guò)一致性視覺(jué)規(guī)則是否統(tǒng)一對(duì)照約束清單無(wú)偏差一致性交互反饋是否一致同類操作反饋相同一致性術(shù)語(yǔ)是否統(tǒng)一同一概念同一叫法容錯(cuò)性依賴失敗是否有降級(jí)不直接崩潰容錯(cuò)性錯(cuò)誤信息是否友好用戶能看懂可維護(hù)性關(guān)鍵決策是否有注釋說(shuō)明為什么可維護(hù)性接手者能否獨(dú)立理解無(wú)需口頭解釋這份清單不需要每次全過(guò)一遍但至少要在交付前掃一眼確認(rèn)沒(méi)有明顯遺漏。用久了之后很多項(xiàng)會(huì)變成肌肉記憶但清單還是要留著——因?yàn)槿丝倳?huì)累、會(huì)趕、會(huì)疏忽清單是最后一道防線。4.3 把復(fù)盤變成習(xí)慣而不是事故后的補(bǔ)救很多人只在出問(wèn)題之后才復(fù)盤這叫救火式復(fù)盤。impeccable 的做法是主動(dòng)復(fù)盤即使這次沒(méi)出問(wèn)題也定期回顧哪些地方本來(lái)可能出問(wèn)題。我習(xí)慣在每個(gè)階段結(jié)束時(shí)問(wèn)自己三個(gè)問(wèn)題這次有哪些地方是靠運(yùn)氣才沒(méi)出事的如果重來(lái)一次哪個(gè)環(huán)節(jié)我會(huì)做得不一樣這次積累了什么經(jīng)驗(yàn)下次可以直接用第一個(gè)問(wèn)題最關(guān)鍵。因?yàn)榭窟\(yùn)氣沒(méi)出事和靠設(shè)計(jì)沒(méi)出事是兩回事前者是隱患后者才是能力。把靠運(yùn)氣的部分找出來(lái)補(bǔ)上設(shè)計(jì)下次就不用再賭運(yùn)氣了。5. 幾個(gè)容易翻車的細(xì)節(jié)來(lái)自一線的踩坑記錄5.1 提示信息寫得太技術(shù)用戶根本看不懂我見(jiàn)過(guò)太多項(xiàng)目出錯(cuò)時(shí)彈出一句Error: null pointer exception at line 42或者操作失敗錯(cuò)誤碼 500。寫的人覺(jué)得信息很全啊但用戶看到只會(huì)一臉懵。impeccable 的提示信息必須回答三個(gè)問(wèn)題發(fā)生了什么、為什么發(fā)生、用戶能做什么。比如同樣是失敗好的提示是保存失敗可能是網(wǎng)絡(luò)不穩(wěn)定請(qǐng)檢查網(wǎng)絡(luò)后重試。它沒(méi)有暴露任何技術(shù)細(xì)節(jié)但用戶知道該怎么辦。這里有個(gè)原則技術(shù)細(xì)節(jié)寫進(jìn)日志用戶提示寫人話。日志給開(kāi)發(fā)者看可以詳細(xì)提示給用戶看必須通俗。兩者分開(kāi)各司其職。5.2 狀態(tài)管理混亂導(dǎo)致看起來(lái)對(duì)但實(shí)際錯(cuò)狀態(tài)不一致是特別隱蔽的坑。比如界面上顯示已保存但實(shí)際數(shù)據(jù)沒(méi)落庫(kù)或者列表顯示有5條點(diǎn)進(jìn)去只有3條。這種問(wèn)題在測(cè)試時(shí)不容易發(fā)現(xiàn)因?yàn)榭雌饋?lái)是對(duì)的但用戶一旦依賴這個(gè)狀態(tài)做決策就會(huì)出大問(wèn)題。避免這個(gè)坑的核心是單一數(shù)據(jù)源。同一個(gè)狀態(tài)只在一個(gè)地方維護(hù)其他地方都從它派生。不要在兩個(gè)地方各存一份然后靠同步來(lái)保持一致——同步遲早會(huì)漏。5.3 過(guò)度設(shè)計(jì)為了完美引入了不必要的復(fù)雜度這一條是給完美主義者的提醒。impeccable 不是越復(fù)雜越好。我見(jiàn)過(guò)一些項(xiàng)目為了處理一個(gè)幾乎不可能發(fā)生的邊界情況引入了一大堆抽象和配置結(jié)果主流程反而變得難懂、難改。判斷標(biāo)準(zhǔn)很簡(jiǎn)單這個(gè)處理帶來(lái)的收益是否大于它增加的復(fù)雜度如果一個(gè)邊界情況發(fā)生的概率極低且發(fā)生后影響可控那不處理或者簡(jiǎn)單處理可能就是更 impeccable 的選擇。真正的無(wú)可挑剔是恰到好處不是堆砌。5.4 文檔和實(shí)際不符比沒(méi)有文檔更糟文檔寫的是A實(shí)際做的是B這種情況比完全沒(méi)文檔更危險(xiǎn)。因?yàn)闆](méi)文檔時(shí)人會(huì)去讀代碼、去問(wèn)反而能拿到真實(shí)情況有錯(cuò)誤文檔時(shí)人會(huì)信文檔然后被帶偏。所以我的原則是文檔要么不寫要寫就必須和實(shí)際同步。如果沒(méi)精力維護(hù)寧可不寫把關(guān)鍵信息放在代碼注釋里至少離真相更近。6. 把 impeccable 變成一種工作方式而不是一次性的沖刺聊到這里我想強(qiáng)調(diào)一個(gè)觀點(diǎn)impeccable 不是某個(gè)項(xiàng)目的終極目標(biāo)而是一種持續(xù)的工作方式。它體現(xiàn)在每一次交付、每一個(gè)決策、每一個(gè)細(xì)節(jié)里而不是靠某一次突擊就能達(dá)成的。我自己的體會(huì)是做到 impeccable 最難的不是技術(shù)而是心態(tài)上的轉(zhuǎn)變——從做完就行變成做完還要經(jīng)得起看。這個(gè)轉(zhuǎn)變一旦發(fā)生你會(huì)發(fā)現(xiàn)自己對(duì)很多東西的要求都變了寫注釋會(huì)多想一步別人能不能看懂做界面會(huì)多想一步空狀態(tài)長(zhǎng)什么樣設(shè)計(jì)流程會(huì)多想一步出錯(cuò)了怎么辦。這種心態(tài)帶來(lái)的回報(bào)是長(zhǎng)期的。你交付的東西越來(lái)越穩(wěn)別人越來(lái)越信任你你自己也越來(lái)越少被事后救火消耗。短期看你多花了時(shí)間長(zhǎng)期看你省下的返工和溝通成本遠(yuǎn)超當(dāng)初多花的那點(diǎn)精力。最后分享一個(gè)我一直在用的小技巧每次交付前問(wèn)自己一句——如果這個(gè)東西明天被一個(gè)我很尊敬的人拿去用我會(huì)不會(huì)心虛如果答案是會(huì)那就說(shuō)明還有沒(méi)做到位的地方繼續(xù)查。如果答案是不會(huì)那基本就到位了。這個(gè)自問(wèn)自答比任何清單都管用因?yàn)樗苯诱{(diào)動(dòng)了你內(nèi)心對(duì)質(zhì)量的那桿秤。