Неправильно ли использовать логический параметр для определения поведения?


194

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

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

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


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

28
У Мартина Фаулера есть статья об этом: martinfowler.com/bliki/FlagArgument.html
Кристоффер Хаммарстрем


@ ChristofferHammarström Хорошая ссылка. Я включу это в свой ответ, поскольку он объясняет во многих деталях ту же идею моего объяснения.
Алекс

1
Я не всегда согласен с тем, что должен сказать Ник, но в этом случае я согласен на 100%: не используйте логические параметры .
Марьян Венема

Ответы:


107

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

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

Так что же такое очень сплоченная функция?

Это функция, которая делает одно и только одно.

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

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

Как вы уже отметили, переплетение этих проблем кажется неуместным.

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

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

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

Ссылка: сплоченность (информатика)


Роб, не могли бы вы объяснить, что такое сплоченность и как она применяется?
Рэй

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

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

1
Спасибо, Рэй. Похоже, это будет достаточно легко, если позволит время. Возможно, стоит добавить комментарий TODO, чтобы осветить проблему, соблюдая баланс между техническим авторитетом и чувствительностью к давлению, которое мы иногда испытываем, чтобы добиться цели.
Роб

"не подлежит ремонту"
aaa90210

149

Я прекратил использовать эту модель давным-давно по очень простой причине; стоимость технического обслуживания. Несколько раз я обнаруживал, что у меня есть какая-то функция say, frobnicate(something, forwards_flag)которая была вызвана много раз в моем коде, и мне нужно было найти все места в коде, где значение falseбыло передано как значение forwards_flag. Вы не можете легко найти их, поэтому это становится головной болью при обслуживании. И если вам нужно сделать исправление на каждом из этих сайтов, у вас может возникнуть неприятная проблема, если вы пропустите один.

Но эта конкретная проблема легко решается без фундаментального изменения подхода:

enum FrobnicationDirection {
  FrobnicateForwards,
  FrobnicateBackwards;
};

void frobnicate(Object what, FrobnicationDirection direction);

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

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

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

Стоит также признать, что индикатор состояния системы, который необходимо передать почти каждой функции, на самом деле является глобальной переменной. Многие из недостатков глобальной переменной будут применяться. Я думаю, что во многих случаях лучше инкапсулировать знание состояния системы (или учетные данные пользователя, или идентификационные данные системы) в объекте, который отвечает за правильное действие на основе этих данных. Затем вы передаете ссылку на этот объект в отличие от необработанных данных. Ключевой концепцией здесь является инкапсуляция .


9
Действительно замечательные конкретные примеры, а также понимание природы того, с чем мы имеем дело, и как это влияет на нас.
Рэй

33
+1. Я использую перечисления для этого в максимально возможной степени. Я видел функции, где дополнительные boolпараметры были добавлены позже, и вызовы начинают выглядеть так DoSomething( myObject, false, false, true, false ). Невозможно понять, что означают дополнительные логические аргументы, в то время как со значениями enum со значимыми именами это легко.
Грэм Перроу

17
О человек, наконец хороший пример того, как FrobnicateBackwards. Искал это вечно.
Алекс Притчард

38

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

Основной сценарий, которого следует избегать в отношении логических параметров:

public void foo(boolean flag) {
    doThis();
    if (flag)
        doThat();
}

Затем, когда вы звоните, вы обычно звоните foo(false)и в foo(true)зависимости от того, какое поведение вы хотите.

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

То, что вы должны сделать в этом случае, это оставить doThisи doThatкак отдельные и публичные методы затем сделать:

doThis();
doThat();

или же

doThis();

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

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

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

Есть хорошая статья Мартина Фаулера, в которой более подробно объясняется та же идея.

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


1
Спасибо, что назвали термин «запах кода» - я знал, что он плохо пахнет, но не мог понять, что это за запах. Ваш пример в значительной степени соответствует тому, на что я смотрю.
Рэй

24
Есть много ситуаций, когда if (flag) doThat()внутри foo()законно. Принятие решения о вызове doThat()для каждого вызывающего абонента заставляет повторение, которое придется удалить, если вы позже узнаете о некоторых методах, flagповедение также необходимо вызвать doTheOther(). Я бы предпочел поместить зависимости между методами в одном и том же классе, чем потом обыскивать всех вызывающих.
Blrfl

1
@Blrfl: Да, я думаю, что более простым рефакторингом было бы либо создание методов a doOneи doBoth(для ложного и истинного случая, соответственно), либо использование отдельного типа перечисления, как предложено Джеймсом
Янгманом

