2017-04-24 69 views
1

我已经读了一下,试图找出何时适当地使用断言和例外,但仍然有一些我错过了大图。可能我只是需要更多的经验,所以我想带一些简单的例子来更好地理解我应该在什么情况下使用什么。断言和例外的适当使用

例1:让我们从一个无效值的经典情况开始。例如,我有下面的类,在这两个领域必须为正:

class Rectangle{ 
    private int height; 
    private int length; 

    public int Height{ 
     get => height; 
     set{ 
      //avoid to put negative heights 
     } 
    } 
    //same thing for length 
} 

让我此话,我不是在谈论如何在这个例子中处理用户输入,因为我可以做一个简单的控制流程。虽然,我面临的想法是,在别的地方可能会出现一些意想不到的错误,我希望能够检测到这个错误,因为我不想要一个带有invald值的对象。所以我可以:

  • 使用Debug.Assert并停止程序,如果发生这种情况,以便我可以纠正可能出现的错误。
  • 抛出一个ArgumentOutOfRangeException基本上做同样的事情?这感觉不对,所以我应该使用它,只有当我知道我要在某个地方处理它时。虽然,如果我知道在哪里处理异常,我不应该解决它存在的问题吗?或者它可能意味着可能发生的事情,但你不能直接在代码中控制,就像用户输入(可以处理,没有例外,但也许别的东西不能)或加载数据?

问题:我是否明白断言和例外的含义是正确的?另外,请举例说明处理例外情况是否有用(因为发生前无法控制的情况)?除了我提到的情况之外,我无法弄清楚还有什么可以发生,但我显然还是缺乏经验。
为了扩展我的问题:我可以想到为什么会抛出异常的各种原因,比如NullReferenceExceptionIndexOutOfBoundsException,IO异常如DirectoryNotFoundExceptionFileNotFoundException等等。虽然我无法弄清楚在哪些情况下处理除了简单地停止程序(在这种情况下,不应该使用断言?)或给出简单的问题出现位置之外,它们变得有用。我知道即使这是有用的,例外也意味着将“错误”分类并给出如何解决它们的线索。虽然,是一个简单的消息,真的他们对有用吗? 这听起来很腥,所以我会坚持“我从来没有面对过适当的情况,因为经验”的口头禅。

示例2:现在让我们来谈谈用户输入,使用第一个示例。正如我所预料的那样,我不会使用异常来检查值是否为正值,因为这是一个简单的控制流程。但是如果用户输入一个字母会发生什么?我是否应该在这里处理例外情况(可能是简单的ArgumentException)并在catch块中给出消息?或者也可以通过控制流程避免(检查输入是否为int或类似的东西)?

感谢任何会清除我心有余悸的人。

+1

没有一个正确的答案,它完全取决于你有支持系统来处理你的代码的客户端程序员错误,不知道为什么。用户输入无效数据并不例外。 –

+0

我知道高度依赖于情况,我只是想弄清楚会出现什么样的情况。此外,坚持这个简单的例子,相反,如果我的意思是验证值只是为了检测代码中的错误,我应该使用'Debug.Assert'。如果我打算实施保存/加载数据,并且加载文件中的某些内容可能会失败,我可以捕获它并使用“损坏的数据”消息停止该程序。这种推理是正确的还是我没有达到断言和/或例外的目的? – Harnak

+0

其不完整的问题。通过鼓励(或迫使)他们输入正确的数据来处理用户。停止程序时,它可能只是帮助用户似乎不必要 – ferday

回答

10

抛出一个ArgumentOutOfRangeException基本上做同样的事情?这感觉不对,所以我应该使用它,只有当我知道我要在某个地方处理它时。但是,如果我知道应该在哪里处理异常,我不应该将问题解决在哪里吗?

