iT邦幫忙

2026 iThome 鐵人賽

DAY 9
0
AI Engineering

AI 寫的測試,誰來測?系列 第 9

【Day9】閘門一:替一年前的自己做決定

  • 分享至 

  • xImage
  •  

TL;DR

  • 為了驗一句「這是業界通行寫法」,我拆了六個試算器:四個看原始碼,一個黑箱反解,一個它自己寫在頁面上
  • 查完之後改了一條裁決:同帳戶兩種複利頻率,沒有任何一家這樣做
  • 照新規格算,目標金額漲 6.09%,而 Day 4 那個 (1+r) 只解釋了其中三分之二

https://ithelp.ithome.com.tw/upload/images/20260909/201038266SlAgp7BVS.jpg


前言

昨天攤開了十個沒有人做過決定的地方。今天做決定。

(閘門 1:這系列有五道閘門,都是「AI 產出、人驗收」的交接點。閘門 1 是最前面那道——規格層。它處理的不是程式對不對,是「什麼叫對」還沒有人說過。)

如果現在把那十條丟給 AI 問「每一條該怎麼算」,它會給我十個答案,語氣一致地肯定。再問一次,可能又是另外十個。不是因為它不夠聰明。是因為這些問題本來就沒有標準答案,而它不會告訴你這件事。

先分三堆

十個現場不是同一種東西,混在一起裁會出事。

第一堆:已經表態的,只是還沒寫成文字。

RC-02(折現時點)在 Day 4 就論證完了:期初才對,沒有人能等到年底才吃第一頓飯。這不需要再裁一次,需要的是把它寫得夠精確——精確到年份索引。

第二堆:規則根本缺席,補上就是。

RC-06(大筆支出沒有上界)和 RC-10(輸入防呆),這兩條沒有取捨可言,只有「當初漏了」。95 歲的支出要不要算進 85 歲就結束的規劃?填了倒置的年齡要不要擋?這種問題沒有第二種答案。

第三堆:真的要選邊站。

剩下三個,它們每一個都有兩種以上說得通的做法,而選哪一種會直接改變使用者看到的數字。

(先講結論:其中一個查到一半裂成兩條,所以下面會有四個裁決。)

裁決

歧義 A:r/12 到底改不改(RC-03)

原本我打算把 RC-03(名目月利率)和 RC-04(同帳戶兩種複利頻率)綁在一起裁,理由是「這是業界通行寫法」。寫到這裡我停下來,問自己一句:「我憑什麼說那是通行寫法?」

所以我去打開六個線上試算器,能看原始碼的看原始碼,看不到的就用黑箱反解。結果如下:

試算器 月利率寫法 年金時點 怎麼確認的
中國信託・退休試算 expectedRoi / 12 PVIFA 期末 原始碼
中國信託・投資試算 roi / 100 / 12 FVIFA 期初 原始碼
台北富邦・退休計畫試算 yRate / 1200 期初 原始碼
國泰世華・嚮退 Easy investRatio / 100 / 12 Excel pmtfv 原始碼
國泰世華・試算小幫手 年複利,不是 r/12 期末 ×(1+r)^(1/12) 黑箱反解
YP-Finance・複利計算機 (1+r)^(1/12) − 1 月底投入(期末) 頁面自述

中國信託的程式碼甚至把註解寫在旁邊:

// r=預期投資報酬率/12
r: parseFloat((expectedRoi / 12).toFixed(4)),

富邦更直接,整段計算就三行:

var rate = yRate / 1200;                          // 年利率% ÷ 100 ÷ 12
for (i = 0; i < year * 12; i++) { tRate *= (1 + rate) }
var total = totalAmt / (tRate - 1) / (1 + rate) * rate;

裁決:維持 r/12 六個裡有四個這樣寫,理由從「我的業界認知」變成「我查過」。

但要記兩件事。第一,國泰世華自己的兩個計算機用了兩套不同假設:同一家銀行,一個 r/12、一個年複利;第二,唯一把假設寫在頁面上給使用者看的,是那個獨立部落格做的計算機:

本計算機採用每月複利計算,年利率會轉換為月利率:月利率 = (1+年利率)^(1/12)-1
投資時點假設:採用「月底投入」模式

它用的是我沒選的那個公式,而且它是唯一一個告訴你它選了什麼的。

歧義 A′:兩種複利頻率(RC-04)——這條我改判了

原本要一起維持現況。查完之後改不了了。受測物是本金按「年複利」、月存按「月複利」(r/12),同一個帳戶、同一個報酬率、相鄰兩行程式碼,兩種頻率。

而上面六個試算器裡,沒有一個這樣做。中信和富邦全程 r/12,國泰試算小幫手全程年複利,YP 全程有效月利率。每一家內部都是一致的。所以「維持現況」的理由當場消失了:它不是市場慣例,它就是我們自己的不一致。

裁決:統一為 r/12,本金也改成 (1 + r/12)^(12n)

兩種寫法差多少,算給大家看(基準情境:本金 80 萬、月存 19,391、8%、23 年):

本金部分   現況(年複利)  4,697,171
          統一 r/12      5,006,566   +6.59%
累積期合計 現況          19,991,456
          統一後        20,300,851   +1.55%

r/12 這個方向而不是反過來把月存改成年複利,理由是前者跟 RC-03 的裁決一致。我在這裡刻意讓兩條裁決共用同一把尺,因為我在後記會說明為什麼這件事比每條裁決本身更重要。

歧義 B:盈餘要不要滾存(RC-05)

