代码契约如何处理异常

发布于 2024-12-20 16:07:47 字数 291 浏览 0 评论 0原文

我的主要问题是如何处理我抛出的异常?

例如:

 Contract.Requires(foo != null);

在函数调用的上层抛出此异常该怎么办?

我是否应该忽略它,当我看到它时就知道我的设计有问题并修复它?

但是,如果我在没有合同的情况下部署,并且我将得到一个 foo == null 的参数,并且我的逻辑不知道如何处理这样的参数,那么部署中会发生什么。然后一切都会崩溃。

有人可以解释如何处理所有这些情况吗?

谢谢

My main question is what to do with those Exceptions that I throw ?

For example:

 Contract.Requires(foo != null);

What to do in uper levels of the function call with this thrown exception ?

Should I ignore it , and just when I see it know that something wrong with my design and fix it ?

But what would happen in deployment if I deploy without the Contracts and I will get an argument that is foo == null ,and my logic has no clue how to deal with argument like this .Then everything will crash .

Could someone explain how to deal with all those scenarios ?

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

橙味迷妹 2024-12-27 16:07:47

我也一直在寻找有关如何捕获合同条件引发的异常的解决方案。显式抛出可能发生的异常始终是一个好主意。您是否想要捕获它们,以便您的代码不会因大爆炸而停止;取决于验证的数据。我还使用合约来验证用户输入。通过合约前提条件,您可以强制用户输入符合某些要求(例如非空或空字符串)。其次,您可以使用合约来验证您自己的内部代码(尤其是计算),不仅强制参数输入有效,而且强制结果有效计算有效。

可以捕获合约条件抛出的异常;只需调用代码放入try-catch块中,并显式捕获条件将抛出的异常类型。我只会通过用户输入验证来做到这一点。因为当合约条件设置时不仅要验证参数,而且关键的代码逻辑也会抛出错误;您的代码逻辑而不是参数值可能有问题。在这种情况下,最好完全停止该程序。但如果您希望程序以更受控制的方式终止,您可以捕获它们。然后由您来验证让该程序继续运行是否安全

我发现还可以检查事件的空引用(至少是您自己创建的)。我在自己的示例代码中使用了它,该代码也捕获了合约抛出的错误。您需要传递事件或调用事件的对象作为附加参数来访问事件。以下代码是我的一个课程中的一部分:

public delegate void Transactie(Rekening rekening);//signature for events
    public Transactie RekeningUittreksel;
    public Transactie NegatiefSaldo;

public void Storten(decimal bedrag,Transactie actie)
    {

        Contract.Requires<NullReferenceException>(actie!=null,"\n\nNo event listeners have been added yet!\n\n");
        VorigSaldo = Saldo;
        Saldo += bedrag;
        RekeningUittreksel(this);

    }
 public void Afhalen(decimal bedrag,Transactie actie,Transactie actie2)
    {
        Contract.Requires<NullReferenceException>(actie!=null,"\n\nNo event listeners have been added yet!\n\n");
        Contract.Requires<NullReferenceException>(actie2 != null, "\n\nNo event listeners have been added yet!\n\n");
        VorigSaldo = Saldo;
        if (bedrag <= Saldo)
            {
                Saldo -= bedrag;
                RekeningUittreksel(this);
            }
            else
            {
                NegatiefSaldo(this);
            }
    }

接下来是程序主方法的一部分。我注释掉了添加事件侦听器的行,以便上面定义的合约规则将抛出空引用异常。这是如何捕获它们而不用大爆炸终止的方法:

        //mijnZichtrekening.RekeningUittreksel += pietjePek.ToonUittreksel;
        //mijnZichtrekening.NegatiefSaldo += pietjePek.ToonNegatief;
        try
        {
            mijnZichtrekening.Storten(50m, mijnZichtrekening.RekeningUittreksel);
        }
        catch (NullReferenceException ex)
        {
            Console.WriteLine(ex);
        }
        try
        {
            mijnZichtrekening.Afhalen(100m, mijnZichtrekening.RekeningUittreksel, mijnZichtrekening.NegatiefSaldo);
        }
        catch(NullReferenceException ex)
        {
            Console.WriteLine(ex);
        }

