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


123

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

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

file.writeData( data, true );

Или

enum WriteMode {
  Append,
  Overwrite
};

file.writeData( data, Append );

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

Итак, что вы думаете по этой теме?


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

Хм, я делал это раньше, но никогда не понимал, насколько это хороший шаблон дизайна. так что перечисление идет в файл?
Шон,

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

2
просто спросите лимонного парня из «Времени приключений», если это недопустимо
ajax333221

Ответы:


131

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

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


3
Кроме того, имя метода должно четко указывать на то, что делает аргумент yes или no, т.е. void turnLightOn (bool) clealy установка true или yes будет включать свет.
Саймон

10
Хотя в этом случае я бы предпочел turnLightOn () и turnLightOff (), в зависимости от ситуации.
skaffman

14
TurnLightOn (false) означает «не включать свет»? Confusiong.
Джей Базузи

17
Как насчет setLightOn(bool).
Finbarr

10
Поздний комментарий, но @Jay Bazuzi: если ваш метод называется turnLightOn, и вы передаете false, вы также можете вообще не вызывать метод, передача false говорит, что не включайте свет. Если свет уже горит, это не значит, что его выключать, это значит, что не включать ... Если у вас есть метод turnLight, имеет смысл перечисление с On и Off, turnLight ( Вкл.), Включите свет (Выкл.). Я согласен со скаффманом, но я бы предпочел два разных явных метода, turnLightOn () и turnLightOff (). (Кстати: это объясняется в книге дяди Бобса «Чистый код»)
Фил,


32

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

lock.setIsLocked(True);

или

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

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


2
в вашем примере я бы предпочел два метода. lock.lock () lock.release () и lock.IsSet, но все зависит от того, что имеет наибольший смысл для потребляющего кода.
Роберт Полсон,

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

Я полностью согласен :), я просто комментировал конкретный псевдокод, предлагая еще один вариант. Я согласен с твоим ответом.
Роберт Полсон,

14

Для меня ни логическое значение, ни перечисление - не лучший подход. Роберт С. Мартин очень четко это уловил в своем Совете № 12 по чистому коду: устранение логических аргументов :

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

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

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


7
Разве это не означает, что функция, которая принимает строку ASCII длины N, выполняет 128 ^ N вещей?
Detly

@delty Это серьезный комментарий? Если да, часто ли вы кодируете if для всех возможных значений String? Есть ли какое-нибудь сравнение с логическим аргументом?
Паскаль Тивент

Я считаю, что они приемлемы, когда вы устанавливаете логическое значение внутри объекта. Прекрасным примером будет setVisible(boolean visible) { mVisible = visible; }. Какую альтернативу вы бы предложили?
Брэд

2
@Brad show () {mVisible = true} hide () {mVisible = false}
Освальдо Акауан,

@Oswaldo, хотя все еще правильно, я не думаю, что имеет смысл иметь два разных метода для присвоения логического значения разным значениям. У вас нет setIntToOne (), setIntToTwo (), setIntToThree (), верно? Это немного более неоднозначно, когда вы можете иметь только два возможных значения, но для чистоты используйте в этом случае логическое значение.
Брэд

13

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


13

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

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

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

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

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


13

Я считаю, что это плохо по двум причинам:

  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.


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

Однако этот ответ может выиграть от некоторого переформатирования! ;-)
Ярик

7

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


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

6

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

file.writeData(data, overwrite=true)

или:

[file writeData:data overwrite:YES]

1
IMHO, writeData () - плохой пример использования логического параметра, независимо от того, поддерживаются ли именованные параметры или нет. Как бы вы ни называли параметр, значение значения False неочевидно!
Ярик

4

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

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

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

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

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

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


1
Хотя я согласен с тем, что логические параметры приемлемы во многих случаях, в случае этого примера writeData () логический параметр, такой как shouldAppend, очень неуместен. Причина проста: не сразу понятно, что означает False.
Ярик

4

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

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

Свойства (особенно с инициализаторами объектов C # 3) или аргументы ключевого слова (a la 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 :-(


4

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

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

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

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

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

public bool IsFoo
public bool IsBar

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

enum FooBarType { IsFoo, IsBar, IsNeither };

4

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

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

3

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

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


2

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

KeepWritingData (DataAvailable());

где

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

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

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


Когда-нибудь вам нужно будет добавить «продолжайте писать, если у вас есть свободное место», и тогда вы все равно перейдете от bool к enum.
Илья Рыженков

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

1
@ Илья, а может и нет! Создание возможной ситуации, когда ее в настоящее время не существует, не отменяет решения.
Джесси С. Слайсер,

1
Джесси прав. Планировать такие изменения - глупо. Делайте то, что имеет смысл. В этом случае логическое значение интуитивно понятно и понятно. c2.com/xp/DoTheSimplestThingThatCouldPossablyWork.html
Дерек Парк,

@Derek, в этом случае логическое значение даже не нужно, потому что DataAvailable всегда возвращает true :)
Илья Рыженков

2

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

CommentService.SetApprovalStatus(commentId, false);

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


2

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


1
А что насчет добавления в качестве третьего возможного варианта? ;-))
Ярик

2

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

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

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

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

Я могу легально написать

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

Для борьбы с этим любой код, использующий перечисление, в котором вы не можете быть уверены (например, общедоступный 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]перечислениями можно сделать еще больше удовольствия , и код проверки для этого немного отличается.

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

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


1

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


0

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

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


0

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

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

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

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

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

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

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

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


РЕДАКТИРОВАТЬ

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

Enum.Parse(str, ignoreCase: true);

0

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

например

public void writeData(Stream data, boolean is_overwrite)

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


0

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

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

void writeData( DataObject data, bool use_append_mode );

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

file.writeData( data, true );

с участием

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

-1

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


-1

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

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

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

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


Интересная идея. Но сможете ли вы изменить порядок параметров? Пропустить параметры? Как компилятор узнает, к какой перегрузке вы привязаны, если бы это было необязательно? Кроме того, вам нужно было бы назвать все параметры в списке?
Дрю Ноукс,

-1

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.