test(ai-review): 補使用量統計 NaN 安全測試並排除微優化誤報 #46
@@ -211,24 +211,33 @@ function round1(n) {
|
||||
|
||||
const RATE_KIND_LABEL = { tokens: 'token', requests: '次數' };
|
||||
|
||||
|
admin marked this conversation as resolved
|
||||
/** 是否為有限正數(排除 0、負數、NaN、Infinity),避免算出 Infinity%/NaN%/負百分比。 */
|
||||
function isFinitePositive(n) {
|
||||
|
admin marked this conversation as resolved
Outdated
admin
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Assassin
**問題**:在 `isFinitePositive` 函數中,使用 `Number(n)` 進行強制轉型。若 `n` 為物件(例如 `{}` 或 `[]`),`Number()` 的行為在某些邊緣情況下可能會產生非預期的數字,雖然當前邏輯有做 `Number.isFinite` 檢查,但對於這種隱式轉型仍需保持警惕,且函數名稱 `isFinitePositive` 雖然清楚,但在這個上下文中,函式內部使用了 `Number(n)` 強制轉型,這在 JavaScript 中可能會隱蔽掉原本資料型態的不一致。
**建議**:建議在 `isFinitePositive` 內部補上對 `null` 或 `undefined` 的顯式檢查,並直接在外部嚴格限制輸入類型或使用更明確的檢查(例如 `typeof n === 'number'`)取代隱式轉型,避免隱式轉型帶來的不可控副作用。
|
||||
return Number.isFinite(Number(n)) && Number(n) > 0;
|
||||
|
admin marked this conversation as resolved
Outdated
admin
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Maya
**問題**:新增的 `isFinitePositive` 函數雖然確保了大於 0,但當 `n` 是類似 "100" 的數字字串時,`Number(n)` 可以正常運作,然而如果是 `null` 或 `undefined`,`Number(n)` 分別會變成 `0` 和 `NaN`。雖然在目前的使用邏輯下(`limit != null`)似乎安全,但這個檢查函數本身沒有對 `null`/`undefined` 做明確的防禦性處理,容易在未來被錯誤複用。
**建議**:建議在 `isFinitePositive` 內部補上對 `null` 或 `undefined` 的顯式檢查,或是增加對輸入型別的限制,確保該函數作為基礎邏輯檢查更健壯。
|
||||
}
|
||||
|
||||
/**
|
||||
* 計算「剩餘可用百分比」,依優先序擇一:
|
||||
* 1. 帳號額度(quota 有上限)→ 剩餘 credits / 上限;
|
||||
* 2. 速率配額(rate limit header)→ 當前視窗剩餘 / 上限;
|
||||
* 皆無法取得時回傳 { percent: null, reason }。
|
||||
* 1. 帳號額度(quota 有有限正數上限)→ 剩餘 credits / 上限;
|
||||
* 2. 速率配額(rate limit header,有限正數上限)→ 當前視窗剩餘 / 上限;
|
||||
|
admin marked this conversation as resolved
Outdated
admin
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Leo
**問題**:resolveRemainingPercent 函式同時處理了 quota 和 rate 的邏輯,導致檢查 limit 和 remaining 的計算與驗證邏輯在兩處重複,未來若要調整百分比算法,必須兩處同步修改,增加維護負擔。
**建議**:建議將百分比計算邏輯抽象為獨立的輔助函式(如 calculatePercent(remaining, limit)),讓 resolveRemainingPercent 僅負責選擇計算基準,降低重複性並提升封裝度。
|
||||
* 上限或剩餘為 0/負數/NaN/Infinity 等無效值時不計算,落到 { percent: null, reason }。
|
||||
*/
|
||||
export function resolveRemainingPercent(quota, rate) {
|
||||
|
admin marked this conversation as resolved
Outdated
admin
commented
嚴重等級:🔵 建議 **嚴重等級**:🔵 建議
**審查員**:Bard
**問題**:註解中提到「落到 { percent: null, reason }」,但程式碼實際邏輯是在 if 判斷式內直接 return,且 `resolveRemainingPercent` 函式中檢查分散在兩個 if 區塊,產生了重複的邏輯結構。
**建議**:建議將註解簡化為:「上限或剩餘為無效值時跳過計算」,並將計算百分比的邏輯抽離成一個獨立的輔助函式,統一處理 `isFinite` 的檢查與百分比運算。
|
||||
if (quota?.available && quota.limit != null && Number(quota.limit) > 0) {
|
||||
if (quota?.available && quota.limit != null && isFinitePositive(quota.limit)) {
|
||||
const limit = Number(quota.limit);
|
||||
const remaining = quota.remaining == null ? limit - num(quota.used) : Number(quota.remaining);
|
||||
if (Number.isFinite(remaining)) {
|
||||
return { percent: round1((remaining / limit) * 100), basis: '帳號額度', remaining, limit, unit: quota.currency || '' };
|
||||
}
|
||||
|
admin marked this conversation as resolved
Outdated
admin
commented
嚴重等級:🔴 嚴重 **嚴重等級**:🔴 嚴重
**審查員**:Mage
**問題**:在計算 `remaining` 時,`remaining = Number(rate.remaining);` 之後直接檢查 `Number.isFinite(remaining)`,但若 `rate.remaining` 是 `undefined` 或 `null`,`Number()` 會轉為 `0`。這意味著如果 `rate.remaining` 缺失,會被錯誤地視為「剩餘 0」而不是「無效值」,進而導致計算出 `0%` 的錯誤結果,而非預期的落到 `{ percent: null }`。
**建議**:應先檢查 `rate.remaining` 是否為 null/undefined,或者使用更嚴格的轉換方式,確保只有在確實是有限數字時才進行後續運算。
|
||||
if (rate?.hasData && Number(rate.limit) > 0) {
|
||||
}
|
||||
if (rate?.hasData && isFinitePositive(rate.limit)) {
|
||||
const limit = Number(rate.limit);
|
||||
const remaining = Number(rate.remaining);
|
||||
if (Number.isFinite(remaining)) {
|
||||
const kindLabel = RATE_KIND_LABEL[rate.kind] || rate.kind;
|
||||
return { percent: round1((remaining / limit) * 100), basis: `速率配額(當前視窗,${kindLabel})`, remaining, limit, unit: '' };
|
||||
}
|
||||
}
|
||||
let reason;
|
||||
if (quota?.available && quota.limit == null) reason = '帳號額度無上限,無法計算百分比';
|
||||
else if (quota && !quota.available) reason = quota.reason || '平台未提供額度';
|
||||
|
||||
嚴重等級:🟡 警告
審查員:Rogue
問題:在函式內部重複呼叫
Number(n),造成不必要的型別轉換開銷,且在條件式之後又呼叫一次Number(quota.limit),造成重複轉換。建議:建議將
Number(n)的結果暫存起來,或在進入條件式後立即將值賦值給一個變數並重複使用,減少重複轉換的成本。