我重写了一些代码,以使用新的 .NET 4.5 合约缩写对事件执行空引用检查:

    public void Afhalen(decimal bedrag)
    {
        NegatiefSaldoHasListeners(this.RekeningUittreksel, this.NegatiefSaldo);//calls  the contract abbreviator with delegate type parameters to check for Nullreference
        VorigSaldo = Saldo;
        if (bedrag <= Saldo)
            {
                Saldo -= bedrag;
                RekeningUittreksel(this);
            }
            else
            {
                NegatiefSaldo(this);
            }
    }

    public void Storten(decimal bedrag)
    {
        UittrekselHasListeners(this.RekeningUittreksel);//calls the contract abbreviator with a delegate type (event) parameter to check for Nullreference

        VorigSaldo = Saldo;
        Saldo += bedrag;
        RekeningUittreksel(this);

    }


    public virtual void Afbeelden()
    {
        Console.WriteLine("Rekeningnr: {0:0000 0000 0000 0000}",Nummer);
        Console.WriteLine("Saldo:  {0}",Saldo);
        Console.WriteLine("Creatiedatum: {0:dd-MM-yyyy}",CreatieDatum);
    }

    [ContractAbbreviator]
    public void CheckArgs(string nummer, Klant eigenaar)
    {
        Contract.Requires<ArgumentException>(!String.IsNullOrWhiteSpace(nummer), "Geen nummer ingevuld!");
        Contract.Requires<FormatException>(nummer.Trim().Length == 16,"Ongeldig aantal tekens ingevoerd!");
        Contract.Requires<ArgumentException>(!String.IsNullOrWhiteSpace(eigenaar.ToString()), "Eigenaar niet opgegeven!");
    }

    [ContractAbbreviator]
    public void UittrekselHasListeners(Transactie actie)
    {
        Contract.Requires<NullReferenceException>(actie != null, "\n\nGeen event listener toegewezen!\n\n");
    }

    [ContractAbbreviator]
    public void NegatiefSaldoHasListeners(Transactie actie,Transactie actie2)
    {
        Contract.Requires<NullReferenceException>(actie != null, "\n\nGeen event listener toegewezen!\n\n");
        Contract.Requires<NullReferenceException>(actie2 != null, "\n\nGeen event listener toegewezen!\n\n");
    }

I've been looking around too for a solution on how to catch exceptions thrown by contract conditions. It is always a good idea to explicitly throw the exceptions that can occur. Wether you want to catch them so your code doesn't halt with a nice big bang;depends on what data is validated. I also use contracts for user input validation. With contract preconditions you can enforce user input to conform to certain requirements (like not null or empty string).Second, you can use contracts to validate your own internal code (especially calculations) and enforce not only parameter input being valid but also the result of calculations being valid.

It is possible to catch exceptions thrown by contract conditions; simply put the calling code inside a try-catch block and explicitly catch the type of exception(s) that your condition(s) will throw. I would only do that with user input validation. Because when contract conditions set up to not only verify parameters but also essential code logic throw an error; something might be wrong with your code logic rather than the parameter values. In that case it is better to halt the program completely. But if you want your program to terminate in a more controlled way, you can catch them though. It is then up to you to verify if it is safe to let the program continue or not.

And I found out it is also possible to check for null references on events (at least created by yourself). I used it in my own sample code that also catches the contract thrown error. You need to pass the event or the object that calls the event as an additional parameter to access the event. Following code is part of what I have in one of my classes:

public delegate void Transactie(Rekening rekening);//signature for events
    public Transactie RekeningUittreksel;
    public Transactie NegatiefSaldo;

