Мой коллега утверждает, что логические значения в качестве аргументов метода недопустимы. Их заменяют перечислениями. Сначала я не увидел никакой пользы, но он привел мне пример.
Что легче понять?
file.writeData( data, true );
Или же
enum WriteMode {
Append,
Overwrite
};
file.writeData( data, Append );
Теперь я понял! ;-)
Это определенно пример, когда перечисление в качестве второго параметра делает код более читаемым.
Итак, что вы думаете по этой теме?
Хм, я делал это раньше, но никогда не понимал, насколько это хороший шаблон дизайна. так что перечисление идет в файл?
Перечисления, безусловно, имеют больше смысла с точки зрения семантики. С другой стороны, было бы интересно посмотреть, что некоторые программисты придумали для обработки нечеткой логики.
просто спросите лимонного парня в Adventure Time, если это неприемлемо
См. Также: логические параметры - они пахнут?





Перечисления также допускают будущие модификации, когда вам теперь нужен третий вариант (или больше).
Или GetMessage () Win32: ИСТИНА, ЛОЖЬ или -1.
@ Богатый: thedailywtf.com/Articles/What_Is_Truth_0x3f_.aspx
Логическое значение будет приемлемо только в том случае, если вы не собираетесь расширять функциональные возможности фреймворка. Enum предпочтительнее, потому что вы можете расширить перечисление и не нарушить предыдущие реализации вызова функции.
Другое преимущество Enum в том, что его легче читать.
Логические значения представляют варианты «да / нет». Если вы хотите представить «да / нет», используйте логическое значение, оно не требует пояснений.
Но если это выбор между двумя вариантами, ни один из которых явно не является «да» или «нет», тогда перечисление иногда может быть более читаемым.
Кроме того, в названии метода должно быть четко указано, что делает аргумент yes или no, т.е. void turnLightOn (bool) clealy setting true или yes будет включать свет.
Хотя в этом случае я бы предпочел turnLightOn () и turnLightOff (), в зависимости от ситуации.
TurnLightOn (false) означает «не включать свет»? Путаница
Что насчет changeLightState( bool ) или чего-то такого? :П
Как насчет setLightOn(bool).
Поздний комментарий, но @Jay Bazuzi: если ваш метод называется turnLightOn, и вы передаете false, вы также можете вообще не вызывать метод, передача false говорит, что не включайте свет. Если свет уже горит, это не значит, что выключать, это значит не включать ... Если у вас есть метод turnLight, имеет смысл перечисление с On и Off, turnLight ( Вкл.), Включите свет (Выкл.). Я согласен со скаффманом, но я бы предпочел два разных явных метода, turnLightOn () и turnLightOff (). (Кстати: это объясняется в книге дяди Бобса «Чистый код»)
Я думаю, вы почти ответили на это сами, я думаю, что конечная цель - сделать код более читаемым, и в этом случае перечисление сделало это, ИМО всегда лучше смотреть на конечную цель, а не на общие правила, может быть, подумайте об этом больше в качестве ориентира, т.е. перечисления часто более читабельны в коде, чем общие bools, int и т. д., но всегда будут исключения из правила.
Перечисления лучше, но я бы не назвал логические параметры «неприемлемыми». Иногда проще добавить одно маленькое логическое значение и двигаться дальше (подумайте о частных методах и т. д.)
просто сделайте метод очень наглядным, чтобы было понятно, что означает «истина» или «да».
Если метод задает такой вопрос, как:
KeepWritingData (DataAvailable());
где
bool DataAvailable()
{
return true; //data is ALWAYS available!
}
void KeepWritingData (bool keepGoing)
{
if (keepGoing)
{
...
}
}
Аргументы логического метода кажутся абсолютно разумными.
Когда-нибудь вам нужно будет добавить «продолжайте писать, если у вас есть свободное место», и тогда вы все равно перейдете от bool к enum.
И тогда у вас будут критические изменения, или устаревшая перегрузка, или может быть что-то вроде KeppWritingDataEx :)
@ Илья, а может и нет! Создание возможной ситуации, когда ее в настоящее время не существует, не отменяет решения.
Джесси прав. Планировать такие изменения - глупо. Делайте то, что имеет смысл. В этом случае логическое значение интуитивно понятно и понятно. c2.com/xp/DoTheSimplestThingThatCouldPossibleWork.html
@Derek, в этом случае логическое значение даже не нужно, потому что DataAvailable всегда возвращает true :)
Это зависит от метода. Если метод делает что-то, что, очевидно, является истинным / ложным, тогда все в порядке, например. ниже [хотя я не говорю, что это лучший дизайн для этого метода, это просто пример того, где использование очевидно].
CommentService.SetApprovalStatus(commentId, false);
Однако в большинстве случаев, таких как приведенный вами пример, лучше использовать перечисление. В самой .NET Framework есть много примеров, когда это соглашение не соблюдается, но это потому, что они представили это руководство по проектированию довольно поздно в цикле.
ИМХО кажется, что перечисление было бы очевидным выбором для любой ситуации, когда возможно более двух вариантов. Но определенно существуют ситуации, когда логическое значение - это все, что вам нужно. В этом случае я бы сказал, что использование перечисления, в котором будет работать bool, будет примером использования 7 слов, когда подойдет 4.
Логические значения имеют смысл, когда у вас есть очевидный переключатель, который может быть только одним из двух (то есть состоянием лампочки, включенным или выключенным). Помимо этого, хорошо написать его таким образом, чтобы было очевидно, что вы проходите - например, запись на диск - небуферизованная, строчная или синхронная - должна передаваться как таковая. Даже если вы не хотите разрешать синхронную запись сейчас (и поэтому вы ограничены двумя вариантами), стоит подумать о том, чтобы сделать их более подробными, чтобы с первого взгляда было понятно, что они делают.
Тем не менее, вы также можете использовать False и True (логические 0 и 1), а затем, если вам понадобится больше значений позже, разверните функцию для поддержки пользовательских значений (скажем, 2 и 3) и ваших старых значений 0/1. будет перенесен красиво, поэтому ваш код не должен сломаться.
Это делает вещи более явными, но действительно начинает значительно увеличивать сложность ваших интерфейсов - при чисто логическом выборе, таком как добавление / перезапись, это кажется излишним. Если вам нужно добавить дополнительную опцию (о которой я не могу думать в этом случае), вы всегда можете выполнить рефакторинг (в зависимости от языка)
А как насчет добавления в качестве третьего возможного варианта? ;-))
Я бы не согласился, что это хороший правило. Очевидно, что Enum в некоторых случаях улучшает явный или подробный код, но, как правило, это кажется чрезмерным.
Сначала позвольте мне взять ваш пример: Ответственность (и способность) программиста писать хороший код на самом деле не подвергается опасности из-за наличия логического параметра. В вашем примере программист мог написать такой же подробный код, написав:
dim append as boolean = true
file.writeData( data, append );
или я предпочитаю более общий
dim shouldAppend as boolean = true
file.writeData( data, shouldAppend );
Второй: Приведенный вами пример Enum «лучше» только потому, что вы передаете CONST. Скорее всего, в большинстве приложений по крайней мере некоторые, если не большинство временных параметров, которые передаются в функции, являются ПЕРЕМЕННЫМИ. В этом случае мой второй пример (с указанием переменных с хорошими именами) намного лучше, и Enum принесет вам небольшую пользу.
Хотя я согласен с тем, что логические параметры приемлемы во многих случаях, в случае этого примера writeData () логический параметр, такой как shouldAppend, очень неуместен. Причина проста: не сразу понятно, что означает False.
Используйте тот, который лучше всего моделирует вашу проблему. В приведенном вами примере лучше выбрать перечисление. Однако в других случаях логическое значение лучше. Что имеет для вас больше смысла:
lock.setIsLocked(True);
или же
enum LockState { Locked, Unlocked };
lock.setLockState(Locked);
В этом случае я мог бы выбрать логический вариант, поскольку я считаю, что он довольно ясен и недвусмысленен, и я почти уверен, что моя блокировка не будет иметь более двух состояний. Тем не менее, второй вариант действителен, но излишне сложен, ИМХО.
в вашем примере я бы предпочел два метода. lock.lock () lock.release () и lock.IsSet, но все зависит от того, что имеет наибольший смысл для кода-потребителя.
Это справедливый комментарий, но я думаю, что он также иллюстрирует более крупную мысль о том, что существует множество способов смоделировать данную проблему. Вам следует использовать лучшую модель для ваших обстоятельств, а также лучшие инструменты, которые предоставляет среда программирования, чтобы соответствовать вашей модели.
Я полностью согласен :), я просто комментировал конкретный псевдокод, предлагая еще один вариант. Я согласен с твоим ответом.
Помните вопрос, который Адлай Стивенсон задал послу Зорину в ООН во время Кубинский ракетный кризис?
"You are in the courtroom of world opinion right now, and you can answer yes or no. You have denied that [the missiles] exist, and I want to know whether I have understood you correctly.... I am prepared to wait for my answer until hell freezes over, if that's your decision."
Если флаг, который у вас есть в вашем методе, имеет такую природу, что вы можете привязать его к двоичное решение, и это решение превратит никогда в трехстороннее или n-стороннее решение, выберите логическое значение. Показания: ваш флаг называется isXXX.
Не делайте его логическим в случае чего-то, что является переключатель режима. Всегда есть еще один режим, чем вы думали при написании метода.
Дилемма еще одного режима имеет, например, преследуемая Unix, где возможные режимы разрешений, которые может иметь файл или каталог сегодня, приводят к странному двойному значению режимов в зависимости от типа файла, владельца и т. д.
Логические значения могут быть приемлемыми для языков с именованными параметрами, таких как Python и Objective-C, поскольку имя может объяснить, что делает параметр:
file.writeData(data, overwrite=true)
или же:
[file writeData:data overwrite:YES]
IMHO, writeData () - плохой пример использования логического параметра, независимо от того, поддерживаются ли именованные параметры или нет. Как бы вы ни называли параметр, значение значения False неочевидно!
Я считаю, что это плохо по двум причинам:
Потому что некоторые люди напишут такие методы, как:
ProcessBatch(true, false, false, true, false, false, true);
Это явно плохо, потому что слишком легко перепутать параметры, и, глядя на это, вы даже не представляете, что указываете. Хотя всего один bool не так уж и плохо.
Поскольку управление потоком программы с помощью простой ветки «да / нет» может означать, что у вас есть две совершенно разные функции, которые неудобно объединены в одну. Например:
public void Write(bool toOptical);
На самом деле, это должно быть два метода
public void WriteOptical();
public void WriteMagnetic();
потому что код в них может быть совершенно другим; им, возможно, придется выполнять всевозможные обработки ошибок и проверки, а может быть, даже придется по-другому форматировать исходящие данные. Вы не можете сказать это, просто используя Write() или даже Write(Enum.Optical) (хотя, конечно, вы можете использовать любой из этих методов, просто вызывая внутренние методы WriteOptical / Mag, если хотите).
Я думаю, это зависит от обстоятельств. Я бы не стал придавать этому большого значения, кроме №1.
Очень хорошие моменты! Два логических параметра в одном методе действительно выглядят ужасно (если, конечно, вам не повезло иметь именованные параметры).
Однако этот ответ может выиграть от некоторого переформатирования! ;-)
У перечислений есть определенное преимущество, но вы не должны просто заменять все свои логические значения перечислениями. Есть много мест, где истина / ложь на самом деле лучший способ представить, что происходит.
Однако использование их в качестве аргументов метода немного подозрительно, просто потому, что вы не можете увидеть, не копаясь в вещах, что они должны делать, поскольку они позволяют вам увидеть, что истинное / ложное на самом деле означает
Свойства (особенно с инициализаторами объектов 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 :-(
Перечисления, безусловно, могут сделать код более читабельным. Есть еще несколько вещей, на которые следует обратить внимание (по крайней мере, в .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], и код проверки для этого немного отличается.
Я также отмечу, что для переносимости вы должны использовать вызов ToString() в перечислении и использовать Enum.Parse() при их обратном чтении. И ToString(), и Enum.Parse() также могут обрабатывать перечисления [Flags], поэтому нет причин не использовать их. Имейте в виду, это еще одна ловушка, потому что теперь вы не можете даже изменить имя перечисления, возможно, не нарушив код.
Итак, иногда вам нужно взвесить все вышеперечисленное, когда вы спрашиваете себя: Могу ли я обойтись только с bool?
Это действительно зависит от точного характера аргумента. Если это не да / нет или истина / ложь, то перечисление делает его более читабельным. Но с перечислением вам необходимо проверить аргумент или иметь приемлемое поведение по умолчанию, поскольку могут быть переданы неопределенные значения базового типа.
Хотя верно, что во многих случаях перечисления более читабельны и более расширяемы, чем логические, абсолютное правило, что «логические значения неприемлемы», является глупым. Он негибкий и контрпродуктивный - он не оставляет места для человеческого суждения. Они являются фундаментальным встроенным типом для большинства языков, потому что они полезны - подумайте о применении его к другим встроенным типам: например, сказать «никогда не использовать int в качестве параметра» было бы просто безумием.
Это правило просто вопрос стиля, а не возможностей для ошибок или производительности во время выполнения. Лучшим правилом было бы «предпочитать перечисления логическим из соображений удобочитаемости».
Взгляните на структуру .Net. Логические значения используются в качестве параметров во многих методах. .Net API не идеален, но я не думаю, что использование логических значений в качестве параметров является большой проблемой. Всплывающая подсказка всегда дает вам имя параметра, и вы также можете создать такое руководство - заполните свои XML-комментарии к параметрам метода, они появятся во всплывающей подсказке.
Я также должен добавить, что есть случай, когда вы должны явно рефакторировать логические значения для перечисления - когда у вас есть два или более логических значения в вашем классе или в параметрах вашего метода, и не все состояния действительны (например, их недопустимо иметь оба установлены true).
Например, если у вашего класса есть такие свойства, как
public bool IsFoo
public bool IsBar
И это ошибка, если оба они истинны одновременно, на самом деле у вас есть три действительных состояния, лучше выраженных как что-то вроде:
enum FooBarType { IsFoo, IsBar, IsNeither };
Использование перечислений вместо логических в вашем примере действительно помогает сделать вызов метода более читабельным. Однако это замена моему любимому элементу пожеланий в C#, именованным аргументам в вызовах методов. Этот синтаксис:
var v = CallMethod(pData = data, pFileMode = WriteMode, pIsDirty = true);
будет отлично читаться, и тогда вы сможете делать то, что должен делать программист, а именно выбирать наиболее подходящий тип для каждого параметра в методе, независимо от того, как он выглядит в среде IDE.
C# 3.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);
На мой взгляд, ни логическое значение, ни перечисление - не лучший подход. Роберт С. Мартин очень четко это уловил в своем Совет № 12: исключите логические аргументы:
Boolean arguments loudly declare that the function does more than one thing. They are confusing and should be eliminated.
Если метод выполняет несколько функций, вам лучше написать два разных метода, например, в вашем случае: file.append(data) и file.overwrite(data).
Использование перечисления не проясняет ситуацию. Это ничего не меняет, это все еще аргумент флага.
Разве это не означает, что функция, которая принимает строку ASCII длины N, выполняет 128 ^ N вещей?
@delty Это серьезный комментарий? Если да, часто ли вы кодируете if для всех возможных значений String? Есть ли какое-нибудь сравнение со случаем логического аргумента?
Я считаю, что они приемлемы, когда вы устанавливаете логическое значение внутри объекта. Прекрасным примером может служить setVisible(boolean visible) { mVisible = visible; }. Какую альтернативу вы бы предложили?
@Brad show () {mVisible = true} hide () {mVisible = false}
@Oswaldo, хотя все еще правильно, я не думаю, что имеет смысл иметь два разных метода для присвоения логического значения разным значениям. У вас нет setIntToOne (), setIntToTwo () setIntToThree (), верно? Это немного более неоднозначно, когда у вас может быть только два возможных значения, но для чистоты используйте в этом случае логическое значение.
Использование разных методов для разных опций означает принудительное управление потоком там, где вы могли бы использовать поток данных. Поток данных легче абстрагируется, чем поток управления, а абстракции потока данных обычно легче понять (например, их легче распечатать). Поток данных также лучше работает с функциональным стилем программирования.
Вот некоторые правила, которых может лучше придерживаться ваш коллега:
Только логические значения true / false. Так что непонятно, что это из себя представляет. Enum может иметь значащее имя, например OVERWRITE, APPEND и т. д. Так что перечисления лучше.
Я согласен с тем, что Enums - хороший способ использовать методы, в которых у вас есть 2 варианта (и только два варианта, которые вы можете читать без enum).
например
public void writeData(Stream data, boolean is_overwrite)
Любите Enums, но логическое значение тоже полезно.
Это поздняя запись в старом посте, и она находится так далеко вниз по странице, что ее никто никогда не прочитает, но поскольку никто этого еще не сказал ...
Встроенный комментарий имеет большое значение для решения неожиданной проблемы bool. Исходный пример особенно отвратителен: представьте, что вы пытаетесь назвать переменную в функции declearation! Это было бы что-то вроде
void writeData( DataObject data, bool use_append_mode );
Но для примера допустим, что это декларация. Затем для необъяснимого логического аргумента я помещаю имя переменной во встроенный комментарий. Сравнивать
file.writeData( data, true );
с
file.writeData( data, true /* use_append_mode */);
Это было очень интересное чтение, я буду чаще реализовывать этот метод.