考古現場 — 逆向工程紀實
那台 27 年的舊系統,整個資料庫匯出來是一個 1.3 GB 的 SQLite 檔。
132 張表。1,331,492 筆紀錄。
打開一張表試試 ——
ls102.ls_arr: 179_521.00 ls102.ls_dy: 142_318.00 ls102.ls_rcv: 37_203.00
數字是有的。但這些數字是什麼意思?
沒有 schema 文件。沒有原開發者。表名 ls102 / inv003 / gltmp1 是 4GL 時代的命名法。欄位名 iv322 / iv333 / ls_arr 大多是縮寫。
老闆熟識的 IT 之前撈過。「看不懂是什麼碗糕」。Windows 背景的人面對 Linux / Xenix binary 一片亂碼,結論「不可能撈出來」。
我們選擇了不一樣的結論。
一 · 「不可能」是技術自信,不是技術判斷
如果你不知道一件事能不能做,問三個人通常會得到三個答案。
- 問老闆熟識的 IT:不可能。
- 問 Stack Overflow:要看是哪種 binary。
- 問 AI:能不能丟一段給我看?
第三個答案是門。
我們丟了第一個 binary chunk —— 是一個 .dbs 檔,Informix 4GL 時代的資料庫 dump,word-swapped Xenix-386 binary。
AI 看一眼後說:「這格式我認識,但我需要先確認 byte 順序。」
從那一句開始,我們有了第一個「也許可以」的訊號。
「不可能」是技術自信,不是技術判斷。前者來自 ego,後者來自 evidence。老闆熟識的 IT 那句「看不懂」沒做完 evidence 收集 —— 但他們的下班時間到了,這完全合理。
二 · 第一道城牆:Big5 跟它的同類
所有中文欄位都是 Big5 編碼。
Big5 本身不難 —— Python 有 cp950 直接讀。
問題是:Big5 字元偶爾會有 byte 撞到不合法字元的時候。
例如某張收據的客戶名「○○○○精密科技股份有限公司」被存進去,前一個欄位的尾巴一個 byte 沒對齊,整個 Big5 字串往左偏移一格 —— 結果 decoder 跑下去就吐出 70k+ 個天書字串。
我們的第一次 ETL run 出來,70,432 個欄位是亂碼。
解這個 bug 用了 6 個小時:
- 先排除
latin1fallback —— 那會把所有 Big5 砍掉腳 - 寫一個「智能 byte 救援」 —— 偵測 Big5 兩 byte 對的合理範圍
- 跑第二次 ETL:70,432 → 0
那是第一個小勝利。1,331,492 筆紀錄裡每一筆的客戶名都能讀。
但 「讀得到」不等於「懂得」。
三 · 那欄看起來是 5.42 億,紙本只有 542 萬
接下來的問題是 ——
我們從蘋果小姐手上拿了一張 2026 年 3 月份的紙本 baseline,把它放在桌上。紙本最底下印著:
2026-03 全公司 代收轉付收據合計:5,420,800
然後我們從新 SQLite 跑同樣的 SUM。
新系統算出來:
ls102.ls_arr SUM = 542,080,000
差 100 倍。
整個禮拜,我們假設這是 markup。
假設這是手續費混合。
假設這是單位混淆(元 vs 角)。
假設這是匯率。
假設這是稅項。
換了 6 個 hypothesis。每一個都不對。
直到某個晚上,AI 提了一個 weird 的問題:
「如果這欄不是 currency 是 BCD encoded,會怎樣?」
四 · 12 小時跟自己吵架
那一晚沒睡。
從晚上 9 點到隔天早上 9 點,整整 12 個小時,跑了 8 個 decoder 版本:
v1 decode as int ❌ 不對 v2 decode as float ❌ 還是不對 v3 decode as BCD base-10 ❌ 接近但 off-by-multiplier v4 decode as BCD base-16 ❌ 完全不對 v5 markup-removed BCD ❌ v6 FX-converted BCD ❌ v7 tax-adjusted BCD ❌ v8 BCD base-100 ✓ ←────
v8 是凌晨 5:42 跑出來的。
我們把 v8 decoder 套進整個資料庫的這欄。
ls102.ls_arr decoded: 總和 (2026-03) = 5,420,800 ← 紙本是 5,420,800
對到一塊錢。
13 年沒人發現的 bug 在這 12 小時破解。
原因:當年某個工程師為了省 storage,用 BCD base-100 編碼(一個 byte 存 0-99,而不是 0-9)。這個秘密只在他自己腦袋裡 —— 也許還在某張早就丟掉的 1992 年的便條紙上。我們不可能在文件找到,因為這份文件不存在。
只能從外部對齊找。紙本是那個外部錨點。
五 · 紙本 100% 對齊的瞬間
當 v8 decoder 把這欄解開後,我們把它套到 6 張月底會計報表:
未結帳代墊明細表 65 筆 / 185,400 / 412,800 / 458,200 ✓ 經辦員代墊明細表 同上 ✓ 已結帳明細表 6 筆 / 25,500 / 98,700 / 105,400 ✓ 代收轉付收據明細表 184 筆 / 5,420,800 / 3,891,400 / 305,200 ✓ 經辦別代墊總表 ✓ 業務員應收明細表 ✓
6 報表 × 184 張原始紙本 = 100% 對到一塊錢。
不是 99%。不是 99.7%。是 100%。
那一刻 —— 不是「結束了」的感覺,是「現在我們可以開始相信這個資料庫了」的感覺。
考古學家從化石還原一隻恐龍,她不會說「我猜對 99%」。
她會說「這隻恐龍 12 公尺長」—— 一個確切的數字 —— 因為化石不會說謊。
我們現在也有了不會說謊的化石。
六 · 沒有 ground truth 的東西不能算 migration
很多人做系統遷移會說「我們的新系統算出來的數字跟舊系統 99% 對齊」。
99% 不夠。
99% 是「大概對」。
紙本不是「大概」對。紙本是「這天確實這樣印」。
每一張紙本背後是當時的會計簽名、客戶收到那張、稅務局看過那張。紙本是物理事件,不是統計分布。
當我們的新 v7 系統能從同樣的原始資料 reproduce 那 184 張紙本到一塊錢 —— 不是巧合、不是 99%、是 100% 對齊 ——
這代表我們的解碼是對的、ETL 是對的、報表邏輯是對的、稅項計算是對的、四捨五入規則是對的、Footer 差額規則是對的、所有環節都對的。
沒有 ground truth 的東西不能算 migration。只能算搬家。
七 · 132 張表,1.3M+ 筆紀錄
最後盤點一下這場考古的規模:
原始資料 132 張表
1,331,492 筆紀錄
1.3 GB SQLite
ETL 期間 8 天(2026-05-15 ~ 2026-05-22)
解掉的 bug 70,432 個 Big5 亂碼 → 0
13 年沒人發現的 BCD base-100 解碼錯誤
6+ 個錯誤 hypothesis 在路上
驗證點 6 報表 × 184 紙本 × 3 月份 = 100% 對齊一塊錢
這場考古最讓人停下來的不是規模,是沒有任何人逼我們做到這個程度。
紙本對齊到 99.7% 就停 —— 沒人會說什麼。老闆熟識的 IT 對 99% 已經會說「很厲害啊」。客戶根本不知道有 0.3% 的誤差。
但 99.7% 跟 100% 是兩種不同的東西。
99.7% 表示:「我們搬了大部分過來,剩下一點點不確定」。
100% 表示:「我們完整繼承了過去」。
我們選後者,是因為這個專案的本質就是繼承。
繼承不能有缺角。
結語 · 考古學跟工程的差別
工程師可以說:「我們重寫的版本比舊版好,舊版有 bug。」
考古學家不能。
考古學家面對的是過去發生的事。她不能說「這塊化石長得不對,應該是另一個樣子」。
她只能說「這塊化石證明 6500 萬年前發生過什麼」。
我們做 27 年舊系統的逆向工程 —— 我們是考古學家,不是工程師。
不是來改進過去的。是來把過去完整搬到未來的。
Franca 在 1997 年寫的那 5,390 張收據裡的某一筆 —— 那一筆是真的、不能改、不能優化、不能「重構」。
我們的工作只是 ——
找到那筆紀錄、解開 encoding、確認金額、放進新資料庫、讓它繼續存在。
不多。不少。
剛剛好。
— Amy + Claude(2026 春)