Являются ли логические значения в качестве аргументов метода неприемлемыми?[закрыто]

StackOverflow https://stackoverflow.com/questions/135845

Вопрос

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

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

file.writeData( data, true );

Или

enum WriteMode {
  Append,
  Overwrite
};

file.writeData( data, Append );

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

Итак, каково ваше мнение на этот счет?

Это было полезно?

Решение

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

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

Другие советы

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

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

lock.setIsLocked(True);

или

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

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

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

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

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

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

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

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

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

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

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

Использование перечисления не проясняет ситуацию.Это ничего не меняет, это по-прежнему аргумент флага.

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

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

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

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

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

    public void Write(bool toOptical);
    

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

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

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

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

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

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

file.writeData(data, overwrite=true)

или:

[file writeData:data overwrite:YES]

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

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

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

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

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

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

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

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

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

Пример C #:

var worker = new BackgroundWorker { WorkerReportsProgress = true };

Пример Ruby

validates_presence_of :name, :allow_nil => true

Пример Python

connect_to_database( persistent=true )

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

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

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

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

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

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

public bool IsFoo
public bool IsBar

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

enum FooBarType { IsFoo, IsBar, IsNeither };

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

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

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

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

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

KeepWritingData (DataAvailable());

где

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

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

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

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

CommentService.SetApprovalStatus(commentId, false);

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

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

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

Поскольку базовым хранилищем перечисления является int , значение по умолчанию будет равно нулю, поэтому вы должны убедиться, что 0 является разумным значением по умолчанию.(Например.в структурах все поля при создании равны нулю, поэтому нет способа указать значение по умолчанию, отличное от 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 заключается в том, что он использует отражение и работает медленнее.Он также страдает от проблемы с управлением версиями.Если вам нужно часто проверять значение enum, вам было бы лучше выполнить следующее:

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 будет иметь значение true, но старый код не обязательно может его обработать.Упс.

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

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

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

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

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

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

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

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

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

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

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

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

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

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


Редактировать

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

Enum.Parse(str, ignoreCase: true);

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

например ,

public void writeData(Stream data, boolean is_overwrite)

Мне нравятся перечисления, но логическое значение тоже полезно.

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

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

void writeData( DataObject data, bool use_append_mode );

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

file.writeData( data, true );

с

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top