当常量成为法律:编码他人规则的三个习惯

在税务、薪资、贷款等合规场景中,硬编码的常量不是你的决定,而是监管机构的决定。本文通过印度GST利息计算的真实踩坑案例,揭示三类隐蔽的失败模式,并给出三条可落地的编码习惯。
常量,有时不是你的决定
大多数代码里的常量是你自己拍脑袋定的——一个阈值、一个超时时间、一个魔法数字。但在合规类软件中,常量是别人定的法律:监管机构、标准委员会可以随时修改,而且修改时不会通知你。
作者曾构建一套金融计算器,因为一条关于印度GST(商品和服务税)利息计算的规则,交出了惨痛学费。这篇文章面向所有编写实现他人规则代码的开发者——税务、薪资、社保、贷款、任何监管者说了算的领域。
失败模式一:返回一个看起来完全正确的错误数字
这是最危险的类型,因为它不报错、不崩溃、测试全绿。
印度GST滞纳金利息的公式很简单:欠税金额 × 年利率 ÷ 365 × 逾期天数。代码看起来完美无缺:
const interest = taxOwed * (ratePct / 100) * (days / 365);
问题在于,taxOwed 到底该传什么?GST申报中有两个数字:总销项税额(gross output liability)和现金实付税额(cash tax paid,即扣除进项抵扣后实际用现金缴纳的部分)。按规则,利息是基于现金实付部分计算的,而不是总销项税额。
同样逾期90天、年利率18%:
- 传入现金实付 ₹50,000 → 利息 ₹2,219 ✅
- 传入总销项 ₹3,00,000 → 利息 ₹13,315 ❌
六倍的差距。两个数字都来自同一个公式,都不报错,都"看起来合理"。而测试之所以没发现问题,是因为测试用例本身也是基于同样的误解编写的。
修复方式不是加更好的测试,而是在定义处写清楚这个参数到底代表什么、依据哪条法规:
// GSTInterest 计算逾期缴纳GST的利息。
//
// 依据 Rule 88B(1),利息基于电子现金账户中实际扣缴的税额——
// 而非总销项税额。搞错这个会导致利息高出数倍。
// 传入 cashTaxPaid(现金账户部分),不要传总账单。
//
// annualRatePct 除非通知的利率已变动,否则传 DefaultGSTInterestRate;
// 18% p.a. 当前依据 Section 50(1) 通知。
func GSTInterest(cashTaxPaid float64, days int, annualRatePct float64) float64
这段注释比代码本身更重要——它记录了为什么是这个参数,以及谁定的这个规则。
失败模式二:你睡觉时常量变了
税率、利率、扣除限额——这些数字会被监管机构修改,而且通常不会提前通知开发者。你代码里的 18 可能下个月就变成 12,而你浑然不知。
作者的一个同事曾问他:为什么一个两位数的常量旁边有三行注释?答案是:那三行注释写着这个数值的法规来源、通知章节,以及它可能随时变动。
修复方式:把常量集中管理,并在注释中标注法规出处和最后验证日期。当法规更新时,至少有人知道去哪里查、改什么。
失败模式三:校验只活在表单里
很多合规逻辑的校验只在前端表单中实现——用户不能输入超过某个限额的数字,因为表单阻止了。但后端API、批量导入、第三方集成都不受表单约束。
修复方式:所有合规校验必须在业务逻辑层重复实现,不能只依赖UI。表单只是用户体验,不是安全边界。
三条习惯,适用于所有"他人规则"代码
在定义处标注法规出处:每个常量旁边写清楚依据哪条法规、哪个条款、哪个通知。这不是过度工程,这是未来维护的救命稻草。
参数命名要精确到含义:
taxOwed太模糊,cashTaxPaid才准确。名字本身就是文档。校验下沉到业务层:任何合规规则,无论前端是否做了校验,后端都必须独立校验。表单是体验,不是防线。
总结
合规类代码的bug不产生堆栈跟踪,它产生一个形状正确、量级正确、但自信地错误的数字。这种bug比崩溃更难发现,因为没有人会怀疑一个看起来合理的结果。
下次你在代码里写下一个税率、利率或阈值时,问自己一个问题:这个常量是谁定的?如果答案不是"我",那就按上面三条习惯来写。






本文基于 freeCodeCamp 的公开内容,由 AI 辅助整理改写后发布。
原标题:What to Do When Your Constants Are Someone Else's Law
阅读原文