你的推理在这里还算不错,但不是没错。你挣扎的原因是因为异常是在C#中使用件事:

  • 愚蠢的例外。当调用者知道参数无效时,骨头异常就像“无效参数”。如果引发了一个头引发的异常,那么调用者有一个应该修复的bug。测试用例之外永远不会有catch(InvalidArgumentException),因为它永远不会投入生产。这些例外的存在是为了帮助您的呼叫者在他们犯错时大声告诉他们写出正确的代码。

  • 伤脑筋例外是愚蠢的例外情况主叫方无法知道该参数是无效的。这些都是API中的设计缺陷,应该消除。他们要求你用try-catches来封装API调用,以捕捉看起来应该避免的异常,而不是被捕获。如果你发现你正在编写需要调用者在try-catch中包装调用的API,那么你做错了什么。

  • 致命异常是异常,如线程中止,内存不足等。发生了一些可怕的事情,并且过程无法继续。捕捉这些东西几乎没有意义,因为改善情况的能力并不多,而且可能会让情况变得更糟。

  • 外生的例外是像“网络电缆拔掉”的东西。您预计网络电缆将被插入;它不是,也不可能早些时候检查过,看看是否是这样,因为检查时间和使用时间是不同的时间;电缆可以在这两次之间拔掉。你必须抓住这些。

既然你知道四种异常是什么,你可以看到异常和断言之间有什么区别。

断言是逻辑上必须始终为真,如果不是,那么你有一个应该修复的错误。你永远不会断言网络电缆插入。你永远不会断言调用方提供的值不为空。应该有从来没有是一个测试案例,导致断言发射;如果存在,那么测试用例发现了一个错误。

您断言在您的就地排序算法运行后,非空数组中的最小元素将在开始处。应该没有办法可以是假的,如果有的话,你有一个错误。所以断言这个事实。

A throw相反是声明,而每个语句都应该有一个测试用例,它练习它。 “这个API在被错误的调用者传递时抛出”是它的合约的一部分,并且该合约应该是可测试的。如果你发现你正在编写没有可能的测试用例来验证他们抛出的throw语句,考虑将它改为断言。

最后,永远不要传递无效的参数,然后捕获一个带头的异常。如果您正在处理用户输入,那么UI层应该验证输入是否在语法上有效,即,预期数字的数字。 UI层不应该将可能未被修改的用户代码传递给更深层的API,然后处理产生的异常。

+0

优秀的答案,解开了许多疑问。这正是我正在寻找的东西。谢谢! 只是对拔下的电缆示例的最后一个疑问。我的意思是,我不确定自己是否正确,但我不觉得在这样的情况下,你可以通过抓住它来做很多事情。或者你能吗? – Harnak

2

我明白这一点。断言适用于程序员。例外是针对用户的。你可以在你的代码中拥有你期望具体价值的地方。那么你可以把断言,例如:

public int Age 
{ 
    get { return age; } 
    set 
    { 
     age = value; 
     Debug.Assert(age == value); 
    } 
} 

这只是例子。所以如果年龄!=值没有例外。但“嘿程序员,可能发生了一些奇怪的事情,看看这部分代码”。

当应用程序不知道如何在特定情况下作出反应时,您会使用异常。例如:

public int Divide(int a, int b) 
{ 
    Debug.Assert(b != 0); //this is for you as a programmer, but if something bad happened, user won't see this assert, but application also doesn't know what to do in situation like this, so you will add: 

    if(b == 0) 
     throw SomeException(); 
} 

SomeException可能会在应用程序的其他位置处理。

+0

“嘿,程序员”部分非常具有说明性。我认为我正确的目的是断言然后:)谢谢。 尽管如此,我仍然对处理异常抱有怀疑。比方说,我有一种方法可以加载一些数据,然后调用Divide对数据进行操作(我使用加载数据或用户输入场景,因为它们是我现在可以想到的唯一情况;也许**是* *我在处理**异常时仍然怀疑)。我可以处理一个例外,给出一条消息“嗨用户,你在那里放错了一个文件。”如果出现问题? – Harnak

+0

首先,您应该考虑是否可以处理此异常。例如,如果你除以x/0,那么你可能会把某种默认值作为结果。但是,如果你真的不知道在这种“特殊”情况下该做什么,那么你必须通知用户该文件已损坏或某事。但不要以我写的关于默认值的方式误导我。不要使用异常处理来检查事情。异常是昂贵的,应该只在发生错误时使用。 –

+0

我明白。感谢您的解释:) – Harnak