public void Storten(decimal bedrag,Transactie actie)
    {

        Contract.Requires<NullReferenceException>(actie!=null,"\n\nNo event listeners have been added yet!\n\n");
        VorigSaldo = Saldo;
        Saldo += bedrag;
        RekeningUittreksel(this);

    }
 public void Afhalen(decimal bedrag,Transactie actie,Transactie actie2)
    {
        Contract.Requires<NullReferenceException>(actie!=null,"\n\nNo event listeners have been added yet!\n\n");
        Contract.Requires<NullReferenceException>(actie2 != null, "\n\nNo event listeners have been added yet!\n\n");
        VorigSaldo = Saldo;
        if (bedrag <= Saldo)
            {
                Saldo -= bedrag;
                RekeningUittreksel(this);
            }
            else
            {
                NegatiefSaldo(this);
            }
    }

Next is part of the program main method. I commented out the lines where I add event listeners so the contract rules defined above will throw the nullreference exception. This is how to catch them without terminating with a big bang:

        //mijnZichtrekening.RekeningUittreksel += pietjePek.ToonUittreksel;
        //mijnZichtrekening.NegatiefSaldo += pietjePek.ToonNegatief;
        try
        {
            mijnZichtrekening.Storten(50m, mijnZichtrekening.RekeningUittreksel);
        }
        catch (NullReferenceException ex)
        {
            Console.WriteLine(ex);
        }
        try
        {
            mijnZichtrekening.Afhalen(100m, mijnZichtrekening.RekeningUittreksel, mijnZichtrekening.NegatiefSaldo);
        }
        catch(NullReferenceException ex)
        {
            Console.WriteLine(ex);
        }

I rewrote some code a bit to perform null reference checking on events by using the new .NET 4.5 contract abbreviators:

    public void Afhalen(decimal bedrag)
    {
        NegatiefSaldoHasListeners(this.RekeningUittreksel, this.NegatiefSaldo);//calls  the contract abbreviator with delegate type parameters to check for Nullreference
        VorigSaldo = Saldo;
        if (bedrag <= Saldo)
            {
                Saldo -= bedrag;
                RekeningUittreksel(this);
            }
            else
            {
                NegatiefSaldo(this);
            }
    }

    public void Storten(decimal bedrag)
    {
        UittrekselHasListeners(this.RekeningUittreksel);//calls the contract abbreviator with a delegate type (event) parameter to check for Nullreference

        VorigSaldo = Saldo;
        Saldo += bedrag;
        RekeningUittreksel(this);

    }


    public virtual void Afbeelden()
    {
        Console.WriteLine("Rekeningnr: {0:0000 0000 0000 0000}",Nummer);
        Console.WriteLine("Saldo:  {0}",Saldo);
        Console.WriteLine("Creatiedatum: {0:dd-MM-yyyy}",CreatieDatum);
    }

    [ContractAbbreviator]
    public void CheckArgs(string nummer, Klant eigenaar)
    {
        Contract.Requires<ArgumentException>(!String.IsNullOrWhiteSpace(nummer), "Geen nummer ingevuld!");
        Contract.Requires<FormatException>(nummer.Trim().Length == 16,"Ongeldig aantal tekens ingevoerd!");
        Contract.Requires<ArgumentException>(!String.IsNullOrWhiteSpace(eigenaar.ToString()), "Eigenaar niet opgegeven!");
    }

    [ContractAbbreviator]
    public void UittrekselHasListeners(Transactie actie)
    {
        Contract.Requires<NullReferenceException>(actie != null, "\n\nGeen event listener toegewezen!\n\n");
    }

    [ContractAbbreviator]
    public void NegatiefSaldoHasListeners(Transactie actie,Transactie actie2)
    {
        Contract.Requires<NullReferenceException>(actie != null, "\n\nGeen event listener toegewezen!\n\n");
        Contract.Requires<NullReferenceException>(actie2 != null, "\n\nGeen event listener toegewezen!\n\n");
    }
自此以后,行同陌路 2024-12-27 16:07:47

举个具体的例子,合约传达的是包含方法的参数不应该为空(理想情况下该方法的文档也会告诉你这一点)。在编写使用代码时,您应该认识到这一事实,并且如果您没有可用作方法参数的非空值,则不应调用该方法。然后由使用代码来决定在这种情况下做什么。它可以采用替代执行路径或抛出异常。正如 @sll 所说,在这种情况下操作的选择完全取决于应用程序需求所需的逻辑。

