Feel Good.

12 мая 2011

Проверяем входные данные

Все программисты знают, что пришедшие из внешнего источника данные всегда надо проверять. Например в качестве входного параметра для метода (свойства, конструктора), может приходить ссылка, которую нужно проверить на null, число, которое должно быть из определенного диапазона, или определенного вида строка. Несмотря на то, что по этому поводу даже существует методика разработки через Code Contracts, по-прежнему, с высокой вероятностью в проекте можно встретить код проверки на null аналогичный следующему:

class Bar {}

 

class Foo

{

    Bar _bar;

 

    public Foo(Bar bar)

    {

        // bar может быть null

        if (bar == null)

            throw new ArgumentNullException("bar");

 

        _bar = bar;

    }

}


С ростом проекта, подобных мест становиться все больше, и контроллировать создание исключений стоновиться сложнее. Одно из решений проблемы (для тех, кто пока еще не перешел на Code Contracts), сделать процесс создания исключений более централизованным, а именно в определенном классе - Guard (я остановился на названии Guard, но встречаются еще Assert, Check (проект KIGG CheckArgument.cs)):

public static class Guard

{

    public static T AssertNotNull<T>(T paramValue, string paramName) where T : class

    {

        if (paramValue == null)

        {

            throw new ArgumentNullException(paramName);

        }

        return paramValue;

    }

}


Принцип работы Guard следующий: если входной параметр удовлетворяет некому условию, то Guard пропускает параметр дальше, если нет, то будет сгенерированно исключение:

class Foo

{

    Bar _bar;

 

    public Foo(Bar bar)

    {

        // bar может быть null       

        _bar = Guard.AssertNotNull(bar, "bar");

    }

}


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

public static class Guard

{

    public static T AssertNotNull<T>(T paramValue, string paramName) where T : class

    {

        if (paramValue == null)

        {

            ArgumentNullException ex = new ArgumentNullException(paramName);

 

            OnAlert(ex); // сообщим об исключении, например сделаем запись во внутренний лог.

 

            throw ex;

        }

        return paramValue;

    }

}


Так же иногда бывает удобно пометить Assert методы специальным атрибутом:

[DebuggerStepThrough]

public static T AssertNotNull<T>(T paramValue, string paramName) where T : class

{

    //...

}


PS: Топик навеян статьей habrahabr: Концепция баррикады.

5 комментариев:

  1. Синтаксически стало выглядеть красивее, но смысл от этого не поменялся -- все те же самые проверки, которые программист должен писать вручную.
    Проверки на null -- общая беда мейнстримовых языков, и решить их в пределах технологии C# или Java -- нереально. Все равно самостоятельно придется находить опасные места и дописывать туда обработку null-ов.
    Особенно обидно, что технологии, позволяющей решить эту проблему, уже лет 50. Я говорю о семействе языков Lisp: Common Lisp, Scheme и Clojure. В ту же Clojure, например, такие проверки встроены в язык, и программисту ничего не нужно дополнительно делать вручную: http://clojuredocs.org/clojure_core/clojure.core/if-let.
    Но даже не это важно. Главное, что если бы такой функциональности не было, на любом из лиспов реализовать ее -- раз плюнуть. Пример: http://my-clojure.blogspot.com/2011/01/java-null.html.

    ОтветитьУдалить
  2. Я бы воздержался от таких резких комментариев и посоветовал автору поста выше почитать msdn

    http://msdn.microsoft.com/ru-ru/library/cc488527(v=vs.90).aspx

    http://msdn.microsoft.com/ru-ru/library/ee256141.aspx

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

    Из существующих "костылей" видел такое:
    http://brainster.org/dev/dotnet-code-contracts/

    ОтветитьУдалить
  3. @Andrey
    Хорошо, тогда приведите пожалуйста пример валидации через DataAnnotations конкретно для моего примера.

    ОтветитьУдалить
  4. @Andrey: Илья открывает возможность обработать некорректный параметр и по моему мнению делает это правильно.

    Для .net 4 мы получили класс контрактов
    System.Diagnostics.Contracts Namespace
    http://msdn.microsoft.com/en-us/library/dd287492.aspx
    (cмотрите доку в самом низу страницы)

    ОтветитьУдалить
  5. О Боже, ждать новой версии аж целого фреймворка для такой фигни, которую на нормальном языке добавить в этот самый язык -- дело двух минут... Blub-шиза в действии!..

    ОтветитьУдалить