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

Я час від часу бачив практику, що «відчуває» неправильність, але я не можу чітко сформулювати, що не так. А може це просто мої упередження. Тут йде:

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

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

155
@ ChristofferHammarström Nice посилання. Я включу це у моїй відповіді тому що це роз'яснює у багато деталей ту же ідею мого пояснення.
додано Автор Voltaire, джерело
Я не завжди погоджуюся з тим, що має сказати Нік, але в цьому випадку я погоджуюся на 100%: Не використовуйте булеві параметри .
додано Автор Paul Roub, джерело
Це питання, де належать рішення. Перемістіть рішення на центральне місце, замість того, щоб їх засмічували. Це буде тримати складність нижче, ніж мати два коди шляхів кожен раз, коли у вас є if.
додано Автор Adam Lassek, джерело
У Мартіна Фаулера є стаття про це:
додано Автор igelkott, джерело
додано Автор igelkott, джерело

16 Відповіді

Я перестав користуватися цим шаблоном давно, з дуже простої причини; вартість обслуговування. Кілька разів я виявив, що я маю певну функцію: frobnicate (щось, forwards_flag) , яка називалася багато разів у моєму коді, і потрібна, щоб знайти всі місця в коді, де значення false/code> передано як значення forwards_flag . Ви не можете легко знайти їх, тому це стає головним болем. І якщо вам потрібно зробити виправлення на кожному з цих сайтів, у вас може виникнути нещасна проблема, якщо ви пропустите один з них.

Але ця конкретна проблема легко фіксується без принципової зміни підходу:

enum FrobnicationDirection {
  FrobnicateForwards,
  FrobnicateBackwards;
};

void frobnicate(Object what, FrobnicationDirection direction);

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

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

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

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

132
додано
+1. Я використовую перелік для цього якомога більше. Я бачив функції, де додаткові параметри bool були додані пізніше, і виклики починають виглядати як DoSomething (myObject, false, false, true, false) . Неможливо зрозуміти, що означають додаткові булеві аргументи, тоді як зі значущими значеннями enum це просто.
додано Автор Neel, джерело
Дійсно великі конкретні приклади, а також розуміння природи того, з чим ми маємо справу і як він впливає на нас.
додано Автор ghoppe, джерело
О, людина, нарешті, хороший приклад того, як FrobnicateBackwards. Шукали це назавжди.
додано Автор Alex Pritchard, джерело

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

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

Виконання функції кожного другого параметра 'state' і у випадку логічного, тобто двох функцій, призводить до когезивних функцій.

Отже, що таке високо когезивна функція?

Це функція, яка робить одне і тільки одне. Вона може спиратися на інші згуртовані функції, але коли це відбувається, делегування є «єдиним, що вона робить». Порівняйте з роботою на фабриці реального життя. Кожну частину виробничого процесу обробляє інша особа. Застосовуйте цю ідею нероздільних «дій» або «поведінки» до функції, щоб отримати цілісну функцію.

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

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

Отже, це питання можна було б повторювати; Враховуючи те, що ми всі погоджуємося з проходженням параметрів поведінкового відбору, краще уникати, як покращувати питання?

I would get rid of the parameter completely. Having the ability to turn off access control even for testing is a potential security risk. For testing purposes either or the access check to test both the access allowed and access denied scenarios.

Що стосується майбутніх функцій, які, здається, використовують «параметри поведінки», просто вдайтеся до моделі Стратегії або чогось подібного. Проаналізуйте необхідні функції зверху вниз.

Ref: Cohesion (computer science)

80
додано
Завдяки Ray. Схоже, що це буде досить легко переоцінити, коли час дозволяє. Можливо, варто відмітити коментар TODO, щоб підкреслити проблему, досягнувши балансу між технічною владою та чутливістю до тиску, який ми іноді піддаємо, щоб зробити справу.
додано Автор Matt Pascoe, джерело
Рей, чим більше я думаю про це, тим більше я думаю, що ви повинні заперечувати над кодом, заснованим на дірці безпеки, вбудованому до вмикання контролю доступу. Поліпшення якості бази коду буде гарним побічним ефектом;)
додано Автор Matt Pascoe, джерело
"непідтримуваний"
додано Автор Ali Davut, джерело
Роб, чи не могли б ви пояснити, що таке згуртованість і як вона застосовується?
додано Автор ghoppe, джерело
Дуже приємне пояснення згуртованості та її застосування. Це дійсно має отримати більше голосів. Я також погоджуюся з проблемою безпеки ... хоча вони є приватними методами, це менша потенційна уразливість
додано Автор ghoppe, джерело

