Использует плохую практику

Я только что добавил логический параметр out в метод, который я написал, чтобы получить предупреждение в своем пользовательском интерфейсе. Я использовал out вместо того, чтобы заставить сам метод возвращать false/true, так как это означало бы, что DoSomething завершился неудачно/успешно. Я думал, что warnUser укажет, что на самом деле представляет собой предупреждение, не глядя на реализацию метода.

Исходный код

public void DoSomething(int id, string input);

Новый код

public void DoSomething(int id, string input, out bool warnUser);

Я использую Moq для тестирования этого кода, но он не поддерживает параметры out/ref, поскольку они не поддерживаются лямбда-выражениями.

Тестовый код

mockService.Verify(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<bool>());

Итак, является ли использование параметров out плохой практикой, и если да, то что мне делать вместо этого?


person Antony Scott    schedule 23.11.2010    source источник
comment
Как bool указывает, что на самом деле было предупреждением?   -  person Cody Gray    schedule 23.11.2010
comment
@Cody - потому что это значимое имя в моем реальном коде :)   -  person Antony Scott    schedule 23.11.2010


Ответы (7)


Использование параметра out в методе void обычно является плохой идеей. Вы говорите, что использовали его «вместо того, чтобы заставить сам метод возвращать false/true, поскольку это означало бы, что DoSomething не удалось/удалось» - я не верю, что это подразумевается. Обычно в .NET сбой указывается через исключение, а не через true/false.

out параметры, как правило, более уродливы в использовании, чем возвращаемые значения - в частности, у вас должна быть переменная правильного типа для обработки, поэтому вы не можете просто написать:

if (DoSomething(...))
{
   // Warn user here
}

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

public enum WarningLevel
{
    NoWarningRequired,
    WarnUser
}

Тогда метод мог бы вернуть WarningLevel вместо bool. Это прояснит, что вы имели в виду, хотя вы, возможно, захотите немного переименовать вещи. (Трудно давать советы с метасинтаксическими именами, такими как «DoSomething», хотя я полностью понимаю, почему вы использовали это здесь.)

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