@missingno: У вас все еще будет та же проблема с передачей избыточного кода вызывающим абонентам для принятия решения doOne()или doBoth(). Подпрограммы / функции / методы имеют аргументы, поэтому их поведение можно варьировать. Использование enum для истинно логического условия звучит очень похоже на повторение, если имя аргумента уже объясняет, что оно делает.
Blrfl

3
Если вызов двух методов один за другим или один метод может считаться избыточным, тогда также избыточно то, как вы пишете блок try-catch или, возможно, if else. Означает ли это, что вы напишите функцию, которая абстрагирует их всех? Нет! Будьте осторожны, создание одного метода, который вызывает только два других метода, не обязательно представляет собой хорошую абстракцию.
Алекс

29

Прежде всего: программирование - это не наука, а искусство. Так что очень редко есть «неправильный» и «правильный» способ программирования. Большинство стандартов кодирования - это просто «предпочтения», которые некоторые программисты считают полезными; но в конечном итоге они довольно произвольны. Поэтому я бы никогда не назвал выбор параметра «неправильным» сам по себе - и, конечно, не таким универсальным и полезным, как булев параметр. Использование boolean(или int, в этом отношении) для инкапсуляции состояния во многих случаях вполне оправдано.

Решения по кодированию в целом должны основываться, прежде всего, на производительности и удобстве обслуживания. Если производительность не поставлена ​​на карту (и я не могу себе представить, как это могло бы быть в ваших примерах), то ваш следующий вопрос должен быть следующим: насколько легко это будет для меня (или будущего редактора) поддерживать? Это интуитивно понятно и понятно? Он изолирован? Ваш пример вызовов связанных функций на самом деле кажется потенциально хрупким в этом отношении: если вы решите изменить его bIsUpна bIsDown, сколько других мест в коде тоже нужно будет изменить? Кроме того, ваш список параметров раздувается? Если ваша функция имеет 17 параметров, то удобочитаемость является проблемой, и вам нужно пересмотреть вопрос о том, насколько вы цените преимущества объектно-ориентированной архитектуры.


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

13
Ваш ответ напоминает мне цитату, из которой я не могу вспомнить источник - «если ваша функция имеет 17 параметров, вы, вероятно, пропускаете один».
Йорис Тиммерманс

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

15

Я думаю, что в статье, написанной Robert C Martins Clean, говорится, что вы должны исключать логические аргументы, где это возможно, поскольку они показывают, что метод делает больше, чем одно. Метод должен делать одно и только одно, я думаю, это один из его девизов.


@dreza вы имеете в виду закон Керли .
MattDavey

Конечно, с опытом вы должны знать, когда игнорировать такие аргументы.
gnasher729

8

Я думаю, что самое важное здесь - это быть практичным.

Когда логическое значение определяет все поведение, просто создайте второй метод.

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

Например:

private void FooInternal(bool flag)
{
  //do work
}

public void Foo1()
{
  FooInternal(true);
}

public void Foo2()
{
  FooInternal(false);
}

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


Я использую только логические аргументы для управления поведением в частных методах (как показано здесь). Но проблема: если какой-то дуфус решит увеличить видимость FooInternalв будущем, то что?
ADTC

На самом деле, я бы пошел другим путем. Код внутри FooInternal должен быть разбит на 4 части: 2 функции для обработки логических значений true / false, одна для работы, которая происходит до, и другая для работы, которая происходит после. Тогда, ваш Foo1будет: { doWork(); HandleTrueCase(); doMoreWork() }. В идеале каждая из функций doWorkи doMoreWorkдолжна быть разбита на (одну или несколько) значимых частей дискретных действий (т. Е. Как отдельные функции), а не просто две функции для разделения.
jpaugh

7

Мне нравится подход настройки поведения с помощью методов построения, которые возвращают неизменяемые экземпляры. Вот как это использует GuavaSplitter :

private static final Splitter MY_SPLITTER = Splitter.on(',')
       .trimResults()
       .omitEmptyStrings();

MY_SPLITTER.split("one,,,,  two,three");

Преимущества этого:

  • Превосходная читаемость
  • Четкое разделение конфигурации и методов действия
  • Способствует сплоченности, заставляя вас думать о том, что это за объект, что он должен и чего не должен делать. В этом случае это Splitter. Вы бы никогда не добавили someVaguelyStringRelatedOperation(List<Entity> myEntities)класс с именем Splitter, но вы бы подумали о том, чтобы поместить его как статический метод в StringUtilsкласс.
  • Экземпляры предварительно сконфигурированы, поэтому легко вводятся зависимостями. Клиенту не нужно беспокоиться о том, передать trueили falseметод, чтобы получить правильное поведение.

