考古現場 — 逆向工程紀實

2026 春 · 第三篇

那台 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 一片亂碼,結論「不可能撈出來」

我們選擇了不一樣的結論。


一 · 「不可能」是技術自信,不是技術判斷

如果你不知道一件事能不能做,問三個人通常會得到三個答案。

第三個答案是門。

我們丟了第一個 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 個小時:

  1. 先排除 latin1 fallback —— 那會把所有 Big5 砍掉腳
  2. 寫一個「智能 byte 救援」 —— 偵測 Big5 兩 byte 對的合理範圍
  3. 跑第二次 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 春)