Чи неправильно використовувати булевий параметр для визначення поведінки?


194

Час від часу я бачив практику, яка "почувається" неправильно, але я не можу повністю сформулювати, що в цьому не так. А може, це просто мій упередження. Ось:

Розробник визначає метод із булевим параметром як один із своїх параметрів, і цей метод викликає інший тощо, і, зрештою, булевий використовується, виключно для того, щоб визначити, чи потрібно вжити певних дій чи ні. Це може бути використано, наприклад, для дозволу дії, лише якщо користувач має певні права, або, можливо, ми перебуваємо (або не перебуваємо) в тестовому режимі або пакетному режимі або в режимі живого режиму, або, можливо, лише тоді, коли система знаходиться в певний стан.

Ну, завжди є інший спосіб зробити це - шляхом запиту, коли настає час вжити заходів (а не передавати параметр), або мати декілька версій методу, або декількох реалізацій класу тощо. Не стільки, як би це покращити, а скоріше, чи це дійсно не так (як я підозрюю), і якщо це так, то що в цьому неправильно.


1
Це питання, куди належать рішення. Перемістіть рішення в центральному місці замість того, щоб вони були засмічені по всьому. Це дозволить зберегти складність нижче, ніж коефіцієнт два кодові шляхи кожного разу, якщо у вас є if.

28
Мартін Фаулер має статтю про це: martinfowler.com/bliki/FlagArgument.html
Крістофер Хаммарстрьом


@ ChristofferHammarström Приємне посилання. Я включу це у свою відповідь, оскільки вона детально пояснює ту саму ідею мого пояснення.
Олексій

1
Я не завжди згоден з тим, що має сказати Нік, але в цьому випадку я згоден на 100%: Не використовуйте булеві параметри .
Мар'ян Венема

Відповіді:


107

Так, це, швидше за все, запах коду, який призведе до нездійсненого коду, який важко зрозуміти і який має менший шанс бути легко використаним.

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

Отже, що є сильно згуртованою функцією?

Це функція, яка виконує одне і лише одне.

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

Краще було б відокремити питання контролю доступу від питань завдання, дії або командування.

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

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

Тож питання можна було б переробити; З огляду на те, що всі ми згодні з тим, щоб пройти параметри вибору поведінки краще уникати, як нам покращити питання?

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

Ref: Згуртованість (інформатика)


Роб, ти б пояснив, що таке згуртованість і як вона застосовується?
Рей

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

1
Дуже приємне пояснення згуртованості та його застосування. Це дійсно повинно отримати більше голосів. І я згоден з проблемою безпеки ... хоча якщо це все приватні методи, це менша потенційна вразливість
Рей

1
Спасибі Рей. Здається, це буде досить легко переосмислити, коли дозволяє час. Можливо, варто залишити коментар TODO, щоб висвітлити цю проблему, досягти балансу між технічними повноваженнями та чутливістю до тиску, який ми іноді піддаємося, щоб вчинити.
Роб

"нездійсненний"
aaa90210

149

Я перестала користуватися цією схемою дуже давно, з дуже простої причини; вартість обслуговування. Кілька разів я виявляв, що 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 це легко.
Graeme Perrow

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методів (для помилкового і справжнього випадку відповідно), або використання окремого типу enum, як запропонував Джеймс
Юнгмен

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

3
Якщо виклик двох методів один за одним або один метод один може вважатися зайвим, то також зайвим є те, як ви пишете блок пробного вловлювання, а може бути, якщо ще. Це означає, що ви напишете функцію, щоб абстрагувати їх усі? Немає! Будьте обережні, якщо створити один метод, що все, що він робить, це викликати два інші методи, не обов'язково являє собою хорошу абстракцію.
Олексій

29

По-перше: програмування - це не стільки наука, скільки мистецтво. Тому дуже рідко існує "неправильний" і "правильний" спосіб програмування. Більшість стандартів кодування - це лише "преференції", які деякі програмісти вважають корисними; але в кінцевому рахунку вони досить довільні. Тож я ніколи не мітив би вибір параметра, який би був "неправильним" сам по собі - і, звичайно, не такий спільний і корисний, як булевий параметр. Використання boolean(або int, з цього приводу) для інкапсуляції стану є цілком виправданим у багатьох випадках.