Це не обов'язково є помилковим, але воно може представляти собою запах коду "> запах коду .

Основним сценарієм, який слід уникати щодо булевих параметрів, є:

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

Потім, коли ви телефонуєте, зазвичай ви називаєте foo (false) і foo (true) залежно від потрібної поведінки.

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

У цьому випадку ви повинні залишити doThis і doThat як окремі та загальнодоступні методи, а потім виконайте такі дії:

doThis();
doThat();

або

doThis();

That way you leave the cабоrect decision to the caller (exactly as if you were passing a boolean parameter) without create coupling.

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

Це лише один приклад того, як вирішити цю проблему на основі прикладів, які я написав. Існують і інші випадки, коли буде необхідний інший підхід.

There is a good article from Martin Fowler explaining in further details the same idea.

PS: if method foo instead of calling two simple methods had a mабоe complex implementation then all you have to do is apply a small refactабоing extracting methods so the resulting code looks similar to the implementation of foo that I wrote.

33
додано
Якщо виклик двох методів один за іншим або один метод можна вважати зайвим, то також буде зайвим, як ви пишете блок try-catch або, можливо, if else. Чи означає це, що ви напишете функцію, яка б абстрагувала їх усіх? Ні! Будьте обережні, створюючи один метод, який все, що він робить, викликає дві інші методи, не обов'язково представляє гарну абстракцію.
додано Автор Voltaire, джерело
Існує багато ситуацій, коли if (прапор) doThat() всередині foo() є законним. Надіславши рішення про виклик doThat() на кожне повторення примусових викликів, яке потрібно видалити, якщо ви пізніше дізнаєтеся про деякі методи, поведінка також має викликати < код> doTheOther() . Я б скоріше поставив залежності між методами в тому ж класі, ніж доведеться пізніше переслідувати всіх абонентів.
додано Автор Schroeder, джерело
@missingno: Ви б як і раніше мали таку ж проблему, що висуваєте надлишковий код до абонентів, щоб зробити рішення doOne() або doBoth() . Підпрограми/функції/методи мають аргументи, тому їх поведінка може бути різною. Використання перерахування для дійсно булевої умови дуже схоже на повторення, якщо назва аргументу вже пояснює, що він робить.
додано Автор Schroeder, джерело
Це може бути вірно для if (прапор) doThat (); , однак, на моєму досвіді, частіше можна бачити if (прапор) doThat (local_variable); , щоб витягувати Вихід doThat вимагає розкрити те, що інакше було б приватною змінною. Це не так явно добре.
додано Автор Marco Leogrande, джерело
@Blrfl: Так, я вважаю, що більш простою реорганізацією буде створення методів doOne і doBoth (для помилкового і справжнього випадку, відповідно) або використання окремого переліку , як запропонував Джеймс Янгман
додано Автор hugomg, джерело
Дякуємо за виклик терміну "запах коду" - я знав, що він погано пахне, але не міг цілком отримати те, що було. Ваш приклад дуже багато в чому з тим, на що я дивлюся.
додано Автор ghoppe, джерело
Вибачте за каламбур, але запах коду для мене подібний до аналогії ... голова . Існує завжди більше ніж один спосіб програмувати шматок коду, який буде робити те ж саме, і в обох випадках вони можуть бути вірогідно правильними, але і з власними вадами. Доводиться зважувати компроміси, щоб знайти середню поверхню і баланс між ними, і незалежно від того, який стиль переваги вони вважають за краще використовувати, їм просто потрібно бути з ним узгодженими.
додано Автор Francis Cugler, джерело
Що робити, якщо doThat не має сенсу взагалі без doThis ?
додано Автор Gherman, джерело

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

Coding decisions, by & large, should be based primarily on performance and maintainability. If performance isn't at stake (and I can't imagine how it ever could be in your examples), then your next consideration should be: how easy will this be for me (or a future redactor) to maintain? Is it intuitive and understandable? Is it isolated? Your example of chained function calls does in fact seem potentially brittle in this respect: if you decide to change your bIsUp to bIsDown, how many other places in the code will need to be changed too? Also, is your paramater list ballooning? If your function has 17 parameters, then readability is an issue, and you need to reconsider whether you are appreciating the benefits of object-oriented architecture.