1
Я неравнодушен к вашему решению как большого любителя и евангелиста Гуавы ... но я не могу дать вам +1, потому что вы пропускаете ту часть, которую я действительно ищу, что является неправильным (или вонючим) о другом пути. Я думаю, что это на самом деле подразумевается в некоторых ваших объяснениях, так что, возможно, если бы вы могли сделать это явным, это ответило бы на вопрос лучше.
Рэй

Мне нравится идея разделения методов конфигурации и acton!
Sher10ck

Ссылки на библиотеку
Джош Но

4

Определенно запах кода . Если он не нарушает принцип единой ответственности, то, вероятно, нарушает принцип « говори, не спрашивай» . Рассмотреть возможность:

Если окажется, что он не нарушает один из этих двух принципов, вам все равно следует использовать enum. Булевы флаги являются логическим эквивалентом магических чисел . foo(false)имеет столько же смысла, сколько bar(42). Перечисления могут быть полезны для паттернов стратегий и могут позволить вам добавить еще одну стратегию. (Только не забудьте назвать их соответствующим образом .)

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


4

TL; DR: не используйте логические аргументы.

Смотрите ниже, почему они плохие и как их заменить (жирным шрифтом).


Логические аргументы очень трудно читать, и, следовательно, их трудно поддерживать. Основная проблема заключается в том, что цель обычно ясна, когда вы читаете сигнатуру метода, в которой указан аргумент. Однако наименование параметра обычно не требуется в большинстве языков. Таким образом, у вас будут анти-паттерны, например, RSACryptoServiceProvider#encrypt(Byte[], Boolean)где логический параметр определяет, какой тип шифрования будет использоваться в функции.

Таким образом, вы получите звонок как:

rsaProvider.encrypt(data, true);

где читатель должен искать сигнатуру метода просто, чтобы определить, что на trueсамом деле может означать ад . Передача целого числа, конечно, так же плоха:

rsaProvider.encrypt(data, 1);

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

Лучший способ решить эту проблему - использовать перечисление . Если вам нужно передать перечисление RSAPaddingс двумя значениями: OAEPили PKCS1_V1_5тогда вы сразу сможете прочитать код:

rsaProvider.encrypt(data, RSAPadding.OAEP);

Логическое значение может иметь только два значения. Это означает, что если у вас есть третий вариант, вам придется реорганизовать свою подпись. Обычно это не может быть легко выполнено, если проблема заключается в обратной совместимости, поэтому вам придется расширять любой открытый класс другим открытым методом. Это то, что Microsoft наконец сделала, когда они представили, RSACryptoServiceProvider#encrypt(Byte[], RSAEncryptionPadding)где они использовали перечисление (или, по крайней мере, класс, имитирующий перечисление) вместо логического значения.

Может быть даже проще использовать полный объект или интерфейс в качестве параметра, если сам параметр должен быть параметризован. В приведенном выше примере само дополнение OAEP может быть параметризовано с использованием значения хеш-функции для внутреннего использования. Обратите внимание, что теперь существует 6 алгоритмов хеширования SHA-2 и 4 алгоритма хеширования SHA-3, поэтому число значений перечисления может взорваться, если вы используете только одно перечисление, а не параметры (возможно, это следующее, что Microsoft собирается выяснить ).


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


Почти все гуру программного обеспечения, которые мне нравятся, предостерегают от логических аргументов. Например, Джошуа Блох предостерегает их в высоко оцененной книге «Эффективная Ява». В общем, они просто не должны использоваться. Можно утверждать, что их можно использовать, если есть один параметр, который легко понять. Но даже тогда: Bit.set(boolean)вероятно, лучше реализовать двумя способами : Bit.set()и Bit.unset().


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

const boolean ENCRYPT = true;
const boolean DECRYPT = false;

...

cipher.init(key, ENCRYPT);

гораздо более читабельно, чем:

cipher.init(key, true);

даже если бы вы предпочли:

cipher.initForEncryption(key);
cipher.initForDecryption(key);

вместо.


3

Я удивлен, что никто не упомянул named-параметры .

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

myObject.UpdateTimestamps(true);

делать? Понятия не имею. Но что насчет:

myObject.UpdateTimestamps(saveChanges: true);

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


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

//Enum used
double a = Math.Round(b, MidpointRounding.AwayFromZero);

//Two separate methods used
IEnumerable<Stuff> ascendingList = c.OrderBy(o => o.Key);
IEnumerable<Stuff> descendingList = c.OrderByDescending(o => o.Key); 

1
Вопрос не в том, что предпочтительнее флага, определяющего поведение, а в том, пахнет ли такой флаг, и если да, то почему
Рэй