Рішення кодування, за великим рахунком, повинні базуватися насамперед на продуктивності та ремонтопридатності. Якщо продуктивність не поставлена ​​на карту (і я не можу уявити, як це могло бути у ваших прикладах), то наступним вашим роздумом має бути: наскільки легко мені це (або майбутньому редактору) підтримувати? Це інтуїтивно і зрозуміло? Це ізольовано? Ваш приклад зчеплених викликів функцій, в дійсності , здається , потенційно крихкими в цьому відношенні: якщо ви вирішите змінити , bIsUpщоб bIsDown, як і багато інших місць в коді , потрібно буде змінити теж? Крім того, ваш список параметрів на повітряній кулі? Якщо ваша функція має 17 параметрів, то читабельність - це проблема, і вам потрібно переглянути, чи оцінюєте ви переваги об'єктно-орієнтованої архітектури.


4
Я вдячний застереженням у першому пункті. Мене навмисно провокують, кажучи "неправильно", і, безумовно, визнаю, що ми маємо справу у царині "найкращих практик" та принципів дизайну, і що подібні речі часто бувають ситуативними, і треба зважувати численні фактори
Рей

13
Ваша відповідь нагадує мені цитату, про яку я не можу згадати джерело - "якщо у вашої функції 17 параметрів, ви, мабуть, не вистачаєте".
Йоріс Тіммерманс

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

15

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


@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 функції для обробки булевих справжніх / хибних випадків, одна для роботи, що відбувається раніше, а інша для роботи, яка відбувається після. Тоді, ваш Foo1буде: { doWork(); HandleTrueCase(); doMoreWork() }. В ідеалі, doWorkі doMoreWorkфункція кожен розбита (один або більше) значимі шматки дискретних дій (тобто в вигляді окремих функцій), а не тільки дві функції заради розщеплення.
jpaugh

7

Мені подобається підхід до налаштування поведінки за допомогою методів builder, які повертають незмінні екземпляри. Ось як 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, тому що ви пропускаєте частину, яку я справді шукаю, а це не так (або смердючий) про інший шлях. Я думаю, що це насправді неявне в деяких ваших поясненнях, тому, можливо, якщо ви могли б зробити це явним, це відповість на питання краще.
Рей

Мені подобається ідея розділення методів конфігурації та актону!
Sher10ck

Посилання на бібліотеку Guava порушені
Josh Noe

4

Виразно кодовий запах . Якщо він не порушує Принцип єдиної відповідальності, то, ймовірно, порушує функцію " Скажи, не питай" . Поміркуйте:

Якщо виявиться, що не порушує один із цих двох принципів, вам все-таки слід скористатися перерахунком. Булеві прапори - бульовий еквівалент магічних чисел . foo(false)має стільки ж сенсу, скільки bar(42). Переліки можуть бути корисними для "Шаблону стратегії" та матимуть гнучкість дозволити додавати іншу стратегію. (Просто пам’ятайте, щоб назвати їх відповідним чином )

Ваш конкретний приклад особливо мене турбує. Чому цей прапор пройшов через стільки методів? Це здається, що вам потрібно розділити параметр на підкласи .


4

TL; DR: не використовуйте булеві аргументи.

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


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

Тож ви отримаєте дзвінок на зразок:

rsaProvider.encrypt(data, true);

де читач повинен шукати підпис методу, щоб просто визначити, що пекло trueнасправді може означати. Проходження цілого числа, звичайно, так само погано:

rsaProvider.encrypt(data, 1);

сказала б вам так само - а точніше: так само мало. Навіть якщо ви визначаєте константи, які будуть використовуватися для цілого числа, користувачі функції можуть просто ігнорувати їх і продовжувати використовувати буквальні значення.

Найкращий спосіб вирішити це - використовувати перерахування . Якщо вам доведеться передати enum RSAPaddingз двома значеннями: OAEPабо PKCS1_V1_5тоді ви відразу зможете прочитати код:

rsaProvider.encrypt(data, RSAPadding.OAEP);