23
додано
Ваша відповідь нагадує мені цитату, яку я не пам'ятаю, джерело - "якщо у вашій функції є 17 параметрів, то вам, ймовірно, відсутній один".
додано Автор Eddie Gems, джерело
Я б дуже погодився з цим, і звертаюся до питання, щоб сказати, що так його часто погана ідея, щоб пройти в boolean прапор, але це ніколи не так просто, як погано/добре ...
додано Автор Rosa Gronchi, джерело
Я ціную застереження в першому абзаці. Я був навмисно провокаційний, кажучи "неправильно", і, безумовно, визнаю, що ми маємо справу в області "найкращих практик" і принципів дизайну, і що ці види речей часто ситуаційні, і треба зважувати численні фактори
додано Автор ghoppe, джерело

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

10
додано
@dreza ви звертаєтеся до Curlys Law .
додано Автор MattDavey, джерело
Звісно, ​​з досвідом ви повинні знати, коли ігнорувати такі аргументи.
додано Автор gnasher729, джерело

Мені подобається підхід до налаштування поведінки за допомогою методів будівельника, які повертають незмінні екземпляри. Ось як Гуава Splitter використовує його:

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

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

Переваги цього:

  • Superior readibility
  • Clear separation of configuration vs. action methods
  • Promotes cohesion by forcing you to think about what the object is, what it should and shouldn't do. In this case it's a Splitter. You'd never put someVaguelyStringRelatedOperation(List myEntities) in a class called Splitter, but you'd think about putting it as a static method in a StringUtils class.
  • The instances are pre-configured therefore readily dependency-injectable. The client doesn't need to worry about whether to pass true or false to a method to get the correct behavior.
7
додано
Посилання на бібліотеку Guava порушені
додано Автор Josh Noe, джерело
Я небайдужий до вашого рішення, як великий любитель гуави і євангеліст ... але я не можу дати вам +1, тому що ви пропускаєте через ту частину, яку я дійсно шукаю, що є неправильним (або смердючим) про інший шлях. Я думаю, що це насправді приховано в деяких твоїх поясненнях, так що, можливо, якщо б ви могли зробити це явно, це відповіло б краще.
додано Автор ghoppe, джерело
Мені подобається ідея розділення конфігурації і методів актонів!
додано Автор Sher10ck, джерело

Думаю, найголовніше тут бути практичним.

Коли boolean визначає всю поведінку, просто зробіть другий метод.

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

Наприклад:

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

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

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

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

6
додано
Я використовую лише булеві аргументи для керування поведінкою приватними методами (як показано тут). Але проблема: якщо якийсь dufus вирішить збільшити видимість FooInternal у майбутньому, то що?
додано Автор ADTC, джерело
Фактично, я пішов би іншим шляхом. Код всередині FooInternal повинен бути розділений на 4 частини: 2 функції для оброблення булевих випадків true/false, один для роботи, що відбувається раніше, і інша для роботи, яка відбувається після. Тоді ваш код Foo1 стає: {doWork (); HandleTrueCase (); doMoreWork ()} . В ідеалі, функції doWork і doMoreWork розбиваються на (один або більше) значущі фрагменти дискретних дій (тобто як окремі функції), а не тільки дві функції. розщеплення.
додано Автор Anne Aunyme, джерело

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

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

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

3
додано