2
@Ray: я не вижу разницы между этими двумя вопросами. В языке, где вы можете принудительно использовать именованные параметры или когда вы можете быть уверены, что именованные параметры всегда будут использоваться (например, частные методы) , логические параметры хороши. Если именованные параметры не могут быть применены языком (C #) и класс является частью общедоступного API, или если язык не поддерживает именованные параметры (C ++), так что myFunction(true)может быть написан такой код, как код запах.
BlueRaja - Дэнни Пфлюгофт

Подход с именованными параметрами еще более неправильный. Без имени вы будете вынуждены читать документацию по API. С именем вы думаете, что вам не нужно: но параметр может быть неверным. Например, его можно было использовать для сохранения (всех) изменений, но позже имплементация немного изменилась, чтобы сохранить только большие изменения (для некоторого значения большого).
Инго

@ Инго, я не согласен. Это общая проблема программирования; Вы можете легко определить другое SaveBigимя. Любой код может быть испорчен, этот вид не относится к именованным параметрам.
Maarten Bodewes

1
@Инго: Если тебя окружают идиоты, то иди и найди работу в другом месте. Это то, для чего у вас есть обзоры кода.
gnasher729

1

Я не могу четко сформулировать, что в этом плохого.

Если он выглядит как запах кода, ощущается как запах кода и - ну, хорошо пахнет запахом кода, это, вероятно, запах кода.

Что вы хотите сделать, это:

1) Избегайте методов с побочными эффектами.

2) Обрабатывать необходимые состояния с помощью центрального, формального автомата (как это ).


1

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

Я начал проектировать аппаратное обеспечение в середине 70-х годов, которое мы теперь называем SCADA (диспетчерское управление и сбор данных), и это было точно настроенное аппаратное обеспечение с машинным кодом в EPROM, работающее с макро-пультами дистанционного управления и собирающее высокоскоростные данные.

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

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

Но теперь, с большим объемом памяти и простотой OOK, Encapsulation является важным компонентом сегодня, но штрафом, когда код подсчитывается в байтах для реального времени и машинного кода SCADA.


0

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

Это проясняет и помогает несколькими способами.

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

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

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


0

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

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

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

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


Перечисление WARN, SILENT будет иметь больше смысла, даже если вы используете класс / структуру, как вы написали (т.е. контекст ). Или просто настроить внешнюю регистрацию - не нужно ничего передавать.
Maarten Bodewes

-1

Контекст важен. Такие методы довольно распространены в iOS. В качестве одного из часто используемых примеров UINavigationController предоставляет метод -pushViewController:animated:, а animatedпараметр представляет собой BOOL. Метод выполняет в основном ту же самую функцию в любом случае, но он оживляет переход от одного контроллера представления к другому, если вы передаете YES, и не делает, если вы передаете NO. Это кажется вполне разумным; было бы глупо предоставлять два метода вместо этого, чтобы вы могли определить, использовать анимацию или нет.

Это может быть проще для обоснования такого рода вещей в Objective-C, где синтаксис именования методов обеспечивает больше контекста для каждого параметра, чем в таких языках, как C и Java. Тем не менее, я думаю, что метод, который принимает один параметр, может легко принять логическое значение и все же иметь смысл:

file.saveWithEncryption(false);    // is there any doubt about the meaning of 'false' here?

21
На самом деле я понятия не имею, что falseозначает в file.saveWithEncryptionпримере. Значит ли это, что он будет сохранять без шифрования? Если так, то почему в мире этот метод имеет «с шифрованием» право на название? Я мог бы понять, что есть такой метод save(boolean withEncryption), но когда я вижу file.save(false), на первый взгляд совсем не очевидно, что параметр указывает, что он будет с или без шифрования. Я думаю, на самом деле, это делает первое замечание Джеймса Янгмана об использовании enum.
Рэй

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

5
Метод с именем, saveWithEncryptionкоторый иногда не сохраняется с шифрованием, является ошибкой. Должно быть file.encrypt().save(), или как Javas new EncryptingOutputStream(new FileOutputStream(...)).write(file).
Кристофер Хаммарстрем

2
На самом деле, код, который делает что-то еще, кроме того, что он говорит, не работает, и поэтому является ошибкой. Он не может создать именованный метод, saveWithEncryption(boolean)который иногда сохраняет без шифрования, точно так же, как он не может создать метод , saveWithoutEncryption(boolean)который иногда сохраняет с шифрованием.
Кристофер Хаммарстрем

2
Метод плохой, потому что здесь очевидно «сомнение в значении« ложь »здесь». В любом случае, я бы никогда не написал такой метод. Сохранение и шифрование - это отдельные действия, и метод должен делать одно и делать это хорошо. Смотрите мои предыдущие комментарии для лучших примеров, как это сделать.
Кристофер Хаммарстрем
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.