Какова наилучшая практика в случае, если один аргумент равен нулю?
при проверке ввода методов, я использовал для проверки, является ли аргумент нулевым, и если это так, я бросаю ArgumentNullException. Я делаю это для каждого аргумента в списке, поэтому я получаю код вроде этого:
public User CreateUser(string userName, string password,
string Email, string emailAlerts,
string channelDescription)
{
if (string.IsNullOrEmpty(userName))
throw new ArgumentNullException("Username can't be null");
if (string.IsNullOrEmpty(Email))
throw new ArgumentNullException("Email can't be null");
//etc, etc, etc
}
Это нормально? Зачем мне это делать? Было бы хорошо, если бы я просто группировал все проверки и возвращал нулевое значение вместо того, чтобы бросать исключение? Какова наилучшая практика для решения этой проблемы?
PS: Я хочу изменить это, потому что с помощью длинных методов это очень утомительно.
Идеи?
Ответы
Ответ 1
Сделайте класс ArgChecker с чем-то вроде этого
ArgChecker.ThrowOnStringNullOrEmpty(userName, "Username");
где ThrowOnStringNullOrEmpty -
public static void ThrowOnStringNullOrEmpty(string arg, string name)
{
if (string.IsNullOrEmpty(arg))
throw new ArgumentNullException(name + " can't be null");
}
Вы также можете попробовать обработать список аргументов с помощью params arg, например:
public static void ThrowOnAnyStringNullOrEmpty(params string[] argAndNames)
{
for (int i = 0; i < argAndName.Length; i+=2) {
ThrowOnStringNullOrEmpty(argAndNames[i], argAndNames[i+1]);
}
}
и называйте это
ArgChecker.ThrowOnAnyStringNullOrEmpty(userName, "Username", Email, "email");
Ответ 2
Подход, который я использую, и я, возможно, взял из источника NHibernate, состоит в создании статического класса Guard
, который используется следующим образом:
public void Foo(object arg1, string arg2, int arg3)
{
Guard.ArgumentNotNull(arg1, "arg1");
Guard.ArgumentNotNullOrEmpty(arg2, "arg2");
Guard.ArgumentGreaterThan(arg3, "arg3", 0);
//etc.
}
public static class Guard
{
public static void ArgumentNotNull(object argument, string parameterName)
{
if (parameterName == null)
throw new ArgumentNullException("parameterName");
if (argument == null)
throw new ArgumentNullException(parameterName);
}
//etc.
}
Это сокращает массу мякины в начале методов и хорошо работает.
Ответ 3
Вы должны подумать о методе, о том, что ему нужно делать и с какими данными. Если нулевые значения представляют собой фактические условия отказа, используйте исключения. Если допустимы нулевые значения, примите их.
Подумайте о принципах дизайн по контракту, в частности, какие предпосылки для вашей функции и стандартизировать способ их принудительного исполнения (что Мэтт и Лу оба предлагают в своих ответах, поэтому мне не нужно вдаваться в подробности).
Еще одна важная вещь, которую следует учитывать, - это размер подписей вашего метода. Если у вас много параметров для ваших методов, это, вероятно, означает, что у вас плохие абстракции. Вы можете сократить количество проверок параметров, которые вы должны выполнить, если группировать параметры вместе в объектах коллекции и использовать эти объекты в качестве параметров. Вы можете перенести проверку параметров на эти объекты, а не проверять их в каждой функции, которая их использует.
Таким образом, вместо того, чтобы передавать десять связанных параметров каждой функции, выясните те немногие, которые используются в каждой функции, и упакуйте их в объект, и включите в этот объект методы для проверки параметров. Это имеет дополнительное преимущество в том, что его легко изменить, если правила, касающиеся одного параметра, необходимо обновить.
Ответ 4
И для разработчиков С# 3.0 среди нас отличный способ инкапсулировать эту нулевую проверку внутри метода расширения.
public void Foo(string arg1, int? arg2)
{
arg1.ThrowOnNull();
arg2.ThrowOnNull();
}
public static class extensions
{
public static void ThrowOnNull<T>(this T argument) where T : class
{
if(argument == null) throw new ArgumentNullException();
}
}
И если вы хотите, вы всегда можете перегрузить это, чтобы принять имя аргумента.
Ответ 5
Небольшое улучшение ответа Лу будет заключаться в том, чтобы вместо этого использовать хеш-таблицу, это означает, что он проверяет объекты, а также просто строки. Также лучше всего заполнить и обработать метод:
public static class ParameterChecker
{
public static void CheckForNull(Hashtable parameters)
{
foreach (DictionaryEntry param in parameters)
{
if (param.Value == null || string.IsNullOrEmpty(param.Value as string))
{
throw new ArgumentNullException(param.Key.ToString());
}
}
}
}
Как вы бы хотели использовать:
public User CreateUser(string userName, string password, string Email, string emailAlerts, string channelDescription)
{
var parameters = new Hashtable();
parameters.Add("Username", userName);
parameters.Add("Password", password);
parameters.Add("EmailAlerts", emailAlerts);
parameters.Add("ChannelDescription", channelDescription);
ParameterChecker.CheckForNull(parameters);
// etc etc
}
Ответ 6
Я бы придерживался вашего первоначального подхода, за исключением простого указания имени параметра. Причина в том, что как только вы начинаете писать эти вспомогательные процедуры, это становится проблемой, когда каждый начинает использовать разные соглашения для того, как они пишут вспомогательные процедуры. Когда кто-то просматривает ваш код, они теперь должны проверить, чтобы убедиться, что вы правильно написали вспомогательную процедуру при отладке кода.
Продолжайте проверять каждый аргумент отдельно, хотя ваши пальцы устают от ввода Grasshopper:) Ваши последователи благословит вас, когда они получат неожиданное исключение ArgumentException и будут сохранены из прогона отладки, чтобы определить, какой аргумент не удалось.
Ответ 7
Мой первый совет для вас - получить ReSharper. Он скажет вам, когда есть проблема возможных нулевых значений, и когда нет необходимости проверять их, и щелчком мыши добавится проверка. Сказав это...
Обычно вам не нужно проверять нулевые строки и целые числа, если только вы не получаете информацию из какого-либо старого источника.
Строки можно проверить с помощью string.IsNullOrEmpty()...
Если вы все еще решите, что хотите проверить каждый параметр, вы можете использовать шаблон проектирования команды и отражение, но ваш код будет излишне неуклюжим или использовать следующее и вызывать его для каждого метода: private myType myMethod (строка param1, int param2, byte [] param3) { CheckParameters ( "myMethod", {param1, param2, param3}); // остальная часть кода...
И в вашем классе утилиты поместите это:
///<summary>Validates method parameters</summary>
///... rest of documentation
public void CheckParameters(string methodName, List<Object> parameterValues)
{
if ( string.IsNullOrEmpty(methodName) )
throw new ArgumentException("Fire the programmer! Missing method name", "methodName"));
Type t = typeof(MyClass);
MethodInfo method = t.GetMethod(methodName);
if ( method == null )
throw new ArgumentException("Fire the programmer! Wrong method name", "methodName"));
List<ParameterInfo> params = method.GetParameters();
if ( params == null || params.Count != parameterValues.Count )
throw new ArgumentException("Fire the programmer! Wrong list of parameters. Should have " + params.Count + " parameters", "parameterValues"));
for (int i = 0; i < params.Count; i++ )
{
ParamInfo param = params[i];
if ( param.Type != typeof(parameterValues[i]) )
throw new ArgumentException("Fire the programmer! Wrong order of parameters. Error in param " + param.Name, "parameterValues"));
if ( parameterValues[i] == null )
throw new ArgumentException(param.Name + " cannot be null");
}
} // enjoy