To take your concrete example, the contract is communicating that the argument to the containing method should not be null (and ideally the documentation of the method would also tell you this). When writing consuming code, you should recognize this fact, and if you do not have a non-null value available to pass as the argument to to the method then you should not call the method. It is then up to the consuming code to decide what to do in such a circumstance. It could either take an alternative execution path or throw an exception. As @sll says, the choice of action in this circumstance is completely dependent on the logic required by the needs of your application.

秉烛思 2024-12-27 16:07:47

除了 Pavel Gatilov 的回答之外,请记住,您始终可以针对先决条件抛出特定异常。

Contract.Requires<ArgumentException>(foo != null, "foo");

这些可以由用户捕获和处理,并且可以为他们提供处理无效输入的方法,例如:通过显示如果用户输入一些无效数据,则会向用户发出警告。

In addition to Pavel Gatilov's answer, remember that you can always throw specific exceptions for preconditions

Contract.Requires<ArgumentException>(foo != null, "foo");

These can be caught and handled by the user, and can provide them with a means of handling invalid input eg: by showing a warning to the user if he input some invalid data.

爱*していゐ 2024-12-27 16:07:47

我的主要问题是如何处理我抛出的异常?

首先,从技术上讲,您无法捕获这些异常。除 Contract.Requires() 之外的所有方法都会抛出 System.Diagnostics.Contracts.__ContractsRuntime.ContractException ,该异常嵌入到您的程序集中,并且是私人的。为了捕获它,您必须捕获所有异常,这是您能做的最糟糕的事情。

契约和断言都是必须始终为真的条件。如果不是,那么该程序就处于其设计目的之外的状态,并且您无法确定它是否可以安全地继续运行。您可以将契约视为语言的扩展。您不会期望 .NET 程序会让您在“特殊情况”下违反类型安全,对吗?合同也是如此。

函数调用的上层抛出此异常该怎么办?

契约的整个想法是强制调用者在调用带有契约的方法之前进行检查。如果调用者没有检查并且做了错误的事情 - 则必须修复它。我的意思是:如果您有一个带有 Contract.Requires(arg != null) 的方法,那么,如果您有一个 null 值,就不要调用它。

另一个问题是“你是否应该将所有合约保留在已发布的位中?”为了安全起见,你最好把它们都保留下来。

如果您的代码不需要某些值,但它得到了它们,那么唯一绝对安全的决定是因错误而停止当前操作。您无法确定如果您忽略合同,您不会损坏数据或做其他坏事。当然,您需要一定程度的粒度来让您的程序在安全状态下继续运行,而不是突然终止,尽管在某些情况下需要终止。

我应该忽略它,当我看到它时就知道我的设计有问题并修复它吗?

如果你发布你的软件并发现有一个用例由于合同失败而无法工作,那么即使没有合同它也可能无法工作 - 你只是没有考虑清楚并且必须做一些事情额外的工作来支持它。您应该仔细设计所有用例并进行彻底的质量检查以避免这种情况。合同与这些问题无关。

但是,如果我在没有合约的情况下进行部署,并且我将得到一个 foo == null 的参数,那么部署中会发生什么,并且我的逻辑不知道如何处理这样的参数。然后一切都会崩溃。

这是保留合同的另一个原因。在预先设计的地方发生事故比在你没有预料到的地方发生事故要好。

也许,删除某些契约的唯一重要原因是性能:在每个方法之后检查不变量可能会非常昂贵。

My main question is what to do with those Exceptions that I throw?

First of all, you technically cannot catch these exceptions. All methods except Contract.Requires<TExc>() throw a System.Diagnostics.Contracts.__ContractsRuntime.ContractException which is embedded into your assembly and is private. In order to catch it you'd have to catch all exceptions which is the worst thing you could do.

