Кодекс Контракты, что делать с исключениями - PullRequest
4 голосов
/ 10 декабря 2011

Мой главный вопрос: что делать с теми исключениями, которые я выбрасываю?

Например:

 Contract.Requires(foo != null);

Что делать на уровнях вызова функции с этим брошенным исключением?

Должен ли я игнорировать это, и как только я увижу, что что-то не так с моим дизайном и исправлю это?

Но что произойдет при развертывании, если я разверну без контрактов и получуаргумент foo == null, и моя логика не имеет ни малейшего представления о том, как обращаться с таким аргументом. Тогда все рухнет.

Может ли кто-нибудь объяснить, как поступать со всеми этими сценариями?

Спасибо

Ответы [ 5 ]

18 голосов
/ 10 декабря 2011

Мой главный вопрос: что делать с теми исключениями, которые я выбрасываю?

Прежде всего, технически вы не можете поймать эти исключения.Все методы, кроме Contract.Requires<TExc>(), выдают System.Diagnostics.Contracts.__ContractsRuntime.ContractException, который встроен в вашу сборку и является закрытым.Чтобы поймать его, вам нужно перехватить все исключения, что является худшим, что вы можете сделать.

Контракты и утверждения - это условия, которые должны всегдабыть правдой.Если это не так, то программа находится в состоянии, для которого она не была разработана, и вы не можете быть уверены, что она может продолжаться безопасно.Вы можете думать о контрактах как о расширении языка.Вы не ожидаете, что программа .NET позволит вам нарушать безопасность типов в «особом случае», не так ли?То же самое верно для контрактов.

Что делать на верхних уровнях вызова функции с этим сгенерированным исключением?

Вся идея контрактов заключается в применении caller для проверки перед вызовом метода с контрактами.И если звонящий не проверяет и делает что-то не так - это нужно исправить.Я имею в виду: если у вас есть метод с Contract.Requires(arg != null), то не вызывайте его, если у вас есть значение null.

Другой вопрос: «следует ли оставлять все контракты в освобожденных битах или нет??С точки зрения безопасности вам лучше оставить их всех.

Если ваш код не ожидает каких-то значений, но получает их, единственное абсолютно безопасное решение - остановить текущую операцию с ошибкой.Вы не можете быть уверены, что, если вы проигнорируете свой контракт, вы не будете портить данные или делать другие плохие вещи.Конечно, вам нужна некоторая степень детализации, чтобы ваша программа продолжала работать в безопасном состоянии, а не заканчивалась большим взрывом, хотя в некоторых случаях требуется завершение.

Должен ли я игнорировать это, и простокогда я вижу, что что-то не так с моим дизайном и исправляю это?

Если вы выпускаете свое программное обеспечение и обнаруживаете, что есть вариант использования, который не работает из-за сбоя контракта, это, вероятно,не сработает, даже если бы не было контрактов - вы просто не обдумали это и должны были сделать дополнительную работу, чтобы поддержать это.Вы должны заботиться о тщательном проектировании всех вариантов использования и проведении тщательного контроля качества, чтобы избежать этого.Контракты не имеют никакого отношения к этим вопросам.

Но что произойдет при развертывании, если я разверну без контрактов, и я получу аргумент foo == null, и моя логика не имеет ни малейшего понятия, какиметь дело с аргументом, как это.Тогда все рухнет.

Это еще одна причина оставить контракты на месте.Лучше иметь сбой в заранее спроектированном месте, чем там, где вы этого не ожидаете.

Возможно, единственной существенной причиной для удаления некоторых контрактов является производительность: проверка инвариантов после каждого метода может быть оченьдорогостоящий.

3 голосов
/ 10 декабря 2011

Кодовые контракты позволяют вам более точно объявить, какие аргументы принимает ваш метод, а также что он возвращает (предусловия и постусловия).Вместо функции, которая принимает строку (любую строку), вы можете объявить, что строка должна быть ненулевой, иметь длину больше 10, полностью состоять из символов верхнего регистра и т. Д.

Есливызывающая сторона не придерживается контракта, это ошибка и должна быть сообщена как таковая (например, должно быть выдано исключение).Тем не менее, размещение Contract.Requires() операторов в вашем источнике не генерирует никакого реального кода IL.Вы должны запустить программу переписки Code Contracts для постобработки вашего кода.Это будет вставлять проверку контрактов в окончательный IL, и эти проверки будут генерировать исключения, если контракт не соблюдается.

Вы также можете использовать статический контролер Code Contracts, чтобы доказать, что контракты применяются во всем коде.Если это так, вы можете поручить переписчику не вставлять чеки, поскольку вы уже доказали, что контракты всегда соблюдаются.С открытым API вы не можете этого сделать, потому что статическая проверка не знает, как будет вызываться ваш код.Однако, если вы объявляете контракты кода в своем общедоступном API, ваш вызывающий абонент может использовать статическую проверку, чтобы проверить правильность своего кода.

Поэтому, чтобы ответить на ваш вопрос, вы должны ожидать, что вызывающий абонент будет придерживаться вашего контракта.Вы должны использовать переписывающее устройство для вставки проверок и сбоя в контролируемой манере, если на самом деле вызывающая сторона не придерживается вашего контракта.

В журнале MSDN есть статья о Кодовых договорах , которая являетсяХорошая отправная точка для изучения концепций.

2 голосов
/ 13 декабря 2011

Помимо ответа Павла Гатилова, помните, что вы всегда можете выбросить определенные исключения для предварительных условий

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

Эти могут быть перехвачены и обработаны пользователем и могут предоставить им средства для обработки неверного ввода, например: путем показа пользователю предупреждения, если он вводит некоторые неверные данные.

0 голосов
/ 07 мая 2014

Я тоже искал решение о том, как отлавливать исключения, выдвигаемые условиями контракта. Это всегда хорошая идея явно бросить исключения, которые могут возникнуть. Хотите ли вы их поймать, чтобы ваш код не останавливался на ударе, зависит от того, какие данные проверены. Я также использую контракты для проверки пользовательского ввода. С помощью предварительных условий контракта вы можете принудительно вводить пользовательский ввод в соответствии с определенными требованиями (например, не нулевой или пустой строкой). Во-вторых, вы можете использовать контракты для проверки собственного внутреннего кода (особенно вычислений) и принудительного применения не только ввода параметров, но и результата действительных расчетов.

Можно отловить исключения, выдвинутые условиями договора ; просто поместите вызывающий код в блок 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);
            }
    }

Далее - часть основного метода программы. Я закомментировал строки, в которые я добавляю прослушиватели событий, чтобы правила контракта, определенные выше, выдавали исключение nullreference . Вот как их поймать без большого взрыва:

        //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");
    }
0 голосов
/ 10 декабря 2011

Чтобы взять ваш конкретный пример, в контракте сообщается, что аргумент к содержащемуся методу не должен быть нулевым (и в идеале документация о методе также скажет вам это).При написании потребляющего кода вы должны признать этот факт, и если у вас нет ненулевого значения, доступного для передачи в качестве аргумента методу, то вы не должны вызывать метод.Затем код потребителя решает, что делать в таких обстоятельствах.Это может быть альтернативный путь выполнения или исключение.Как говорит @sll, выбор действия в этих обстоятельствах полностью зависит от логики, необходимой для вашего приложения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...