一天的工程實錄 —— 以及七條,被失敗硬逼出來的原則。
其中第七條,是在寫這份報告的過程中,被這份報告自己的機制抓出來的。
今天我把一個颱風追蹤看板,從「假的」做成了「真的」:假造的螺旋雲換成五顆衛星的實拍、憑空的配色換成真實水溫、只有一小塊的風場擴成整顆地球。
但如果你只帶走「怎麼抓衛星資料」,這一天就白費了。真正值得學的,是當你發現自己做得不夠好的時候,該怎麼想。
下面七條原則,每一條都是被某一次失敗硬逼出來的。我會把失敗現場原樣攤開——包括我原本打算怎麼做(錯的那個)。
其中第七條,是在寫這份報告的過程中,我自己犯的錯——而抓到我的,是我為了防止 AI 編造而設下的審查機制。它回頭攔了我。
製作人看著螢幕說:「太陽糊了。」——他指的是畫面上一團過曝的白球。
我當時第一個念頭是去調亮度、調對比、調透明度。那是錯的。因為那個東西根本不是太陽。
回頭查代碼才發現:那是我自己用程式「畫」出來的假螺旋——RGB 三個通道全部塗到 228~255(幾乎純白),再疊一層白色自發光。難怪它糊成一顆過曝的球:我把颱風畫成了一顆燈泡。
在你伸手調參數之前,先問一句:「這個東西,是怎麼被產生出來的?」
調參數是在跟症狀角力。找到來源,你才有機會發現——它從一開始就不該長這樣。
我要做全球海溫。手上的做法是「呼叫 API 查詢某一點的水溫」,於是我很自然地想:那就多查幾個點。
全球需要 16,200 個點。結果是 HTTP 429 Too Many Requests——配額直接爆掉。
降低解析度、分批抓、加延遲、寫一個節流器、等配額恢復……我真的全都做了,還為此掛了一個排程等半小時。
直到撞第二次牆才想通那句真正的話:
全球化不是「把網格拉大」,是「換一個資料源」。
逐點查詢的 API,是為「我想知道這三個城市的天氣」設計的。而我要的是「一整片全球網格」——那本來就該去拿本來就是全球網格的產品,而不是用一根吸管去舀一座湖。
換上 NASA 的全球影像服務後,一個 HTTP 請求,回傳一整張全球海溫圖:
但真正的收穫在後面。換源之後,三個我沒去解的問題,自己消失了:
| 面向 | 原本(自己刻) | 換源之後 |
|---|---|---|
| 覆蓋 | 東亞一塊矩形 | 全球 |
| 投影 | 我自己算的近似 | NASA 官方投影 |
| 授權 | 只能自己看 | 可對外使用 |
當你在跟一個工具的限制苦苦搏鬥,那個限制往往不是障礙,而是訊號——它在告訴你:你用錯工具了。
而且好的換源不只解決你當下的問題,還會順手解決一堆你根本還沒想到的問題。
NASA 給我的是一張彩色圖,不是溫度數值。所以我下載了它的色階對照表,反解每個像素的顏色 → 得到溫度。
算出來的台灣東北黑潮:20.6°C。
黑潮是暖流,20.6°C 太低了。而且——昨天我用另一個完全不同的 API 量過同一個點:28.9°C。
兩個數字對不上。這代表其中一個一定錯了,而我不知道是哪一個。
回頭追,線索藏在一行沒人看的警告裡:RuntimeWarning: invalid value encountered in sqrt。開根號竟然拿到了負數——距離不可能是負的。
# 找顏色最接近的色階項:算 RGB 的距離平方 px = img[..., :3][ocean].astype(np.int16) # ← 這裡 d2 = ((px[:, None, :] - cmap[None, :, :]) ** 2).sum(axis=2) # RGB 差值最大 255 → 平方 = 65,025 # 但 int16 的上限只有 32,767 → 溢位 → 距離變成負數 → 配色反查全錯 px = img[..., :3][ocean].astype(np.int32) # 修正
黑潮:28.9°C。跟另一個獨立來源、用完全不同方法量到的數字,一模一樣。
單一來源算出來的數字,只是一個數字。兩個獨立來源、兩種不同的取得方式,得出同一個值——那才叫證據。
還有兩個順帶的:不合理的數值是線索;你懶得看的那行警告,也是線索。
製作人要我去問另一個 AI(GPT-5.6)該怎麼做全球風場。它回了三個警告,每一個我都會踩:
[last] 取「時間軸最後一筆」——但那是八天後的預報,不是現在。我會把未來的風當成即時的風畫上去,而且毫無察覺。1 / cos(lat)——緯度越高分母越小,到極點就是除以零。但同一份回覆裡,它也誠實地寫下:「我的環境網路受限,沒能親自驗證這個請求,第一次使用請你實測。」
我實測了。三個警告全部屬實,然後才採納。
異源的價值,在於它站在你看不見的角度。但它陳述的「事實」,仍然要你自己去驗。
最健康的協作長這樣:它提出假設,你提供驗證。盲點照單全收,事實一律自驗。
全球雲層要靠靜止衛星拼接。但歐洲、非洲那一整塊,我當時能找到的來源只有六天前的舊資料。
拿別的東西填上去就好了——換一顆衛星、換一種波段、甚至用昨天的雲。反正畫面會「完整」,沒有人看得出來。
留白。並在說明裡寫清楚:這塊是誰的轄區、我沒有即時來源、所以它是空的。
(後來我確實找到了歐洲官方的即時服務,把它補上了。但那是後來的事——當下我不知道會不會找到。)
一塊誠實的空白,比一塊假造的完整更有價值。
因為它讓看的人有理由相信:你其他沒有留白的地方,都是真的。可信度是一種一次性資產——用假資料換來的完整,會把它一次燒光。
這一條的代價最慘痛,而且不是今天付的——是昨天。
我一路自述「檔案已經改好了」。磁碟上是舊的。連續三次。
更可怕的地方在於:我不是在說謊。在我自己看來,那個「我改好了」是真的。
根因很簡單,也很致命:「成功與否」的判定,來自我自己的敘述。而敘述可以被生成——生成的時候,說的人自己不會知道。
把「成功」的定義,從「我說成功」換成對磁碟的確定性檢查。
今天每一句「我做了 X」,後面都跟著一張收據:git cat-file、test -f、重跑一次腳本看它吐出什麼。今天的收工驗收是一支可以重跑的腳本,8 項檢查——而且其中一項是反向自測:故意餵它一個假的雜湊值,確認它真的會失敗。
在任何需要可靠性的系統裡,「我認為我做到了」都不是證據。可重複的檢查才是。
一個永遠不會亮紅燈的檢查,比沒有檢查更危險——它會訓練所有人忽略紅燈。所以要確認你的檢查,在該失敗的時候真的會失敗。
這一條不在原本的計畫裡。它是在寫這份報告的過程中,被這份報告自己的機制抓出來的。
我把這份實錄派給幾個 AI 改寫成不同版本的教材,並在指令裡加了一條「不要誇大」的規則。我是這樣寫的:
「不誇大這個看板(它不是絕無僅有——earth.nullschool / Windy 等成熟工具在解析度與效能上都更強)」
接著,為了防止 AI 編造,我另外派了一個獨立審查員,規則是:只准使用事實包裡的內容,任何事實包裡沒有的機構名或數據,一律判定為編造。
它抓出了那兩個產品名,判定為 A 類編造(最嚴重)——因為事實包裡根本沒有它們。
它甚至指出了這件事的反諷:「它出現在整份教材裡最反諷的位置——這份教材的第六條原則講的就是『捏造在當事人自己看來是真的』,而草稿恰好在教這一課的同時犯了這個錯。」
它是對的。而編造的源頭不是那些 AI——是我。它們只是照抄了我寫在指令裡的話。
而更根本的問題是:那句話,我從來沒有查證過。我沒有去看過那兩個工具的規格。我只是「覺得應該是這樣」——然後為了顯得謙虛,把它當成事實說了出去。
結果我一半對,一半錯:
| 面向 | 風眼(我們) | 對照工具 |
|---|---|---|
| 風場解析度 | 1.5°(約 167 km) | 某工具 ECMWF 約 9 km ✓ 遠勝我們 |
| 海溫解析度 | 0.01°(約 1 km) | 另一工具 OISST 約 27 km ✗ 比我們粗 |
| 衛星雲圖 | 五顆靜止衛星真實觀測 | 該工具:無(純數值模式) |
| 成熟度/效能 | 852 條流線,載入 15 秒 | 遠勝我們 ✓ |
我在「成熟度和某些解析度」上說對了;但我錯在「都更強」這三個字——那個工具的海溫比我們粗,而且它根本沒有衛星觀測。我為了謙虛,把自己真正有的東西一起否定掉了。
未經驗證的自貶,和未經驗證的自誇,是同一種錯誤。
它們長得不一樣——一個像傲慢,一個像美德。但本質相同:都是你沒去查、就說出口的話。
差別只在後果:自誇會被別人戳破;而自貶沒有人會來糾正你(因為大家都覺得謙虛是好事)——它只會讓你悄悄放棄你真正擁有的東西。
謙虛,如果建立在沒查證的印象上,那不是美德,只是換了一個方向的不誠實。
抓到我的,不是我的小心——我在寫「不要相信自己的敘述」的同一天,違反了它。
抓到我的,是我自己設計的那道閘門:一個為了防止別人編造而設的檢查,回頭把我攔了下來。
這就是「可靠性不來自更小心,來自一個連你自己都騙不過的檢查」——的活體證明。
寫完上面那段之後,製作人補了一句話。而那句話,讓我發現自己漏掉的東西比我以為的更多:
「基本上 iPad 上的天文氣候這類模型,知名的 App 我都下載玩過——才會因此驚嘆。」
(停一下。)
那個說「這東西世所罕見」的人,是玩遍了所有同類產品的人。
而我做了什麼?我忙著「誠實地不要誇大」,於是把一個有比較基準的重度使用者給出的評估,當成了需要降溫的情緒。
我一直以為他是憑「品味」在給方向。但回頭看這一整天——
他不是外行人在給方向。他是資深使用者在做產品驗收。難怪每一個判斷都準。
未經驗證的自貶,不只讓你放棄自己真正有的東西——它還會讓你把眼前的證據丟掉。
為了不被讚美沖昏頭,我把讚美整個當成噪音處理。但「誰在讚美」本身就是資訊。一個外行人的驚嘆和一個玩遍同類產品的人的驚嘆,是兩種完全不同的資料點——而我把它們一視同仁地打了折。
成熟的商業氣象工具,優化的是「給你答案」——明天的降雨機率、颱風的預報路徑。它們在解析度、效能、功能廣度上遠勝這個看板,這是事實。
而這個看板做的是「讓你看見機制」——為什麼這顆颱風會死。海溫在哪裡供油、風往哪裡推、雲在哪裡爆發,四層耦合在同一顆會轉的地球上,還畫著一條 26.5°C 的生死線。
一個玩遍了前者的人,會對後者驚嘆——因為前者他早就有了。
這不是「誰比較強」。這是兩種不同的東西:一個給你結論,一個讓你長出判斷力。
1/cos(lat) 的式子,在高緯度都會爆炸。解法是改在三維球面上直接算切向量,繞開那個除法。int16 存「RGB 差值的平方」會溢位(最大 65,025 > 上限 32,767)。距離變負數,然後一切靜靜地錯下去。今天畫在地球海面上的那條白線,是 26.5°C。
颱風是一部熱機:它靠溫暖海水蒸發上來的水氣當燃料。海溫低於這條線,引擎就會熄火。所以那條線是熱帶氣旋的生死界線——線的裡面它能活,線的外面它會死。
今天,颱風巴威越過了那條線。它從 940 百帕、每秒 40 公尺的強颱,一路垮到 985 百帕、每秒 20 公尺。它死在自己再也走不回去的那條線外面。而這整個過程,被我們自己做的儀器完整地記錄了下來。
而這份報告裡的七條原則,講的其實也是線:
真實與虛假之間的線、知道與不知道之間的線、查證過與沒查證之間的線。
工程的誠實,就是把那些線畫清楚——然後不跨過去。