“防御性编程”真的健康吗?AWS GenAI 上线!

2025-06-04

“防御性编程”真的健康吗?

AWS GenAI 直播!

我解决不了这个问题,我想我需要 DEV 社区的帮助。一位开发者回复了我写的一条代码审查评论,然后直接问我:“我为什么要这么做?”我给出了一个标准的、老套的答案:“因为你必须谨慎地编写代码——你不知道未来会发生什么。” 但我突然意识到……我是不是在滋生对未来的恐惧?我运营着 CubicleBuddha.com,我经常在博客上写关于如何快乐地活在当下的文章,我怎么能带着恐惧去编写代码呢?我会和大家分享具体的代码示例。我希望社区能告诉我,我的解决方案是“活在当下”,还是我实际上在向恐惧低头。

经典的防御性编程示例

审查同事代码的职责之一就是尝试找出他们可能遗漏了什么。这符合防御性编程的标准定义:

防御性编程是指程序员预见问题并编写代码来处理这些问题。(1

想象一下,你正在审查一个拉取请求,而代码正在做一些假设。乍一看,下面的代码示例似乎没什么问题。或许确实如此。但是,在花了几十年时间修复他人的生产环境bug之后,我的“蜘蛛感应”却让我感到一阵恐惧。一个特定的bug浮现在我的脑海里(我将在下面的第二个代码示例中演示它),这让我盯着Github代码审查,不知该如何继续。我陷入了困境,不知道是应该保持沉默以维护与同事之间轻松愉快的关系,还是应该站出来阻止潜在的生产环境bug。我是否被职业生涯早期被降级为只能修复bug的那段经历所困扰?或者,我的成长岁月是一段宝贵的训练场,造就了今天的我?

“如果你对过去感到悲伤和后悔,或者对未来感到焦虑,那么你就无法真正自由地享受此时此地的众多生活奇迹。”

~ 一行禅师

亲自看看你是否能找到bug容易出现的地方。如果你没发现bug,那我真有点嫉妒你过去的经历没有提醒你潜在的噩梦。未知本身就是一种幸福。但遗憾的是,遇到生产环境bug的用户并不在乎你的“幸福”,他们只想完成他们正在做的事情:

type ITrafficLight = "red" | "green" | "yellow";
// Can you see where the function below might go wrong in the future?
// You can skip ahead to the answer here: https://gist.github.com/TheCubicleBuddha/7a8854f244697b9894e41d44e1fc6967
function respondToTrafficSignal(signal: ITrafficLight): "stop" | "go" | "pause" {
if(signal === "red"){
return "stop";
} else if(signal === "yellow"){
return "pause";
} else {
return "go";
}
}
type ITrafficLight = "red" | "green" | "yellow";
// Can you see where the function below might go wrong in the future?
// You can skip ahead to the answer here: https://gist.github.com/TheCubicleBuddha/7a8854f244697b9894e41d44e1fc6967
function respondToTrafficSignal(signal: ITrafficLight): "stop" | "go" | "pause" {
if(signal === "red"){
return "stop";
} else if(signal === "yellow"){
return "pause";
} else {
return "go";
}
}

好的,是的。“目前”没有问题。有人可能会争辩说(我的同事一直在这么说),因为我们的程序只在受三大交通信号灯(红、黄、绿)限制的地理区域使用,所以我们现在不必担心这个问题。我的同事用我最喜欢的一句话来反驳我:“你不需要它”(YAGNI)。我明白。但我们真的不在乎扩展软件吗?

而这正是我内心最大的冲突,在我的编码风格和我的哲学信仰之间挣扎。如果你不想让越来越多的人使用你的软件,那你为什么要开发它呢?业余编程没什么可羞耻的。但如果你是一名职业程序员,你这样做是为了赚钱,或者改善客户的生活。

那么,我们能务实一点吗?我们能在格子间这种枯燥乏味的环境中努力成为佛陀吗?我们能一脚踏入商业,一脚踏入宁静吗?以下的编码技巧(在我看来)将帮助你在平静地专注于当下的同时,为未来开辟道路。

预见未来的车祸……并保持冷静

所以,想想看,当你获得新用户时,你应该了解新客户的需求。新的用例意味着要编写新的功能。这里有一个经典的例子。今天,我们只处理3个指示灯。但是,如果我们开始在其他州销售该软件呢?例如,我居住的州有一个闪烁的红灯,要求你先停后行(有点像停车标志)。让我们看看之前有效的代码是否能保护我们免受未来的影响——你能发现可能发生的灾难吗?

type ITrafficLight = "red"
| "redBlinking" // <-- new case
| "green"
| "yellow"
| "yellowBlinking"; // <-- new case
// Can you spot the bug?
// You can skip ahead to the answer here: https://gist.github.com/TheCubicleBuddha/7a8854f244697b9894e41d44e1fc6967
function respondToTrafficSignal(signal: ITrafficLight): "stop" | "go" | "pause" {
if(signal === "red"){
return "stop";
} else if(signal === "yellow"){
return "pause";
} else {
return "go";
}
}
type ITrafficLight = "red"
| "redBlinking" // <-- new case
| "green"
| "yellow"
| "yellowBlinking"; // <-- new case
// Can you spot the bug?
// You can skip ahead to the answer here: https://gist.github.com/TheCubicleBuddha/7a8854f244697b9894e41d44e1fc6967
function respondToTrafficSignal(signal: ITrafficLight): "stop" | "go" | "pause" {
if(signal === "red"){
return "stop";
} else if(signal === "yellow"){
return "pause";
} else {
return "go";
}
}

等一下,如果司机看到红灯闪烁……那岂不是落入了 fall-through/else 的陷阱?难道他们不会……哦不! 轰!!!让我们看看能否在不做太多工作的情况下,预防未来的车祸。

捍卫未来:“永不”型人来救援!

值得庆幸的是,TypeScript 有一个称为“never”类型的语言特性,它允许编译器识别类型联合中的所有情况(或枚举中的每个情况)是否未被考虑。如下所示,通过不允许一系列 if-else 语句落入默认的 else 语句,编译器会告诉我们,我们忘记指示驱动程序如何响应“红色闪烁指示灯”。

type ITrafficLight = "red" | "redBlinking" | "green" | "yellow" | "yellowBlinking";
function respondToTrafficSignal(signal: ITrafficLight): "stop" | "go" | "pause" {
if(signal === "red"){
return "stop";
} else if(signal === "yellow"){
return "pause";
} else if(signal === "green") {
return "go";
} else {
return neverGonnaGetHere(signal) // The compiler catches the logical error: Type '"redBlinking"' is not assignable to type 'never'.
}
}
function neverGonnaGetHere(hopefullyNotPossibleValue: never): never {
throw new Error(`Did not expect this value: ${hopefullyNotPossibleValue}`)
}
type ITrafficLight = "red" | "redBlinking" | "green" | "yellow" | "yellowBlinking";
function respondToTrafficSignal(signal: ITrafficLight): "stop" | "go" | "pause" {
if(signal === "red"){
return "stop";
} else if(signal === "yellow"){
return "pause";
} else if(signal === "green") {
return "go";
} else {
return neverGonnaGetHere(signal) // The compiler catches the logical error: Type '"redBlinking"' is not assignable to type 'never'.
}
}
function neverGonnaGetHere(hopefullyNotPossibleValue: never): never {
throw new Error(`Did not expect this value: ${hopefullyNotPossibleValue}`)
}

现在,当我们决定开始处理闪烁的红灯时,驾驶员就不会发生车祸了……因为在我们指示驾驶员如何应对这种新情况之前,我们根本无法编译代码。在最初的例子中,代码会告诉驾驶员“走”。在我看来,这似乎不太明智。

这种防御性编程技巧的妙处在于,几乎无需花费时间就能在代码中添加详尽的类型检查。我大脑中经验丰富的程序员部分知道,防御性编程是满足用户需求最简单、最好的方法。但是,我有时会担心我的职业会阻碍我真正像佛教徒一样行事。希望像这种“永不断言”的方法能让我找到平衡。毕竟,我只是个凡人。而佛教教导我们热爱人性,接纳自己的情感。


但是您怎么看呢?我很想在TwitterDev.to上听听您对防御性编程健康性的看法。您觉得它过于担心未来吗?您是否应该只关注软件当前需要做的事情?或者您认为防御性编程是可以接受的?

文章来源:https://dev.to/cubiclebuddha/is-defective-programming-actually-healthy-5flj
PREV
When to actually use preventDefault(), stopPropagation(), and setTimeout() in Javascript event listeners
NEXT
使用 Material UI 和 Spark of Joy 来 React 数据表 ⚛️ 😛 如何构建数据表 🤔 准备数据库中的数据 💾 启动 API 来处理数据 🚀 使用 React 创建应用程序 ⚛️ 构建基本数据表 🏗