為了省下 30% 營運成本,我差點搞垮了訂單系統:倖存者偏差如何讓我對「95% 準確」失去信心
同事常說我有點偏執。不是那種「我的方案最牛」的偏執,而是「我永遠假設會出事」的偏執。
10 年做工程,這個習慣救過我好幾次。但這次,它幾乎害死我們。
一場省錢計畫
幾年前,我在一個 B2B2C 外送平台工作。那時我們用 Google Maps API 來判定外送資格,每個月要燒不少錢。
高層看著預算,決定切換到成本只有十分之一的第三方圖資商(我稱它為 Map B)。
我被指派去驗證這個想法行不行得通。
我寫了一個 Node.js 腳本,設計了一個叫做「滾動式驗證」的測試計畫:
每週讓後端團隊匯出過去一整週的真實訂單資料
持續跑了一個月,累積了 2,000 多筆訂單
用 Haversine Formula 計算 Google 和 Map B 的距離誤差
結果顯示:在都會區,95% 的訂單誤差都在可接受範圍內
看起來棒極了。報告給了業務,他們給了高層。高層一拍桌子:「好,切過去。」
我們都信心滿滿。

商業邏輯我沒想清楚
這裡我需要停下來解釋一個細節,因為這個細節決定了災難的嚴重程度。
我們的系統是月費租賃制 (SaaS)。商家每個月付錢來租用我們的接單系統。
地圖 API 的工作很簡單但很關鍵:判定「你在不在我能送的範圍內」。每個門市都有嚴格的設定,比如「3 公里內可送」、「5 公里以上加收費用」。
距離算錯,後果不是一般 App 的 bug。後果是擋人財路。
客人明明在附近卻無法下單。系統誤判導致訂單無法成立。商家看著手機,眼睜睜看著這筆錢沒了。
然後商家會打電話過來,聲音很大:「我每個月付你們幾千塊租金,結果系統誤判讓我少接十張單。這筆損失算誰的?要不要減免月費?要不要賠償?」
這不是客服問題,這是賠償責任。
我當時沒有充分認識到這一點。或者說,我認識到了,但低估了它的重要性。
這是賠償責任。
中文
English
上線後 15 分鐘
上線那天下午 2 點,流量高峰。
15 分鐘後,我開始看 Slack 訊息暴增。
不是「成本節省了」這種好消息。
是「客人說地址判定錯誤」、「為什麼我在他的範圍內卻不能下單」、「怎麼回事」。
營運部主管直接貼了客訴截圖。他的訊息沒有句號。
災難有兩個形狀
第一種:被擋在門外的熟客
「我在 Google Maps 上看離店才 2.8 公里,為什麼 App 跟我說超過 3 公里不能送?」
Map B 對某些新路徑的資料不完整。把距離算成了 3.2 公里。超出門檻。訂單無法成立。
對商家來說,這是煮熟的鴨子飛了。
當天下午,好幾個連鎖品牌直接打電話給業務:「如果系統再亂擋單,下個月我們就不續約了。」
第二種:系統性的封殺
「新竹某個重劃區的訂單全部定位失敗,直接判定無法外送!」
新開發區域。Google 已經有了完整的路網。Map B 卻還是一片空白。
系統的邏輯很簡單:無法計算距離 → 視為不可外送區域。
於是,整個重劃區的訂單被我們系統性地否決了。
賠償要求開始湧進
客訴量在 30 分鐘內暴增了 400%。
這不只是 5% 的技術誤差。這是真金白銀的商業損失。
營運長坐在戰情室,看著賠償預估表。每一行都是一個商家的月費減免申請。
我記得他抬起頭,問:「這個要賠多少?」
沒有人想說那個數字。
那個逃生門
我坐在自己的座位上,鍵盤前面。
我的本能反應不是「趕快修」,而是「等等,我有後路嗎?」
好在我這個人比較悲觀。
早在實作 Map B 的時候,我就在程式碼裡埋了一個 Feature Flag。這不是事後才想的,而是當初設計時就想好的退場機制。
// 一鍵切換的邏輯
const distanceCalculator = config.useMapB
? new MapBService()
: new GoogleMapsService();
const deliveryFee = distanceCalculator.calculate(store, customer);
一個布林值。改它就能瞬間切換整個距離計算引擎。
不需要重新部署。不需要停機時間。毫秒級的切換。
我打開了配置,按下了那個開關。
系統的「距離計算引擎」從 Map B 瞬間切回 Google Maps。
客人又能正常下單了。商家的怒火逐漸平息。營運團隊停止了賠償金額的計算。
從全面爆發到止血,持續時間不到 45 分鐘。

