пятница, 2 апреля 2010 г.

Сниппеты для проверки аргументов на null

Думаю, есть время поделиться моими любимыми и наиболее часто используемыми сниппетами.

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

Не секрет, что для упрощения проверок аргументов на null изобретено не мало средств: от использования возможностей языка\компилятора до специальных инструментов a-la CodeContracts, аннотоций ReSharper-а или колдунства PostSharp-а.

Мне всё это (пока что) кажется излишним оверхедом: тратить время на разбор выражений обидно, с контрактами действительно пока не всё понятно, завязываться же на инструмент третий стороны не хочется вовсе.

Итак, как делаю я: написав объявление метода и операторные скобки к нему, обозначив тело:

void MyMethod(object param1, object param2) {
  | // <- это позиция курсора в редакторе
}

…набираю "an" (это shortcut для сниппета), жму Tab и получаю:


if(ArgName == null) {
  throw new ArgumentNullException("ArgName");
}//if

Осталось ввести имя аргумента (курсор уже там, где нужно, в выделенном квадрате + помогает IntelliSense) и нажать Enter:

void MyMethod(object param1, object param2) {
  if(param1 == null) {
    throw new ArgumentNullException("param1");
  }//if
  |
}

Можно приступать к написанию тела метода.

Но если нужно проверить и второй параметр, то ставлю курсор перед закрывающей скобкой: }//if:

void MyMethod(object param1, object param2) {
  if(param1 == null) {
    throw new ArgumentNullException("param1");
 |}//if
}

…набираю "an2", жму Tab и получаю:

void MyMethod(object param1, object param2) {
  if(param1 == null) {
    throw new ArgumentNullException("param1");
  } else if(ArgName == null) {
    throw new ArgumentNullException("ArgName");
  }//if
}

Опять быстро с помощью IntelliSense ввожу имя второго параметра, жму Enter:

void MyMethod(object param1, object param2) {
  if(param1 == null) {
    throw new ArgumentNullException("param1");
  } else if(param2 == null) {
    throw new ArgumentNullException("param2");
  }//if
  |
}

Вот так вот несколькими нажатиями я "набираю" довольно много нужного кода.

В добавок к сниппетам "an" и "an2" у меня есть сниппеты ("ans" и "ans2" соответственно) для проверки строк, которые отличаются от первых тем, что вместо проверки на null в условии выполняется проверка аргумента с помощью String.IsNullOrEmpty(string).

Выглядит первый ("an") сниппет так:

<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>Throws ArgumentNullException</Title>
      <Shortcut>an</Shortcut>
      <Description>Code snippet for throws ArgumentNullException</Description>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
      <Author>Viacheslav.Ivanov@GMail.com</Author>
    </Header>
    <Snippet>
      <Declarations>
        <Literal>
          <ID>ArgumentName</ID>
          <ToolTip>Name of the argument</ToolTip>
          <Default>ArgName</Default>
        </Literal>
        <Literal Editable="false">
          <ID>ArgumentNullException</ID>
          <Function>SimpleTypeName(global::System.ArgumentNullException)</Function>
          <ToolTip>Type of the exception</ToolTip>
        </Literal>
      </Declarations>
      <Code Language="CSharp">
<![CDATA[if($ArgumentName$ == null) {
      throw new $ArgumentNullException$("$ArgumentName$");
    }//if
    $end$]]>
      </Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

Остальные сниппеты такие (привожу здесь только "значимую" часть, упустив заголовок):

Проверка второго и последующих аргументов на null ("an2"):

<Code Language="CSharp">
<![CDATA[} else if($ArgumentName$ == null) {
      throw new $ArgumentNullException$("$ArgumentName$");$end$]]>
</Code>

Проверка строки на IsNullOrEmpty ("ans"):

