Булевы как аргументы метода неприемлемы?

Мой коллега заявляет, что логические значения в качестве аргументов метода неприемлемы. Они должны быть заменены перечислениями. Сначала я не видел никакой пользы, но он привел мне пример.

Что легче понять?

file.writeData( data, true );

или

enum WriteMode {
  Append,
  Overwrite
};

file.writeData( data, Append );

Теперь я понял!;-)
Это, безусловно, пример, когда перечисление в качестве второго параметра делает код более читаемым.

Итак, каково ваше мнение по этой теме?

Ответ 1

Boolean - это выбор "да/нет". Если вы хотите представить "да/нет", используйте логическое значение, оно должно быть понятным.

Но если это выбор между двумя опциями, ни один из которых явно да или нет, тогда переименование иногда может быть более читаемым.

Ответ 2

Перечисления также допускают будущие модификации, где теперь вам нужен третий вариант (или более).

Ответ 3

Используйте тот, который лучше всего моделирует вашу проблему. В приведенном примере перечисление - лучший выбор. Однако были бы другие времена, когда логическое было бы лучше. Что имеет для вас больше смысла:

lock.setIsLocked(True);

или

enum LockState { Locked, Unlocked };
lock.setLockState(Locked);

В этом случае я мог бы выбрать логическую опцию, так как я думаю, что она достаточно ясная и однозначная, и я уверен, что мой замок не будет иметь более двух состояний. Тем не менее, второй вариант действителен, но излишне усложнен, ИМХО.

Ответ 4

Я думаю, вы почти ответили на это сами, я думаю, что конечная цель - сделать код более читаемым, и в этом случае enum сделал это, IMO всегда лучше смотреть на конечную цель, а не на общие правила, может быть, подумать это скорее как ориентир, то есть перечисления часто более читабельны в коде, чем общие bools, int и т.д., но всегда будут исключения из правила.

Ответ 5

Запомните вопрос, который Адлай Стивенсон поставил перед послом Зориным в США во время кубинского ракетного кризиса?

"Ты в зале суда мира сейчас, и вы можете ответить да или нет. Вы отрицали, что [ракеты] существуют, и я хочу знать, правильно поняли... Я готов ждать ответа до тех пор, пока ад замерзает, если решение".

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

Не делайте его логическим в случае чего-то, что является переключателем режима . Всегда существует еще один режим, чем вы думали при написании метода в первую очередь.

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

Ответ 6

Для меня, ни с помощью логического, ни для перечисления не является хорошим подходом. Роберт К. Мартин очень четко фиксирует это в "Чистый код" . Совет № 12: "Исключить логические аргументы" :

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

Если метод выполняет несколько действий, вы должны написать два разных метода, например, в вашем случае: file.append(data) и file.overwrite(data).

Использование перечисления не делает вещи более ясными. Это ничего не меняет, это еще аргумент флага.

Ответ 7

Есть две причины, по которым я столкнулся с этим:

  • Потому что некоторые люди будут писать такие методы, как:

    ProcessBatch(true, false, false, true, false, false, true);
    

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

  • Поскольку управление потоком программы с помощью простой ветки yes/no может означать, что у вас есть две совершенно разные функции, которые обернуты в один с помощью awkard. Например:

    public void Write(bool toOptical);
    

    Действительно, это должны быть два метода

    public void WriteOptical();
    public void WriteMagnetic();
    

    потому что код в них может быть совершенно другим; им, возможно, придется выполнять различные виды обработки ошибок и проверки, или, возможно, даже форматировать исходящие данные по-разному. Вы не можете сказать, что просто используя Write() или даже Write(Enum.Optical) (хотя, конечно, вы могли бы использовать любой из этих методов только для вызова внутренних методов WriteOptical/Mag, если хотите).

Я думаю, это просто зависит. Я бы не стал делать слишком много дел, кроме # 1.

Ответ 8

Перечисления лучше, но я бы не назвал логические параметры "неприемлемыми". Иногда просто проще бросать один маленький булев и двигаться дальше (думаю, частные методы и т.д.)

Ответ 9

Булевы могут быть в порядке на языках с именованными параметрами, такими как Python и Objective-C, поскольку имя может объяснить, что делает этот параметр:

file.writeData(data, overwrite=true)

или

[file writeData:data overwrite:YES]

Ответ 10

Я бы не согласился, что это хорошее правило . Очевидно, что в некоторых случаях Enum делает более ясный или подробный код, но, как правило, кажется, что он достигает цели.

Сначала позвольте мне привести ваш пример: Ответственность программистов (и умение) писать хороший код на самом деле не подвержена риску, имея параметр Boolean. В вашем примере программист мог написать так же, как подробный код, написав:

dim append as boolean = true
file.writeData( data, append );

или я предпочитаю более общий

dim shouldAppend as boolean = true
file.writeData( data, shouldAppend );

