为什么有些码农不写异常
异常很好。 这是一个优雅的概念,许多(即使不是全部)主流编程语言本身也支持这种语言,这有助于表明某些事情未按计划进行,并且该程序无法照常执行。 但是,有人认为这是应该避免的反模式。 让我们看一看到底是什么。
不使用异常的想法听起来很荒谬。 异常是完全合理的; 它们易于学习且易于使用。 您为什么要放弃这种方便的工具? 接下来会发生什么:不使用数组? 避免物体? 远离多态? 但是实际上,如果我们看看提出该想法的地方,我们会发现它来自对编程有很多了解的人:Joel Sapolsky有一篇有关2003年该主题的文章,根据C ++样式指南,在 Google C ++代码和Go(也来自Google)在语言功能方面都没有异常(尽管从技术上讲,它的用法有所不同)。 因此,背后必须有一个理性的想法。
让我们创建一个用例,看看使用异常时可能遇到的问题。
爱丽丝
爱丽丝正在研究一个企业项目,并添加了一项新功能。 在编写测试时,Alice看到该应用程序崩溃并带有以下堆栈跟踪:
爱丽丝的想法:
好吧,如果所请求的物品缺货,则看来应用失败了。 我想我应该在某个时候处理此异常,但是添加此处理的最佳位置是什么?
ItemManager.reserveItems可以工作吗? 当然可以。 订单中可能有一些商品,如果还有其他商品,我们可能仍要继续订购。
还是
OrderProcessor.reserveItems更好地工作? 好吧,如果我们在OrderProcessor中处理订单,那么
OrderProcessorcessPrepaidOrder会更合理-订单已经付款,因此我们可能应该对缺货的商品进行处理,例如 退款或其他费用。
或者我们可以使用
OrderProcessorcessOrder -那么我们不仅可以处理预付款订单,还可以处理任何类型的订单。 现在我们只有一位客户使用此功能,他们希望订单为全有或全无。 因此,让我们暂时采用这个想法,并根据需要在较低级别添加处理。
爱丽丝向
OrderProcessorcessOrder添加异常处理,测试现在变为绿色,并且该功能已交付生产。 一切都很好。
请注意,我们必须做出决定:应该在哪个级别处理异常。 这不是一个容易回答的问题:有多种大专选择,而且所有选择似乎合法。 但是,老实说:这与异常无关,与软件设计有关:谁负责什么,我们要支持的功能以及重要的是我们目前不选择支持的功能。
但是,还有一个与异常有关的更微妙的事情:在实现该功能时,爱丽丝没有预料到错误情况:没有库存的情况。 如果不进行测试,则该功能可能已经交付生产,用户会发现该错误。
鲍勃
稍后,另一位从事项目工作的工程师Bob接到了一个任务,要求新客户加入,但要求略有不同:如果某件商品缺货,请跳过该订单并继续订购。
这是鲍勃的理由:
好的,如果商品缺货,应该采取不同的处理方式:要么取消整个订单,要么跳过商品并继续。 看起来"战略"模式在这里很有用。 说,
MissingItemHandlingStrategy。 我们可以在ItemManager.reserveItems或
OrderProcessor.reserveItems中使用它。 OrderProcessor的职责是处理订单,因此ItemManager似乎是一个更好的地方。 毕竟,它是"经理",因此应该应对这种情况。
然后,根据策略,我们将以不同的方式处理缺货项目:请注意该项目不存在,然后继续进行或完全停止。 好吧,如果我们停止了,我们应该抛出一个异常,但是它可能应该是一个新异常:ItemReservationException。 是运行时还是检查? 我想与运行时已经存在的ItemNotFoundException保持一致。
取消策略已完成,现在跳到一个。 为此,我应该将ItemManager.reserveItems的返回类型从没有更改为我们能够保留的项目ID的列表。 做完了 并将策略作为参数添加到我们的方法中。 对于所有现有呼叫者,只需添加
MissingItemHandlingStrategy.Cancel。 真好
让我们运行测试,……嗯……失败了。 嗯,对,在
OrderProcessorcessOrder中,我们以前处理的是ItemNotFoundException,现在已在较低级别处理,因此我们应将其更改为ItemReservationException。
在此示例中,我们看到了更多决策需要:
· 我们应该传播现有的异常还是将其包装在另一个异常中?
· 如果传播,那么在更高的层次上,我们应该准备好处理从调用堆栈开始的非常特殊的异常。
· 如果是wrap —那么我们应该重构所有调用我们方法的代码,以使用新的包装异常。 而且,不仅直接呼叫者-任何间接呼叫者都可以处理我们要处理的异常。
· 另外,如果是自动换行,应该是标准异常(例如IllegalArgumentException或类似的异常)还是自定义的异常? 也许是自定义,但从可能已存在于某种公共包中的已存在列表中呢?
· 如果包装并创建一个新异常:应在运行时还是检查该异常? 顺应现有选择是好的(一致性),但是如果同时使用两种类型该怎么办?
· 如果与运行时一起使用,那么API并不是很清楚。 是的,我们有javadoc来添加文档,但是我们应该列出在类及其所有依赖项中可能发生的所有可能的异常吗? 嗯,这是将事物包装在自定义异常中的一个很好的论据。
所有这些都必须以某种方式回答。 可能是随机的-在项目中造成不一致。 或者可以制定准则,但是这需要与其他人,会议,讨论进行协调,这些工作不会为正在生产的产品增加任何价值。
颂歌
Carol是一个项目的第三位工程师,而Bob要求她审查他的更改。
让我们来看看。 我们有一个ItemManager,它的方法reserveItems现在接受
MissingItemHandlingStrategy,并返回已处理项目的列表。 如果策略为"取消",则将返回所有ID,否则将引发ItemReservationException。 如果策略为"跳过",则仅退货库存项目。
好的,但是为什么我们要为取消策略抛出异常。 某些物品缺货真的是异常情况吗? 看起来并非如此。 为什么不返回诸如ItemReservationResult之类的内容,该内容将包含已处理的ID列表以及状态-预订是否成功。
这个例子说明了决策工程师还必须做出的另外一个决定:这真的是异常情况还是程序的正常运行? 如果是,那么异常是正确的工具; 如果没有,则返回值会更好。
> Is exception an organic thing like return or spaghetti like goto?
我们可以用四个要点来总结开头和示例中的链接,这四个要点使异常不是很容易使用的工具:
· 异常允许您专注于"幸福的道路"。 这是一件好事,但在这项任务上却是如此出色,以至于例外之后通常会添加错误处理。 就像贷款一样,它可以帮助您快速,有趣而又有成本地进入有趣的部分。
· 异常会在调用堆栈中的某个位置执行单向控制转移。 在这方面,异常与goto有点相似,维基百科说它"将控制单向转移到另一行代码"。 结果,代码变得难以预测-我们不能仅通过查看功能代码来说明程序将如何执行。
· 在某些情况下,异常只是返回值。 这是对异常的不当使用,但要进行测试以检查异常使用是否适当(这在某种意义上不是NP问题),并不是一项艰巨的任务。 尤其是在一开始是正确选择的情况下,然后,一如既往,情况有所改变,情况不再如此。
· 做正确的事容易而难于做错事吗? 不,正确使用异常需要相当好的指导原则。 不仅要有指导方针,还应该有实施指导方针的机制。 因此,应完成大量工作,这与项目本身完全无关。
有什么选择?
将错误视为值。 此方法有一些示例:
· Scala的功能错误处理
· 具有RETURN_IF_ERROR和ASSIGN_OR_RETURN宏的Google的C ++状态(或)
· Go返回err,然后如果err!=nil无处不在
因此,永远不要使用异常?
好吧,实际上,没有。
是的,在本文中,我们正在探讨使用异常时遇到的困难。 但! 此处讨论的某些内容适用于异常和"错误是值"。 同样,在某些情况下,异常的这种特征非常方便。
#学习路径#