“防御性编程”真的健康吗?
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}`) | |
} |
现在,当我们决定开始处理闪烁的红灯时,驾驶员就不会发生车祸了……因为在我们指示驾驶员如何应对这种新情况之前,我们根本无法编译代码。在最初的例子中,代码会告诉驾驶员“走”。在我看来,这似乎不太明智。
这种防御性编程技巧的妙处在于,几乎无需花费时间就能在代码中添加详尽的类型检查。我大脑中经验丰富的程序员部分知道,防御性编程是满足用户需求最简单、最好的方法。但是,我有时会担心我的职业会阻碍我真正像佛教徒一样行事。希望像这种“永不断言”的方法能让我找到平衡。毕竟,我只是个凡人。而佛教教导我们热爱人性,接纳自己的情感。
但是您怎么看呢?我很想在Twitter和Dev.to上听听您对防御性编程健康性的看法。您觉得它过于担心未来吗?您是否应该只关注软件当前需要做的事情?或者您认为防御性编程是可以接受的?
文章来源:https://dev.to/cubiclebuddha/is-defective-programming-actually-healthy-5flj