Контекст важливий. Такі методи досить поширені в 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?
2
додано
@Ray Ідея (мені здається очевидною) полягає в тому, що метод saveWithEncryption() може зберігати файл зашифрованим або не зашифрованим. Введення withEncryption у назву методу нагадує читачеві, що означає параметр. Ви маєте рацію: file.save (false); ясно, як бруд. Включення значення параметрів у назву методу допомагає з цим.
додано Автор Caleb, джерело
@ BlueRaja-DannyPflughoeft Це схоже на прекрасне рішення, і, можливо, більше відповідає типовій практиці Java. Я проголосував за вашу відповідь.
додано Автор Caleb, джерело
@ ChristofferHammarström Зрозуміло, що кожен читає різні речі в назві; що це "говорить" для мене "зберегти, з шифруванням (увімкнено або вимкнено)", тобто параметр - це перемикач для ввімкнення шифрування. Див. Приклад UINavigationController у моїй відповіді на подібний випадок. Можливо, ви віддаєте перевагу ім'я, як saveConsideringEncryption (boolean) або saveWithEncryptionOption (boolean) . Для мене teaWithLemonAndSugarAndMilk (true, true, false) набагато легше зрозуміти, ніж tea (true, true, false) ; це означає "чай з лимоном і цукром, але без молока".
додано Автор Caleb, джерело
@ ChristofferHammarström Стиль не може бути вашою чашкою чаю, але це лише помилка, якщо вона не працює. ІМО, 1) використання параметрів для управління необов'язковою поведінкою в методі є законним; 2) кількість та типи параметрів (включаючи булеві) не змінюються (1); 3) методи іменування таким чином, що допомагає документувати параметри, може бути добре. Я згоден з тим, що (3) не має реального впливу на інші дві точки або на питання ОП, і мій POV по цьому питанню сильно залежить від Obj-C. Культура Java може бути іншою, і це добре, але це не робить її поганою ідеєю.
додано Автор Caleb, джерело
@JamesYoungman Я, звичайно, не маю на увазі, що назва методу повністю самостійно документується, але після того, як ви перевірили його один раз, withEncryption має бути сильним нагадуванням про те, що робить метод і що параметр означає. Також добре працює з кількома параметрами. Якщо метод називається colorFromRedGreenBlue (float, float, float) , ви не повинні пам'ятати, який параметр робить це, тому що назва методу говорить вам.
додано Автор Caleb, джерело
Альтернативна гіпотеза полягає в тому, що false означає, не перекривайте будь-який існуючий файл з тим же ім'ям. Я знаю наведений приклад, але, щоб бути впевненим, потрібно перевірити документацію (або код) функції.
додано Автор adandach, джерело
Мені б краще file.save (useEncryption: false)
додано Автор Michael, джерело
Метод поганий, тому що, очевидно, існує "сумнів щодо значення" помилкового "тут". У всякому разі, я б ніколи не написав метод, як це в першу чергу. Збереження та шифрування - це окремі дії, а метод повинен зробити одне і зробити це добре. Див. Мої попередні коментарі для кращих прикладів, як це зробити.
додано Автор igelkott, джерело
Насправді, код, який робить щось інше, ніж те, що він каже, не працює, і тому є помилкою. Не вдається зробити метод, який називається saveWithEncryption (boolean) , який іноді зберігає без шифрування, так само, як і не виконує saveWithoutEncryption (boolean) , що іноді зберігає з шифруванням.
додано Автор igelkott, джерело
Або ви повинні мати два способи: saveWithEncryption() і saveWithoutEncryption() .
додано Автор igelkott, джерело
Метод з назвою saveWithEncryption , який іноді не зберігає при шифруванні, є помилкою. Це має бути file.encrypt (). Save() або подібний Javas new EncryptingOutputStream (новий FileOutputStream (...)).
додано Автор igelkott, джерело
І ваші приклади чаю жахливі. Наведіть реальний приклад. Якщо я повинен був моделювати змішування інгредієнтів, я міг би робити cup.add (чай (2, DECILITER), молоко (5, CENTILITER), sugarlumps (1), lemonSlices (1)) . Або, можливо, tea.withSugar (). WithLemon (). WithMilk() або tea.with (цукор, лимон, молоко) .
додано Автор igelkott, джерело
Насправді я не маю жодної підказки, що означає false у прикладі file.saveWithEncryption . Чи означає це, що він збережеться без шифрування? Якщо так, то чому в світі метод має "з шифруванням" прямо в назві? Я розумію, що такий метод, як save (boolean withEncryption) , але коли я бачу file.save (false) , це зовсім не очевидно з першого погляду, що параметр вказує на те, що це буде з шифруванням або без нього. Я думаю, насправді, це робить першу точку Джеймса Янгмана про використання переліку.
додано Автор ghoppe, джерело

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

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

myObject.UpdateTimestamps(true);

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

myObject.UpdateTimestamps(saveChanges: true);

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


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

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

