Skip to content

沉默的函数

背景: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
调用方可以不用,但你不能没有先把返回值加上,以后调用方需要的时候直接用,不用再改函数

教训:沉默的函数是不负责任的函数。做了事,就要告诉调用者结果。成了还是败了,做了还是没做——说一声。调用者可以不听,但你不能不说。