getWorkingMonth() 方法在首次调用时返回正确值,但第二次调用(如断言中)返回 0,根本原因是该方法直接修改了实例字段 currentSaving,导致后续调用基于已被清零或耗尽的状态重新计算。
`getworkingmonth()` 方法在首次调用时返回正确值,但第二次调用(如断言中)返回 0,根本原因是该方法直接修改了实例字段 `currentsaving`,导致后续调用基于已被清零或耗尽的状态重新计算。
在养老金规划器(PensionPlanner)这类金融计算场景中,查询类方法(如 getWorkingMonth())应具备幂等性与无副作用特性——即多次调用应返回相同结果,且不改变对象的可观察状态。而原实现中:
public int getWorkingMonth() {
double Saving = monthlySaving * (annualSalary / 12);
int months = 0;
while (currentSaving < pensionTarget) {
currentSaving += Saving + (currentSaving * (interestRate / 12)); // ⚠️ 修改了 this.currentSaving!
months++;
}
return months;
}每次调用都会持续累加并永久更新 currentSaving 字段。第一次 System.out.println(...) 调用后,currentSaving 已增长至 ≥ pensionTarget;第二次调用(断言中)进入 while 循环前条件 currentSaving < pensionTarget 即为 false,直接跳过循环,返回 months = 0。
✅ 正确做法是:使用局部变量隔离计算过程,保护原始状态:
public int getWorkingMonth() {
double monthlyContribution = monthlySaving * (annualSalary / 12);
double curr = currentSaving; // ✅ 复制初始值,不触碰字段
int months = 0;
while (curr < pensionTarget) {
curr += monthlyContribution + (curr * (interestRate / 12));
months++;
}
return months;
}? 关键改进点:
- 用 curr 替代 currentSaving 参与迭代计算;
- 原始字段 currentSaving 在整个过程中保持不变,保障方法幂等性;
- 变量命名更清晰(如 monthlyContribution 比 Saving 更具语义)。
⚠️ 额外注意事项:
- Java 中 assert 默认是禁用的(需运行时添加 -ea 参数),若未启用,断言不会执行——请确认测试环境已启用断言;
- 若业务逻辑本身需要“真实推进储蓄状态”,应显式提供 simulateOneMonth() 或 advanceToRetirement() 等有副作用的方法,而非隐藏在 getter 中;
- 对于高精度金融计算,建议后续考虑使用 BigDecimal 避免浮点误差累积(本例中 double 在月复利迭代下可能引入微小偏差,虽不影响 368 这一整数结果,但长期规划需谨慎)。
遵循“查询不改变状态”原则,不仅能修复断言失败,更是构建可预测、可测试、线程安全对象的基础实践。

