person Jon Skeet    schedule 23.11.2010
comment
Я не согласен с последствиями неудачи. если метод возвращает логическое значение, для меня это означает, что возврат false означает, что он потерпел неудачу. Но я согласен с тем, что на сбой обычно указывает выбрасываемое исключение (предпочтительно строго типизированное). Тем не менее, это похоже на идею перечисления, я должен был признать это сам, поскольку я использовал этот подход в прошлом. Благодарю. - person Antony Scott; 23.11.2010
comment
+1 за предложение WarningLevel. Это устранило бы путаницу, которая беспокоит спрашивающего в отношении того, что возвращаемое значение интерпретируется как успех или неудача функции. - person Cody Gray; 23.11.2010
comment
Я принял этот ответ, так как мой код вызова будет гораздо более читаемым. if (!DoSomething(...) vs if (DoSomething(...) == WarningLevel.WarnUser). Очевидно, у меня было бы что-то более значимое, чем WarnUser - person Antony Scott; 23.11.2010

out очень полезная конструкция, в частности в таких шаблонах, как bool TryGetFoo(..., out value), где вы хотите знать "если" и "что" по отдельности (и значение NULL не является обязательно вариант).

Однако - в таком случае, почему бы просто не сделать это:

public bool DoSomething(int id, string input);

и использовать возвращаемое значение, чтобы сигнализировать об этом?

person Marc Gravell    schedule 23.11.2010
comment
Как я уже сказал, я не хотел подразумевать, что Нечто потерпело неудачу/успех. - person Antony Scott; 23.11.2010
comment
@Marc Gravell Я бы сказал, что в .NET 4 использование Tuple‹bool,MyThing› может быть более понятным, поскольку ввод (аргументы метода) отделен от вывода (возвращаемые значения) и по-прежнему предоставляет, если и что. И, конечно же, кортежи прекрасно работают в лямбда-выражениях. - person Damian Powell; 23.11.2010
comment
@Antony - я не уверен, что понимаю значение этого комментария; вы делаете, однако, кажется, что возвращается ровно одно значение - зачем использовать out, если для этого можно использовать return? - person Marc Gravell; 23.11.2010
comment
это просто не правильно для меня. мне нравится, когда мой код читается естественно, если это вообще возможно. и наличие if (!DoSomething(...) выглядит как неспособность что-то сделать для меня. - person Antony Scott; 23.11.2010
comment
@Antony - тогда рассмотрите возможность переименования метода, чтобы он действительно имел смысл; или, честно говоря, свыкнуться с этим ;p Возвращаемое значение не означает неудачу (это не C/C++) - это означает, что это возвращаемое значение. Тот факт, что это bool, значения не имеет. - person Marc Gravell; 23.11.2010
comment
@Antony (выброшенный Exception будет означать провал) - person Marc Gravell; 23.11.2010
comment
Я согласен с Дамианом в пользу Tuple. Я бы хотел, чтобы C# автоматически переводил это для меня, как это делает F# :) По общему признанию, для TryXXX я вполне могу использовать стандартный шаблон просто потому, что в наши дни он является стандартным шаблоном :( - person Jon Skeet; 23.11.2010

Несколько дополнительных мыслей:

  • Что говорит FxCop: CA1021: Избегайте внешних параметров

  • Для приватных/внутренних методов (детали реализации) параметры out/ref не представляют особой проблемы.

  • Кортежи C# 7 часто являются лучшей альтернативой out параметрам

  • Кроме того, C# 7 улучшает обработку out параметра на сайте вызова, вводя "выходные переменные" и позволяя "отбрасывать" out параметры

person ulrichb    schedule 23.11.2010

Учитывая большой устаревший метод кода (скажем, 15+ строк, устаревшие методы кода с 200+ строками довольно распространены), можно использовать рефакторинг «метод извлечения», чтобы очистить и сделать код менее хрупким и более удобным в сопровождении.

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

Лично я использую это обстоятельство для сильного индикатора того, что в предыдущем методе был спрятан один или несколько дискретных классов, поэтому я могу использовать «Извлечь класс», где в новом классе эти выходные параметры получают свойства, видимые вызывающему ( предыдущий) метод.

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

Итак, в таком сценарии выходные параметры, на мой взгляд, являются нарушением SRP с запахом кода.

person Michael Würthner    schedule 23.02.2016

Это действительно сложный вопрос, на который невозможно ответить, не зная контекста.

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

Чисто субъективно, я старался держаться подальше от параметров. Я думаю, что они нарушают поток кода и делают его немного менее ясным.

person Damian Powell    schedule 23.11.2010

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

public class MyWarningException : Exception() {}

...

try
{ 
     obj.DoSomething(id,input);
}
catch (MyWarningException ex)
{
     // do stuff
}

Идея заключалась бы в том, что исключение возникает в конце реализации DoSomething, чтобы поток не останавливался в середине.

person Daniel Perez    schedule 23.11.2010
comment
Исключение следует создавать только в том случае, если метод не может делать то, для чего он предназначен, ИМО. Если это удалось, но по какой-то причине пользователю должно быть выдано предупреждение, для меня это не похоже на исключительную ситуацию. - person Jon Skeet; 23.11.2010
comment
@ykatchou на самом деле это не настолько медленно; но это не очень хорошая практика для общего логического потока. - person Marc Gravell; 23.11.2010
comment
Я не могу использовать исключение, так как это может привести к нежелательным побочным эффектам в моем коде пользовательского интерфейса, который вызывает метод DoSomething. - person Antony Scott; 23.11.2010
comment
Джон, исключение указывает на поведение. Если вы ожидаете такого поведения, то это не значит, что метод не может работать?? - person Daniel Perez; 23.11.2010
comment
какие побочные эффекты? если вы обрабатываете конкретное исключение, побочных эффектов нет. если исключение другого типа (каким бы оно ни было), то оно всплывет в стеке вызовов, как обычно - person Daniel Perez; 23.11.2010
comment
@Daniel Прошу прощения за то, что дал больше контекста в моем вопросе, но мой код уже обрабатывает исключения. В коде контроллера (это на веб-сайте MVC) он перенаправляется на другое действие в зависимости от того, было ли обработано исключение или нет. - person Antony Scott; 23.11.2010
comment
@Daniel: Ничто в контексте, который нам дали, не указывает на то, что это отображение предупреждения для пользователя означает сбой. Правда, здесь у нас не так много подробностей, но, судя по тому, что нам сказали, исключения в данном случае неуместны. - person Jon Skeet; 23.11.2010
comment
ой, это должно было быть прочитано, я извиняюсь за не предоставление большего контекста в моем вопросе - person Antony Scott; 23.11.2010

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

public void DoSomething(int id, string input, Action warnUser);

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

person Juozas Kontvainis    schedule 23.11.2010