為什麼我測試時沒發現這個問題
事後檢討,我坐在自己的座位上,手裡拿著那份「95% 準確」的報告。
我突然意識到一個很蠢的事情。
我測試的所有訂單都成功完成了。
但那些失敗的訂單呢?
新竹重劃區那些定位失敗的案子。河堤邊地址格式怪異的巷弄。高架橋下的小店面。
這些訂單根本進不了我的測試集。它們在流程的早期就被過濾掉了。
我用「全班前十名的成績」推估「留級生的程度」。
而我沒有意識到這一點。
這就是倖存者偏差 (Survivorship Bias)。
在二戰時,統計學家檢查回來的轟炸機,想知道哪些部分最容易被擊中。他發現機翼被擊中的次數最多。所以他建議加強機翼的防護。
但有人提出了異議:「我們看到的是回來的飛機。那些被擊中機身而沒有回來的飛機呢?」
他們應該加強的,不是機翼,而是機身。
我用的測試集就像回來的轟炸機。我只看到了「成功完成」的訂單。我沒看到那些在定位階段就失敗的訂單。
我的 Node.js 測試腳本跑了一個月,樣本量 2,000 多筆,看起來很專業。
但它根本看不到整個問題。
當時的結局:我們退回了舒適圈
那次災難之後,為了避免賠償爭議擴大,我們全面退回了 Google Maps。
專案被無限期擱置,那份「95% 準確」的報告被存進了硬碟深處。
雖然止了血,保住了商家的月費,但我心裡始終有個疙瘩。
那時候的我,受限於技術視野,只能在「昂貴的精準 (Google)」與「危險的抽樣 (Node.js)」之間二選一。我沒有能力去處理那 100 萬筆歷史資料的複雜度,更沒有機制去即時攔截新竹重劃區那樣的突發災情。
那是我當時身為資深工程師 (Senior) 的極限。
三年後的答案:如果讓現在的我來做?
時間來到今天,AI 與大數據技術已經不可同日而語。
最近我看著這個被封存的案子,心想:「如果用現在的技術棧,能不能解決當年的死結?」
我不只是想寫程式,我想彌補那個遺憾。
於是我開啟了一個新的 Side Project,試圖重建當年的決策系統。但這次,我不再用 Node.js 寫腳本,而是構建了一套完整的**「智慧決策矩陣」**:
全量數據清洗 (Apache Spark)
不再抽樣 2,000 筆。我用 Spark 吞吐百萬級的歷史訂單,直接把那些藏在角落的 5% 錯誤挖掘出來。
即時災情攔截 (Real-time Ingestion)
建立即時回報機制。當新竹重劃區出現第一筆定位失敗時,系統不需要等人去查 log,而是能毫秒級反應。
AI 決策大腦 (LLM Analysis)
我引入了大型語言模型,讓它像一個虛擬營運長。它看的不是冷冰冰的誤差值,而是直接計算:「切換這個區域預計省下 500 元 API 費,但潛在賠償風險高達 5000 元,建議不切換。」
從「寫程式」到「定義系統」

這個 Side Project 對我來說,意義不只是練習技術。
它是一個跨越時空的對話。
當年的我,看到的是「Node.js 跑不動」的效能問題。
現在的我,看到的是如何用 Spark + AI 來解決「商業賠償風險」的架構問題。
我們常問自己工程師的價值在哪?
我想價值不在於你當年有沒有寫出完美的 code,而在於多年後回頭看,你有沒有能力用新的維度,去解決那些曾經讓你無能為力的難題。
(下期待續,第二部曲:拒絕盲測!我如何用 PySpark 與 LLM 重建當年的地圖決策系統。)