Appearance
沉默的函数
背景:recreateCloth 失败时默默返回,调用方不知道布料是重建成功、失败、还是压根没启用。 过程:返回值从 void 改为 boolean,成功 true 失败 false——函数契约从"做了就算"变成"做没做成我告诉你"。
有一个函数,叫 recreateCloth。
它的工作是——销毁当前布料,然后重新创建一个。
但它有一个问题:它不说话。
如果你调用它,而布料根本就没启用——它什么都不做,然后默默地返回。 如果你调用它,而模型找不到了——它什么都不做,然后默默地返回。 如果你调用它,而出了什么错——它 catch 住错误,然后默默地返回。
调用的人不知道发生了什么。 成功了?失败了?还是根本没做事? 没人知道。因为函数什么都不说。
沉默的代价
"这样的函数有什么问题?"外交官问。
"嗯……调用方不知道结果?"AI 同行者试着回答。
"对,"外交官点头,"调用方不知道结果,就没法做后续处理。"
他举了个例子:
"用户在设置面板里调了一个布料参数——比如粒子间距。调完之后,UI 应该给个反馈吧?比如'重建成功'或者'参数无效'。但如果 recreateCloth 不返回任何东西,UI 怎么知道要不要给反馈?"
"那 UI 就假设成功了?"
"假设成功了,"外交官说,"但如果实际上失败了呢?比如布料本来就没开,用户调了参数,什么反应都没有——用户就会觉得'这个滑条坏了'。"
"或者更糟——用户调了一个非法值,重建失败了,但 UI 不知道,还是显示'已应用'。用户以为生效了,实际上没有。这就更误导人了。"
"沉默的函数,会让它的调用者也变成瞎子。"
返回值的契约
"那应该怎么改?"
"给它一个返回值,"外交官说,"成功了返回 true,失败了返回 false。就这么简单。"
他翻出 cloth-manager.ts 的改动:
typescript
// 旧:
export function recreateCloth(modelId: string): void {
if (!clothEnabled) return;
// ... 重建逻辑
}
// 新:
export function recreateCloth(modelId: string): boolean {
if (!clothEnabled) return false;
// ... 重建逻辑
return true;
}"改了个返回类型,加了两处 return false。"
"这么简单?"
"就这么简单,"外交官说,"但意义不一样。以前这个函数的契约是'我尽量做,做不成就算了'。现在它的契约是'我告诉你我有没有做成'。"
"契约变了。"
"契约变了,"外交官确认,"函数的返回值不是可有可无的东西。它是函数和调用者之间的契约。返回 void 的函数,意思是'调用我不用关心结果'。返回 boolean 的函数,意思是'我可能失败,你最好检查一下'。"
什么时候该有返回值
"那是不是所有函数都应该有返回值?"AI 问。
"不是,"外交官摇头,"要看调用方需不需要知道结果。"
他想了想,举了几个例子:
应该有返回值的:
- 可能失败的操作(加载、保存、重建)
- 有条件执行的操作(布料没启用就不做)
- 调用方需要根据结果做不同处理的操作
不需要返回值的:
- 不可能失败的操作(比如设置一个变量)
- 调用方不关心结果的操作(比如
console.log) - 纯副作用函数(触发一个事件、播放一个音效)
"recreateCloth 属于哪一类?"
"三类都占了,"外交官笑了,"它可能失败(模型不存在)、它有条件执行(布料没启用就跳过)、调用方需要知道结果(UI 要给反馈)。所以必须有返回值。"
调用方的变化
"那调用方要改吗?"
"目前不用,"外交官说,"因为返回 boolean 是向后兼容的——以前的调用方不关心返回值,现在多了个返回值,它们忽略就行。不影响现有逻辑。"
"但以后的调用方就可以用了?"
"对,以后加 UI 反馈的时候,就可以直接用这个返回值。比如:"
typescript
const ok = recreateCloth(modelId);
if (!ok) {
setStatus('布料未启用,无法重建');
}"这就是为什么现在就要改——不是因为现在要用,是因为以后要用的时候,不用再回来改函数了。函数契约先定好,调用方随时可以用。"
"提前铺路。"
"提前铺路,"外交官点头,"好的代码不是一步到位的,是一步一步把地基打好。今天加个返回值,明天加个错误信息,后天加个重试机制。每一步都不大,但每一步都让系统更健壮。"
沉默不是金
"我以前听说'沉默是金',"AI 说,"函数什么都不说,不是更简洁吗?"
"那是说人,不是说函数,"外交官笑了,"人沉默可能是深沉、是稳重。函数沉默——就是不负责任。"
他顿了顿:
"函数做了事,就要告诉调用者结果。成了还是败了,做了还是没做——说一声。调用者可以选择不听,但你不能不说。"
"这就像……快递员送快递,至少要敲个门。你不能把快递扔门口就走,连个通知都没有。"
"这个比喻好,"AI 笑了。
"本来就是,"外交官耸耸肩,"函数和调用者的关系,就像服务和客户。你提供服务,客户付了钱(传了参数),你至少要告诉人家服务做完了没有。"
第七颗石子
第七颗黄石子落进"已处理"的堆里。
这一颗最小——只是改了个返回类型,加了两处 return。 但它的意义不小——一个函数的契约,从"做了就算"变成了"做没做成我告诉你"。
"这一章好短,"AI 说。
"短,但重要,"外交官说,"很多代码质量的问题,都不是什么大不了的技术难题。就是这些小事——有没有返回值、有没有错误处理、变量名好不好、注释写没写。"
他看着那堆黄石子:
"加起来,就是代码的质感。"
附录:函数返回值设计原则
| 原则 | 说明 |
|---|---|
| 可能失败就返回状态 | 任何有失败可能的函数,都应该返回成功/失败的标识 |
| 向后兼容扩展 | 从 void 改返回 boolean 是向后兼容的——老调用方忽略返回值就行 |
| 契约清晰 | 函数签名就是契约。返回 void = 不关心结果,返回 boolean = 可能失败 |
| 不用异常做控制流 | 预期内的失败(如"布料未启用")用返回值,不用 throw |
| 调用方可以不用,但你不能没有 | 先把返回值加上,以后调用方需要的时候直接用,不用再改函数 |
教训:沉默的函数是不负责任的函数。做了事,就要告诉调用者结果。成了还是败了,做了还是没做——说一声。调用者可以不听,但你不能不说。