//Two separate methods used
IEnumerable ascendingList = c.OrderBy(o => o.Key);
IEnumerable descendingList = c.OrderByDescending(o => o.Key); 
2
додано
@ Інго я не згоден. Це загальне питання програмування; Ви можете легко визначити інше ім'я SaveBig . Будь-який код може бути загвинчений, такий шнек не є особливим для названих параметрів.
додано Автор jms, джерело
@Ingo: Якщо ви оточені ідіотами, то ви йдете і шукаєте роботу в іншому місці. Такі речі - це те, для чого ви маєте огляд коду.
додано Автор gnasher729, джерело
Підхід, названий параметром, ще більш неправильний. Без імені, один буде змушений прочитати API doc. З іменем, ви думаєте, що вам не потрібно: але параметри можуть бути помилковими. Наприклад, його можна було використовувати для збереження (всіх) змін, але пізніше імплантація трохи змінилася, щоб зберегти лише великі зміни (для деяких значень великих).
додано Автор Ingo, джерело
@Ray: Я не бачу різниці між цими двома питаннями. У мові, на якій можна застосувати імена параметрів, або якщо ви можете бути впевнені, що ім'я-параметри завжди будуть використовуватися (наприклад, приватні методи) , булеві параметри добре. Якщо названі параметри не можуть бути застосовані мовою (C #), а клас є частиною загальнодоступного API, або якщо мова не підтримує іменовані параметри (C ++), так що цей код на зразок/code> може бути написано, це запах коду.
додано Автор Michael, джерело
Питання не в тому, що краще для поведінки, що визначає прапор, це чи є такий прапор запахом, а якщо так, то чому
додано Автор ghoppe, джерело

Я не можу чітко визначити, що не так.

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

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

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

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

1
додано

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 # шифрувати (Byte [], RSAEncryptionPadding) , де вони використовували перерахування (або принаймні клас, що імітує перерахування) замість логічного.

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


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

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


І це наводить на погляд іншу важливу проблему. Якщо вам потрібно викликати багато методів, використовуючи той самий параметр, то весь проект може бути неправильним. Схоже, що логічне значення використовується для налаштування системи , а не для параметризації методу. Існує багато способів налаштування системи, наприклад, за допомогою шаблон дизайну держави /a> яка визначає контекст . Контексти також часто використовуються як структури в не-об'єктно-орієнтованих мовах. Наприклад, OpenSSL використовує багато контекстів для конкретних криптографічних операцій.

Інші шаблони проектування, які можна використовувати, це

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


Жахливе використання булевих буде включати/вимикати використання інших параметрів або іншим чином змінювати значення підпису методу. У такому випадку слід замінити їх на додатковий аргумент або клас шаблону Необов'язковий - залежно від використовуваної мови.


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

замість цього.

0
додано

Її не обов'язково неправильно, але у вашому конкретному прикладі дії в залежності від якогось атрибута "користувача" я б пройшов через посилання на користувача, а не прапор.

Це роз'яснює і допомагає різними способами.

Будь-хто, хто читає виклик, зрозуміє, що результат буде змінюватися залежно від користувача.

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

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

0
додано

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

По-перше, якщо кожен виклик у послідовності має сенс самостійно. Це має сенс, якщо код виклику може бути змінений з true на false або false на true, або , якщо викликаний метод може бути змінений на використовуйте параметр boolean безпосередньо, а не передайте його. Імовірність десяти таких викликів поспіль невелика, але це може статися, і якщо це станеться, це буде гарною практикою програмування.

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

Звичайно, у останньому випадку я б створив клас/структуру, чий вміст був булевим і передав що навколо. Я буду майже впевнений, що потрібні інші поля, перш ніж надто довго, як де для надсилання попереджувальних повідомлень, наприклад.

0
додано
Перерахувати WARN, SILENT буде мати більше сенсу, навіть при використанні класу/структури, як ви написали (тобто контекст ). Або взагалі просто налаштувати реєстрацію зовні - не потрібно нічого пропускати.
додано Автор jms, джерело

I agree with all the concerns of using Boolean Parameters to not determine performance in order to ; improve, Readability, Reliability, lowering Complexity, Lowering risks from poor Encapsulation & Cohesion and lower Total Cost of Ownership with Maintainability.

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

The Logic is called Mealey & Moore machines which we call now Finite State Machines. These also must be done in the same rules as above, unless it is a real time machine with a finite execution time and then shortcuts must be done to serve the purpose.

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

Але тепер з великою кількістю пам'яті та простоти OOK, Інкапсуляція є важливим інгредієнтом сьогодні, але покарання, коли код був зарахований в байтах для реального часу і SCADA машинного коду ..

0
додано

З огляду на OO, було б краще використовувати різні реалізації, залежно від значення, яке розраховується спочатку.

Але я не думаю, що це практично весь час.

0
додано