Во-вторых: Пример Enum, который вы дали, только "лучше", потому что вы передаете CONST. Скорее всего, в большинстве приложений по крайней мере некоторые, если не большинство параметров времени, передаваемых в функции, являются ПЕРЕМЕННЫМИ. в этом случае мой второй пример (дающий переменные с хорошими именами) намного лучше, и Enum предоставил вам небольшие преимущества.

Ответ 11

Перечисления имеют определенную выгоду, но вы должны просто заменить все свои логические строки на перечисления. Есть много мест, где true/false на самом деле является лучшим способом представления того, что происходит.

Однако использование их в качестве аргументов метода немного подозрительно, просто потому, что вы не можете видеть, не копаясь в вещах, что они должны делать, поскольку они позволяют вам видеть, что на самом деле означает true/false

Свойства (особенно с инициализаторами объектов С# 3) или аргументы ключевых слов (a la ruby ​​или python) - намного лучший способ пойти туда, где вы использовали бы логический аргумент.

Пример С#:

var worker = new BackgroundWorker { WorkerReportsProgress = true };

Пример Ruby

validates_presence_of :name, :allow_nil => true

Пример Python

connect_to_database( persistent=true )

Единственное, что я могу думать о том, где аргумент логического метода - это правильная вещь, - это java, где у вас нет ни свойств, ни аргументов ключевого слова. Это одна из причин, по которым я ненавижу java: - (

Ответ 12

Хотя верно, что во многих случаях перечисления являются более читабельными и более расширяемыми, чем логические, абсолютное правило, что "логические значения неприемлемы" глупо. Он негибкий и контрпродуктивный - он не оставляет места для человеческого суждения. Они основаны на встроенном типе на большинстве языков, потому что они полезны - подумайте о применении его к другим встроенным типам: говоря, например, "никогда не использовать int как параметр" будет просто сумасшедшим.

Это правило - это только вопрос стиля, а не вероятность ошибок или производительности во время выполнения. Лучшим правилом было бы "предпочитать перечисления для булевых элементов по соображениям удобочитаемости".

Посмотрите на .Net framework. Булевы используются в качестве параметров по нескольким методам. API.Net не идеален, но я не думаю, что использование булевых параметров является большой проблемой. Всплывающая подсказка всегда дает вам имя параметра, и вы также можете создать такое руководство - заполните ваши комментарии XML по параметрам метода, они появятся в подсказке.

Я также должен добавить, что есть случай, когда вам нужно четко рефакторировать логические значения для перечисления - когда у вас есть два или более логических элемента в вашем классе или в ваших параметрах метода, и не все состояния действительны (например, это недействительно чтобы они оба установили true).

Например, если ваш класс имеет такие свойства, как

public bool IsFoo
public bool IsBar

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

enum FooBarType { IsFoo, IsBar, IsNeither };

Ответ 13

Некоторые правила, которые лучше всего придерживаются ваш коллега, заключаются в следующем:

  • Не будьте догматичны с вашим дизайном.
  • Выберите, что наиболее подходит для пользователей вашего кода.
  • Не пытайтесь bash звездообразные колышки в каждое отверстие только потому, что вам нравится форма в этом месяце!

Ответ 14

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

Другим преимуществом Enum является то, что его легче читать.

Ответ 15

Если метод задает вопрос, например:

KeepWritingData (DataAvailable());

где

bool DataAvailable()
{
    return true; //data is ALWAYS available!
}

void KeepWritingData (bool keepGoing)
{
   if (keepGoing)
   {
       ...
   }
}

Логические аргументы метода кажутся абсолютно идеальными.

Ответ 16

Это зависит от метода. Если метод делает что-то, что очень очевидно, истинно/ложно, тогда это нормально, например. ниже [хотя я не говорю, что это лучший дизайн для этого метода, это просто пример того, где использование очевидно].

CommentService.SetApprovalStatus(commentId, false);

Однако в большинстве случаев, например, в примере, который вы упомянули, лучше использовать перечисление. Существует много примеров в самой .NET Framework, где это соглашение не соблюдается, но это потому, что они ввели это руководство по дизайну довольно поздно в цикле.

Ответ 17

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

Ответ 18

Перечисления могут сделать код более читаемым. Есть еще кое-что, на что можно обратить внимание (в .net как минимум)

Поскольку базовое хранилище перечисления является int, значение по умолчанию будет равно нулю, поэтому вы должны убедиться, что 0 является разумным значением по умолчанию. (Например, у structs все поля установлены равными нулю при создании, поэтому нет способа указать значение по умолчанию, отличное от 0. Если у вас нет значения 0, вы даже не можете проверить перечисление без литья в int, что было бы плохой стиль.)

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

Если ваши перечисления каким-либо образом публикуются во внешнем коде и/или сохраняются вне программы, рассмотрите их нумерацию явно. Компилятор автоматически индексирует их с 0, но если вы измените свои перечисления, не указывая их значения, вы можете получить дефекты.

Я могу юридически написать

WriteMode illegalButWorks = (WriteMode)1000000;
file.Write( data, illegalButWorks );

Чтобы бороться с этим, любой код, который использует перечисление, в котором вы не можете быть уверенным (например, public API), должен проверить, действительно ли перечисление. Вы делаете это через

if (!Enum.IsDefined(typeof(WriteMode), userValue))
    throw new ArgumentException("userValue");

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

public static bool CheckWriteModeEnumValue(WriteMode writeMode)
{
  switch( writeMode )
  {
    case WriteMode.Append:
    case WriteMode.OverWrite:
      break;
    default:
      Debug.Assert(false, "The WriteMode '" + writeMode + "' is not valid.");
      return false;
  }
  return true;
}

Проблема с версией заключается в том, что старый код может знать только, как обрабатывать 2 перечисления, которые у вас есть. Если вы добавите третье значение, Enum.IsDefined будет истинным, но старый код не обязательно сможет его обработать. Упс.

Еще больше удовольствия вы можете сделать с перечислениями [Flags], а код проверки для этого немного отличается.

Также я хочу отметить, что для переносимости вы должны использовать вызов ToString() в перечислении и использовать Enum.Parse() при чтении их обратно. Оба ToString() и Enum.Parse() могут обрабатывать также перечисление [Flags], поэтому нет причин не использовать их. Имейте в виду, это еще одна ошибка, потому что теперь вы даже не можете изменить имя переименования, не нарушая при этом код.

Итак, иногда вам нужно взвесить все вышеперечисленное, когда вы спрашиваете себя. Могу ли я уйти просто с bool?

Ответ 19

IMHO похоже, что перечисление станет очевидным выбором для любой ситуации, где возможны более двух вариантов. Но определенно есть ситуации, когда логическое все, что вам нужно. В этом случае я бы сказал, что использование перечисления, в котором будет работать bool, будет примером использования 7 слов, когда 4 сделают.

Ответ 20

Логические значения имеют смысл, когда у вас есть очевидный переключатель, который может быть только одной из двух вещей (например, состояние лампочки, вкл. или выкл.). Помимо этого, хорошо писать его таким образом, чтобы было очевидно, что вы проходите - например, записи диска - небуферизованные, строковые или синхронные - должны передаваться как таковые. Даже если вы не хотите разрешать синхронные записи сейчас (и поэтому вы ограничены двумя вариантами), стоит подумать о том, чтобы сделать их более подробными для целей понимания того, что они делают с первого взгляда.

Тем не менее, вы также можете использовать False и True (boolean 0 и 1), а затем, если вам нужно больше значений позже, разверните функцию, чтобы поддерживать определенные пользователем значения (скажем, 2 и 3), и ваш старый 0/1 будет хорошо переноситься, поэтому ваш код не должен прерываться.

Ответ 21

Иногда просто проще моделировать поведение с перегрузками. Для продолжения из вашего примера:

file.appendData( data );  
file.overwriteData( data );

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

Перечисления могут в некоторых случаях сделать код более удобочитаемым, хотя проверка правильного значения перечисления на некоторых языках (например, С#) может быть затруднительной.

Часто в список параметров добавляется логический параметр в качестве новой перегрузки. Один пример в .NET:

Enum.Parse(str);  
Enum.Parse(str, true); // ignore case

Последняя перегрузка стала доступной в более поздней версии .NET Framework, чем первая.

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


ИЗМЕНИТЬ

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

Enum.Parse(str, ignoreCase: true);

Ответ 22

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

например.

public void writeData(Stream data, boolean is_overwrite)

Возлюбите Enums, но логическое значение также полезно.

Ответ 23

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

Встроенный комментарий имеет большое значение для решения неожиданной проблемы bool. Исходный пример особенно отвратителен: представьте, что вы пытаетесь назвать переменную в функции declearation! Это будет что-то вроде

void writeData( DataObject data, bool use_append_mode );

Но, ради примера, скажем, что декларация. Затем, для другого необъяснимого логического аргумента, я помещаю имя переменной в строковый комментарий. Сравнить

file.writeData( data, true );

с

file.writeData( data, true /* use_append_mode */);

Ответ 24

Это действительно зависит от точной природы аргумента. Если это не yes/no или true/false, то перечисление делает его более читаемым. Но с перечислением вам нужно проверить аргумент или допустимое поведение по умолчанию, так как undefined могут быть переданы значения базового типа.

Ответ 25

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

var v = CallMethod(pData = data, pFileMode = WriteMode, pIsDirty = true);

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

С# 3.0 разрешает именованные аргументы в конструкторах. Я не знаю, почему они тоже не могут делать это с помощью методов.

Ответ 26

Булево значения true/false. Поэтому непонятно, что он представляет. Enum может иметь значащее имя, например OVERWRITE, APPEND и т.д. Так что перечисления лучше.