<Snippet>
  <Declarations>
    <Literal>
      <ID>ArgumentName</ID>
      <ToolTip>Name of the argument</ToolTip>
      <Default>ArgName</Default>
    </Literal>
    <Literal Editable="false">
      <ID>String</ID>
      <Function>SimpleTypeName(global::System.String)</Function>
      <ToolTip>System.String type</ToolTip>
    </Literal>
    <Literal Editable="false">
      <ID>ArgumentNullException</ID>
      <Function>SimpleTypeName(global::System.ArgumentNullException)</Function>
      <ToolTip>Type of the exception</ToolTip>
    </Literal>
  </Declarations>
  <Code Language="CSharp">
<![CDATA[if($String$.IsNullOrEmpty($ArgumentName$)) {
      throw new $ArgumentNullException$("$ArgumentName$");
    }//if
    $end$]]>
  </Code>
</Snippet>

Проверка второго и последующий аргументов строкового типа на IsNullOrEmpty ("ans2"):

<Code Language="CSharp">
<![CDATA[} else if($String$.IsNullOrEmpty($ArgumentName$)) {
      throw new $ArgumentNullException$("$ArgumentName$");$end$]]>
</Code>

Скачать готовые сниппеты можно отсюда. О том, как их подключить к MSVS можно прочитать в статье How to: Manage Code Snippets.

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

  1. Единственное непонятно: зачем нужен else в снипете, и, как следствие an2 ? Почему недостаточно одного an ?

    ОтветитьУдалить
  2. В посте есть ответ на этот вопрос, после слов "…набираю "an2", жму Tab и получаю…".

    Если пример нуждается в пояснениях, то следует сформулировать вопрос более точно.

    ОтветитьУдалить
  3. if(param1 == null) {
    throw new ArgumentNullException("param1");
    } __else__ if(ArgName == null) {
    throw new ArgumentNullException("ArgName");
    }//if


    зачем здесь вообще else и как следствие an2 ?

    p.s. куда уж более точно %)

    ОтветитьУдалить
  4. "else" там для того, что бы проверить второй аргумент - это же сказано в посте.

    ОтветитьУдалить
  5. попробуем иначе спросить: что будет если else убрать? что изменится?

    ОтветитьУдалить
  6. То есть вы на самом деле хотите спросить, зачем используется else, а не несколько if?

    Во-первых, с else вся проверка аргументов выглядит как одно предложение, как единое и неразрывное целое, а не несколько независимых предложений. Мне это кажется более правильным.

    Во-вторых, с else проверка аргументов занимает меньше строк. Обычно, идущие подряд if-ы я разделяю пустой строкой. В этом случае проверка двух аргументов "съест" семь строк вместо пяти (а если проверок нужно ещё больше или аргументов в методе не два, а три или четыре?).

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

    ОтветитьУдалить
  7. но все же осталась проблема - строка с именем параметра. при внесении изменений в имя параметра, можно забыть изменить строку... например, когда массово меняются названия, или при изменении кода, когда нужно добавить еще один параметр, и предыдущему сменить имя, чтобы уточнить его смысл, и т.д. и т.п. Приведенная ссылка на пост Uzzi показывает пример, как избежать строк, но что-то длинновато :))).

    ОтветитьУдалить
  8. Для меня изменения строк никогда небыли проблемой.

    Если всё же мешает, то можно воспользоваться решарпером (в старых версиях точно было, вряд ли выкинули) - он будет подствечивать несоответствие в аргументах ArgumentException.

    ОтветитьУдалить
  9. Прошу прощения за некропостинг, но не смог удержаться:
    void MyMethod(object param1, object param2) {
    if(param1 == null)
    throw new ArgumentNullException("param1");
    if(param2 == null)
    throw new ArgumentNullException("param2");
    |
    }
    Имхо - аноним прав, и без else, второго сниппета, здесь вполне можно обойтись.

    Хотя я бы вовсе не стал проверять аргументы в теле каждого метода, а вынес эту проверку в отдельный метод.

    ОтветитьУдалить
  10. Я, если вы заметили, не спорил с тем, что "и без else, второго сниппета, здесь вполне можно обойтись". Я даже _объяснил_ в одном из коментариев, почему считаю, что без него обходиться в данном случае _не нужно_.

    Кстати, не подскажите, приходилось ли вам вводить капчу для публикации сообщения?

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