如果您有任何结果或输入无法由您的程序处理的情况下,那应该是一个错误。你应该知道你的程序可以处理和只允许。对于未来可能出现的情况,如果结果可以处理,但还没有结果,我仍然建议将其视为一个错误,直到您真正需要结果为止。如果有疑问,你不知道,程序不知道,它不能处理,你没有配备你的程序来处理它,所以这是一个错误。该计划不能做任何事情,只能停下来。
对于用户输入来说,依靠程序最终崩溃是一个非常糟糕的主意。你不知道什么时候,甚至会不会崩溃。它可能只是最终做错了事,或者做了十件事,然后崩溃,它不应该做,并且如果输入已被验证,则不会做。
在防范自己的错误方面,更多的是混合包。您需要更多地关注通过测试来确保工作正常,消除未知因素,校对阅读,确保您确切知道程序的工作方式等。您偶尔还会遇到内部处理可能产生不良结果的情况。
事实证明,对于给定的情况,结果不是错误,你不处理异常,你处理null,而不是一个例外。在这种情况下,结果不是错误。所以你处理结果而不是错误。这不可能更简单。如果你正在捕捉一个异常,然后做一些可以做的事情,如果这样做,如:
try
extra = i_need_it(extra_id)
show('extra', extra)
catch
show('add_extra')
然后,这是不对的。如果你没有额外的东西,你有一个完全可以接受的行动方案。
这是好多了,它让你的意图明显没有额外的冗长:这里
Something extra = i_want_it(extra_id)
if extra ==== null
show('add_extra')
else
show('extra', extra)
通知你需要什么特别的避免受凉从另一层异常。我如何把上面的尝试抓住是一个不好的做法。你应该只是包装引发异常的东西:
Something extra
try
extra = i_need_it(extra_id)
if extra === null
show('add_extra')
else
show('extra', extra)
当你这样的事情,那么它只是将null转换为异常,然后再回来。这是Yo-Yo编码。直到你实际上是能够实现处理的空
Object i_need_it(int id) throws
:
你应该开始。如果您能够实现对异常的处理,则可以实现对null的处理。
当事实证明,事情并不总是需要添加任何此方法或更改i_need_it它(如果为null总是处理):
Object|null i_want_it(int id)
另一种方法是检查是首先存在:
bool does_it_exist(int id)
不这样做,所以经常的原因是因为它通常出来是这样的:
if(does_it_exist(id))
Something i_need = i_need_it(id)
这往往更容易出现并发问题,可能需要更多可能不可靠的调用(网络上的IO),并且可能效率低下(两个RTT而非一个)。其他调用通常会像这样合并,如更新(如果存在),插入(如果唯一),更新(如果存在或插入)等,然后返回通常是最初检查的结果。其中一些在有效载荷大小效率和RTT效率方面存在冲突,其也可以根据多个因素而变化。
然而,当需要根据存在与否存在交替行为时更便宜,但是您不需要处理它。如果你也不需要担心上述问题,它会更清晰一些。
你可能甚至想:
void it_must_exist(int id) throws
这又是有用的,因为如果你只需要确保的东西存在,它往往比得到更便宜。然而,很少有人会需要这样做,因为在大多数情况下,您会想知道是否存在某些内容以便直接处理它。
构想它的一种方式是,如果'does_it_exist'只是简单地在调用堆栈上显式返回一个布尔值,那么'i_want_it'是组合'has'和'get'的效果。
虽然有两种不同的方法更加明确分离方法签名,有时你可能需要从别的东西和最简单的方式传递下来,如果你不是我的位模糊的是:
Object|null get(int id, bool require) throws
这是更好的,因为你将合同交给呼叫链,而不是建立在远离行动的沙滩上。有许多方法可以更明确地传递你的契约,但它往往会让人费解YAGNI(IE,传递一个方法调用者)。
你应该提前抛出异常,你可以想要安全而不是遗憾,所以误报是好的。一旦你发现它是一个误报,尽管如此,你可以在源头修复它。
你应该极其罕见地处理异常。绝大多数应该击中顶部,然后调用日志记录和输出处理程序。通过直接传递结果并处理它,您可以适当地修复其他问题。当你有许多用途中有一个误报时,只有这个用法。不要只是删除根目录中的检查,并打破许多其他仍然存在错误的情况。
Java是一种不幸的语言,因为我不能有一种说法不传递null的方法,或者这个变量必须是非空的。
当缺少这样的功能时,通常最好在它们的来源(例如IO)中检查空值,而不是每次将某个东西传递给某个方法时使用。否则,这是一个荒谬的空值检查。
如果你真的需要这个,你可以应用这个模式来创建函数来替换你的参数检查ifs。您将与对象替换ID本身如:
Object i_want(Object it) throws
if(it === null)
throw
return it;
然后:
void give_me(Object it)
this.it = Lib<Object>::i_want(it)
一种耻辱没有中继类型。
或者:
void i_get_it(Getter<Object> g)
this.it = Lib<Object>::i_want(g.gimme())
你可能有一个特定的吸气剂,而不是通用的。
或者:
void i_need_it(Result<Object> r)
this.it = r.require()
如果你只能做它的边界而不是在(当你打电话东西拿出来控制,其中非空的结果不能得到保证的一侧),而最好是做它在那里或任何用法的方法被记录为返回null并且只在那里,因为这是真正需要的地方,这就意味着当你得到一个null不属于它的地方时(错误发生),你不会去有一个容易的时间找出它来自哪里。它可以从IO传来,并且不会产生空指针异常,除非有东西试图对其进行操作。这些空值可以在系统中传递几天甚至几个月,作为等待中止的定时炸弹。
我不会自己这样做,但在某些情况下,我不会责怪人们实施上面的防御性编程方法,因为Java的破坏类型系统默认情况下是松散的并且不能被限制。在强类型语言中,除非明确声明,否则不允许使用null。
请注意,尽管我称之为破坏,但几十年来我一直在使用明显较宽松的语言来构建大型,复杂和关键系统,而不必花费过多的检查来代码。纪律和能力决定了质量。
假结果或情况发生时,您认为是所有调用者都无法处理的结果,但至少有一个调用者可以正确处理它。在这种情况下,你不会处理异常,而是给调用者提供结果。这很少有例外。
Java 8具有可选功能,但看起来并没有什么帮助。这是内部平台效应的一个可怕的例子,它试图将新的语法作为类实现,并且最终不得不添加一半现有的Java语法,使得它非常笨重。像往常一样,现代Java OOP通过增加更多功能和更复杂的东西解决了每个人都希望减少欺骗的问题。如果你真的想像那样链接,你可能想尝试一些例如直接实现该语法的kotlin。
现代语言将意味着你不必担心极本,只是有:
void i_need_it(Object it)
this.it = it
void i_want_it(Object? it)
this.it = it
现代语言甚至可能回到原来的对象的方法,而不返回(替换为空自我作为标准和自动返回),所以人们可以在编程中拥有自己的花哨链接或其他任何时髦的东西。
你不能有一个基类的工厂给你一个Null或NotNull,因为你仍然需要传递基类,并且当你说你想要NotNull的时候这将是一个类型违规。
你可能想玩弄方面,静态分析等,虽然这有点兔子漏洞。所有的文档都应该指出是否可以返回null(尽管如果是间接的,它可能会被忽略)。
你可以让一个类如MightHave包裹你的结果,你可以把它放在get,require等方法上,如果你不喜欢静态但是想吃一个if虽然它也处于轻度复杂的领域并将所有方法签名搞乱,将遍布各处的所有东西都捆绑起来,这是一个丑陋的解决方案。作为替代罕见异常处理的例子,由于调用图的复杂性和各层中未知数的数量,异常看起来很有用,它只是非常方便。
一个极其罕见的情况是,当你的源代码知道要抛出什么异常时,如果它需要抛出但不能传递下去(尽管在两个层之间需要接近警告)。有些人可能也想要这样做,因为它可以轻松地追踪丢失项目来自哪里以及需要哪些项目,这些都是使用检查可能无法提供的(它们很可能在靠近源的位置失败,但不能保证)。应该谨慎,因为这些问题可能比合理多态性,元/动态编码等问题更能表明流控制不佳或过于复杂。
应该注意事项,如默认值或空对象模式。两者都可能最终隐藏错误,成为最好的猜测或治愈疾病。通常可以使用NullObject模式和Optional两种方法来简单地关闭或双向传递解释器中的内置错误处理。
默认并不总是不好,但可以落入猜测的领域。隐藏来自用户的错误最终将其设置为失败。默认(和sanitisation)总是需要仔细考虑。
诸如NullObject模式和Optional之类的东西可以被过度使用以有效地关闭空指针检查。它只是假设null不会是一个错误,它最终会导致程序在做某些事情,而不是其他事情,但你不知道是什么。在某些情况下,这可能会产生令人捧腹的结果,例如用户的信用卡为空。确保你正在使用这些东西,而不是将它们用于将所有代码简单地包装在忽略空指针异常的try catch中的效果。这是非常普遍的,因为人们倾向于修复引发异常的错误。事实上,真正的错误往往会更远。最终会出现一个错误的IO处理错误返回null,并且null在程序中传递。与其修复null的一个来源,人们会尝试修复它到达的所有地方,从而导致错误。
NullObject模式或MagicNoop模式有它们的地方,但不是常用的。你不应该使用它们,直到它变得显而易见,它们以合理的方式是有用的,不会导致比解决问题更多的问题。有时一个noop实际上是一个错误。
ack ...不要捕捉异常...如果除数为零,该怎么办?然后,您还将捕获除以零的ArithmeticException,但将其视为“预期的”NullPointerException。尽可能具体说明你想要捕捉的异常情况。 – TofuBeer 2010-07-11 21:27:45
有没有人想把自己的脖子伸出来,并说if语句比抛出异常要便宜? – AJay 2010-07-12 00:50:32
是的,我喜欢。 'if'语句比抛出异常要便宜得多。 – EJP 2010-07-12 06:37:05