第 320、363 行的 max(0, 支出 − 收入):某一年收入大於支出時,多出來的錢直接蒸發。

  • (a) 維持截斷,盈餘不滾入本金
  • (b) 允許淨支出為負,盈餘轉入本金並產生報酬

裁決:(a),維持現況。

理由是保守,退休規劃寧可低估晚年資產,也不要高估。選 (b) 會讓那 12 組「目標金額 = 0」的案例變成更樂觀的數字,而那正是我不想給使用者的東西。

歧義 C:勞保要不要照通膨全額調(RC-07)

裁決:維持全額指數化,但在文件層明確標示它與法規的落差。

昨天說過,現實是累計 CPI 達 5% 才階梯式調整。要把階梯模型放進計算核心,得多一組參數和一段狀態機,而使用者拿不到那些輸入。所以這一條我不是選「哪個對」,是選「哪個做得到」。這種裁決最危險,因為它容易被記成技術限制,而忘了它同時也是一個對使用者的承諾。寫進文件是為了讓它不被忘記。

PRD v1.0

三堆處理完,十條全部有了條款。從今天起,PRD 沒寫的行為,程式碼不准自己發揮。

編號 規則 內容 邊界處置
PRD-01 累積期增值 本金與月存統一採名目月利率 r/12,同為月複利 r = 0 時退化為線性累加
PRD-02 目標金額折現 期初年金,第一期對應退休當年 A_r,通膨基準鎖現齡 A_c A_d ≤ A_r 於表單層攔截
PRD-03 提領餘額軌跡 期初扣款,索引與 PRD-02 對齊 禁止 Math.max(0, ...) 截斷真實餘額
PRD-04 輸入校驗 年齡為正整數且 A_c < A_r < A_d;金額非負 禁止靜默補零或整筆丟棄,須回傳明確錯誤
PRD-05 大筆支出區間 A_r ≤ A_e ≤ A_d 計入 超出壽命者兩條路徑皆排除,介面標註已忽略
PRD-06 勞保年金 t ≥ 請領年齡 時計入,隨通膨調增 未達請領年齡為 0
PRD-07 勞退月領 同上 同上
PRD-08 固定年支出 與月支出合併,共用同一組通膨指數
PRD-09 淨支出下限 max(0, 支出 − 收入),盈餘不滾存 保守假設,已知並接受
PRD-10 收入指數化 退休後收入全額隨通膨調增 與法規階梯式調整有落差,已標註

十條裡有四條要求修改現況(PRD-02、03、04、05),六條把現況正式化。

完整版在 spec/PRD-v1.md,裡面附了一張「考古現場 → 條款」的對照表。對照不是一對一,PRD-01 同時承載 RC-03、RC-04、RC-09,而 RC-07 一條散到三個條款。這種多對多就是我後面要做需求追溯矩陣的理由:沒有那張表,Day 30 修完之後沒有人說得出 PRD-01 到底是被誰決定的。

這份 PRD 讓數字變多少

PRD-02 看起來只是把期末改成期初,但它其實動了兩件事:折現指數,以及第一年是哪一年。現況的迴圈從 i = 1 起算,第一筆支出落在 66 歲,總共算 20 年。新規格第一筆落在 65 歲,算到 85 歲,共 21 年。

拿基準情境跑:

現況(期末、66–85,20 年)        9,317,668
只改期初(仍 20 年)              9,690,374   +4.00%
新規格(期初、65–85,21 年)      9,885,351   +6.09%

Day 4 花了一整篇推導那個 (1+r),它解釋的是中間那一列:372,707 元,而多算的那一年又加了 194,977 元。合計 567,684(差額由未取整的值算出,拿上面三個整數相減會差 1 元),其中三分之一來自一個從來沒有人討論過的迴圈起始值。我用一整篇文章推導的數學,只佔偏差的三分之二;剩下三分之一藏在 i = 1 裡,而它連缺陷編號都沒有。

後記

裁決做完,我回頭看自己寫的理由,發現一件不太舒服的事。

  • RC-02 改成期初,理由是「現實就是這樣」
  • 歧義 A 維持 r/12,理由是「別人都這樣算」
  • 歧義 B 維持截斷,理由是「保守比較安全」
  • 歧義 C 維持全額指數化,理由是「階梯做不出來」

四個決定,四種判準。現實正確性、市場相容性、風險保守性、實作可行性,每一個單獨看都站得住,放在一起看就會發現:我是在每一題各自挑一把最順手的尺。

而 RC-02 和歧義 A 這兩把尺是直接互斥的。「沒有人等到年底才吃第一頓飯」用的是現實正確性;(1+r)^(1/12)−1 在同一把尺下也比 r/12 正確,我卻選了市場相容性。同一篇文章裡,我用兩套標準判了兩件同構的事。

我沒有回頭改,因為那兩個裁決我仍然認為結論是對的。但我把矛盾寫在這裡,而不是把理由改得漂亮一點。

真正救了我的是那六個試算器。如果我沒有去查,「這是業界通行寫法」就會成立,RC-04 也會跟著被裁成「維持現況」,用一句我沒驗證過的話,一次過掉兩條規格。

閘門 1 攔的不只是 AI。它也攔我自己那些聽起來很有道理、但從來沒查過的句子。


只帶走一件事
歧義不是 bug。它是還沒有人做的決定,而程式碼已經先替你做了。


今天的 repo:https://github.com/eyelash500/2026_ironman_test_ai/tree/day09


上一篇
【Day8】規格考古:十個沒有人做過決定的地方
下一篇
【Day10】把 PRD 餵給 AI 之前,先把及格線畫好
系列文
AI 寫的測試,誰來測?14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言