Класс Static Throw: хорошая или плохая практика - PullRequest
14 голосов
/ 08 ноября 2011

Бросок исключений часто следует следующему шаблону:

if(condition) { throw exception; }

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

public static class Throw
{
    public static void IfNullOrEmpty<T>(string @string, params object[] parameters) where T : Exception
    {
        Throw.If<T>(string.IsNullOrEmpty(@string), parameters);
    }

    public static void IfNullOrEmpty<T, I>(IEnumerable<I> enumerable, params object[] parameters) where T : Exception
    {
        Throw.If<T>(enumerable == null || enumerable.Count() == 0, parameters);
    }

    public static void IfNullOrEmpty(string @string, string argumentName)
    {
        Throw.IfNullOrEmpty(@string, argumentName, 
            string.Format("Argument '{0}' cannot be null or empty.", argumentName));
    }

    public static void IfNullOrEmpty(string @string, string argumentName, string message)
    {
        Throw.IfNullOrEmpty<ArgumentNullOrEmptyException>(@string, message, argumentName);
    }

    public static void IfNullOrEmpty<I>(IEnumerable<I> enumerable, string argumentName)
    {
        Throw.IfNullOrEmpty(enumerable, argumentName, 
            string.Format("Argument '{0}' cannot be null or empty.", argumentName));
    }

    public static void IfNullOrEmpty<I>(IEnumerable<I> enumerable, string argumentName, string message)
    {
        Throw.IfNullOrEmpty<ArgumentNullOrEmptyException, I>(enumerable, message, argumentName);
    }


    public static void IfNull<T>(object @object, params object[] parameters) where T : Exception
    {
        Throw.If<T>(@object == null, parameters);
    }

    public static void If<T>(bool condition, params object[] parameters) where T : Exception
    {
        if (condition) 
        {
            var types = new List<Type>();
            var args = new List<object>();
            foreach (object p in parameters ?? Enumerable.Empty<object>())
            {
                types.Add(p.GetType());
                args.Add(p);
            }

            var constructor = typeof(T).GetConstructor(types.ToArray());
            var exception = constructor.Invoke(args.ToArray()) as T;
            throw exception;
        }
    }

    public static void IfNull(object @object, string argumentName)
    {
        Throw.IfNull<ArgumentNullException>(@object, argumentName);
    }
}

(Примечание: ArgumentNullOrEmptyException здесь не определено, но это в значительной степени то, чтоможно было бы ожидать.)

поэтому вместо того, чтобы постоянно писать что-то подобное

void SomeFunction(string someParameter)
{
   if(string.IsNullOrEmpty(someParameter))
   {
      throw new ArgumentNullOrEmptyException("someParameter", "Argument 'someParameter' cannot be null or empty.");
   }
}

я просто делаю

void SomeFunction(string someParameter)
{
   Throw.IfNullOrEmpty(someParameter, "someParameter"); // not .IsNullOrEmpty
}

мне действительно нравится это, но так ли этохорошая практика?

Ответы [ 3 ]

12 голосов
/ 08 ноября 2011

Таким способом вы избавляетесь от некоторого дублирования кода (if ... throw), так что в этом смысле это хорошая идея.Просто знайте, что люди, работающие над кодом, должны знать API Throw, чтобы иметь возможность читать и понимать код.

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

Например, в моем текущем проекте с домашним животным у меня есть класс Guardбит):

public static class Guard
{
    public static void NotNullOrEmpty(Expression<Func<string>> parameterExpression)
    {
        string value = parameterExpression.Compile()();
        if (String.IsNullOrWhiteSpace(value))
        {
            string name = GetParameterName(parameterExpression);
            throw new ArgumentException("Cannot be null or empty", name);
        }
    }

    public static void NotNull<T>(Expression<Func<T>> parameterExpression)
        where T : class
    {
        if (null == parameterExpression.Compile()())
        {
            string name = GetParameterName(parameterExpression);
            throw new ArgumentNullException(name);
        }
    }

    private static string GetParameterName<T>(Expression<Func<T>> parameterExpression)
    {
        dynamic body = parameterExpression.Body;
        return body.Member.Name;
    }
}

Который я могу затем использовать следующим образом:

Guard.NotNull(() => someParameter);
7 голосов
/ 08 ноября 2011

В этом шаблоне нет ничего плохого, и я видел, как это делается во многих приложениях. Это в основном вопрос личного стиля.

Однако с этим шаблоном следует помнить одну вещь: он меняет семантику perf для строк ресурсов. Это приложения / библиотеки, которые имеют локализованные сообщения об ошибках, шаблон

if (...) {
  throw new ArgumentExecption("paramName", LoadSomeResource(ErrorId));
}

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

6 голосов
/ 08 ноября 2011

Я бы вместо этого рассмотрел возможность использования Кодовых контрактов .

...