Contracts as well as assertions are conditions that must always be true. If they are not, then the program is in a state that it has not been designed for, and you cannot be sure if it can continue safely. You can think of contracts as of extensions to the language. You don't expect a .NET program to let you violate type safety in 'a special case', do you? The same is true for contracts.

What to do in uper levels of the function call with this thrown exception?

The whole idea of contracts is enforcing the caller to check before calling a method with contracts. And if the caller doesn't check and does something wrong - it must be fixed. I mean: if you have a method with Contract.Requires(arg != null), well, then don't call it if you have a null value.

Another question is 'should you leave all contracts in released bits or not?' From the safety position, you'd better keep them all.

If your code doesn't expect some values but it gets them, the only absolutely safe decision is halting the current operation with an error. You cannot be sure that if you ignore your contract you won't corrupt data or do other bad things. Of course, you need some degree of granularity to let your program continue in a safe state instead of terminating with a big bang, although in some cases termination is required.

Should I ignore it , and just when I see it know that something wrong with my design and fix it?

If you release your software and discover that there is a use case that doesn't work because of a contract failure, it probably wouldn't work even if there were no contracts - you just haven't thought it over and have to do some extra work to support it. You should worry about carefully designing all use cases and carrying out thorough QA to avoid this. Contracts have no relation to these issues.

But what would happen in deployment if I deploy without the Contracts and I will get an argument that is foo == null, and my logic has no clue how to deal with argument like this. Then everything will crash.

This is another reason for leaving the contracts in place. It's better to have a crash in a pre-designed place than somewhere you don't expect it to happen.

Perhaps, the only significant reason to remove some contracts is performance: checking invariants after each method may be very costly.

迷离° 2024-12-27 16:07:47

代码契约允许您更精确地声明您的方法接受哪些参数以及它返回什么(前置条件和后置条件)。您可以声明该字符串应该为非空、长度大于 10、完全由大写字符等组成,而不是使用接受字符串(任何字符串)的函数。

如果调用者不遵守合同中它是一个错误,应该这样报告(例如应该抛出异常)。但是,将 Contract.Requires() 语句放入源代码中不会生成任何实际的 IL 代码。您必须运行代码契约重写器来对代码进行后处理。这会将合同检查插入到最终的 IL 中,如果不遵守合同,这些检查将引发异常。

您还可以使用代码契约静态检查器来证明契约在整个代码中得到执行。如果这是真的,您可以指示重写者不要插入支票,因为您已经证明合同始终得到遵守。对于公共 API,您无法做到这一点,因为静态检查器不知道您的代码将如何被调用。但是,如果您在公共 API 上声明了代码契约,则调用者可以使用静态检查器来验证其代码是否正确。

因此,要回答您的问题,您应该期望呼叫者遵守您的合同。如果事实上调用者不遵守您的合同,您应该使用重写器插入检查并以受控方式失败。

MSDN 杂志有一篇关于代码契约的文章,这是一个很好的起点学习概念。

Code Contracts allow you to more precisely declare what arguments your method accepts and also what it returns (preconditions and postconditions). Instead of having a function that accepts a string (any string) you can declare that the string should be non-null, have a length greater than 10, be entirely made up of upper case characters etc.

If the caller doesn't adhere to the contract it is an error and should be reported as such (e.g. an exception should be thrown). However, putting Contract.Requires() statements in your source doesn't generate any actual IL code. You have to run the Code Contracts rewriter to postprocess your code. This will insert contracts check into the final IL and these checks will throw exceptions if the contract is not adhered to.

You can also use the Code Contracts static checker to prove that contracts are enforced throughout your code. If that is true you can instruct the rewriter to not insert checks as you have already proven that contracts are always adhered to. With a public API you cannot do that because the static checker don't know how your code will be called. However, if you have declare code contracts on your public API your caller can use the static checker to verify that his code is correct.

So to answer your question, you should expect your caller to adhere to your contract. You should use the rewriter to insert checks and fail in a controlled maner if in fact the caller doesn't adhere to your contract.

MSDN Magazine has an article about Code Contracts that is a good starting point for learning about the concepts.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文