Булеви можуть мати лише два значення. Це означає, що якщо у вас є третій варіант, вам доведеться переробляти підпис. Як правило, це не може бути легко виконано, якщо сумісність ззаду є проблемою, тому вам доведеться поширити будь-який публічний клас іншим публічним методом. Це те, що нарешті зробило Microsoft, коли вони представили, RSACryptoServiceProvider#encrypt(Byte[], RSAEncryptionPadding)де вони використовували перерахування (або принаймні клас, що імітує перерахування) замість булевого.

Можливо, навіть простіше використовувати повноцінний об'єкт або інтерфейс як параметр, якщо сам параметр потребує параметризації. У вищенаведеному прикладі прокладка OAEP сама може бути параметризована хеш-значенням для внутрішнього використання. Зауважте, що зараз існує 6 хеш-алгоритмів SHA-2 та 4 хеш-алгоритми SHA-3, тому кількість значень перерахування може вибухнути, якщо ви використовуєте лише одне перерахування, а не параметри (це, можливо, наступне, що Microsoft збирається з'ясувати) ).


Булеві параметри також можуть вказувати на те, що метод або клас не розроблені належним чином. Як і у наведеному вище прикладі: будь-яка криптографічна бібліотека, окрім .NET, взагалі не використовує прапор для замітки у підписі методу.


Практично всі програмні гуру, які мені подобаються, застерігають від бульних аргументів. Наприклад, Джошуа Блох застерігає від них у високо оціненій книзі "Ефективна Java". Взагалі їх просто не слід використовувати. Ви можете стверджувати, що вони можуть бути використані, якщо є один параметр, який легко зрозуміти. Але навіть тоді: 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

Я здивований, що ніхто не згадав названі параметри .

Проблема, яку я бачу з булевими прапорами, полягає в тому, що вони шкодять читабельності. Наприклад, що робить trueв

myObject.UpdateTimestamps(true);

робити? Я поняття не маю. А як же:

myObject.UpdateTimestamps(saveChanges: true);

Тепер зрозуміло, що призначений для цього параметр: ми говоримо функції, щоб зберегти її зміни. У цьому випадку, якщо клас є непублічним, я думаю, що булевий параметр є нормальним.


Звичайно, ви не можете змусити користувачів вашого класу використовувати іменовані параметри. З цієї причини 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 - Danny Pflughoeft

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

@Ingo я не згоден. Це загальне питання програмування; ви можете легко визначити інше SaveBigім’я. Будь-який код можна накрутити, цей тип накрутки не має особливості названих параметрів.
Maarten Bodewes

1
@Ingo: Якщо вас оточують ідіоти, то ви йдете і шукаєте роботу в іншому місці. Така річ - це те, про що ви маєте огляди коду.
gnasher729

1

Я не можу повністю сформулювати, що в цьому не так.

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

Що ви хочете зробити:

1) Уникайте методів із побічними ефектами.

2) Обробляти необхідні стани за допомогою центральної, формальної державної машини (як це ).


1

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

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

Логікою називають машини Mealey & Moore, які ми називаємо тепер Кінцевими державними машинами . Вони також повинні бути виконані за тими ж правилами, що і вище, якщо тільки це не машина в режимі реального часу з обмеженим часом виконання, і тоді необхідно робити ярлики для досягнення мети.

Дані синхронізовані, але команди асинхронні, і логіка команд слідує запам’ятовуваній булевій логіці, але з послідовними командами на основі пам'яті попереднього, теперішнього та бажаного наступного стану. Для того, щоб він функціонував на найефективнішій машинній мові (всього 64 кБ), велику увагу поставили до визначення кожного процесу в евристичному способі IBM HIPO. Це іноді означало передачу булевих змінних та виконання індексованих гілок.

Але тепер з великою кількістю пам’яті та простотою OOK, інкапсуляція є найважливішим інгредієнтом сьогодні, але штрафом, коли код рахувався в байтах у реальному часі та машинним кодом 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), це зовсім не очевидно, що параметр вказує на те, що це було б із шифруванням або без нього. Я думаю, що насправді це є першим моментом Джеймса Янгмена щодо використання енму.
Рей

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.