Чи є змінні прапора абсолютним злом? [зачинено]


47

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

"булеві або цілі змінні, яким ви присвоюєте значення в певних місцях, а потім внизу ви перевіряєте, потім в іншому, щоб зробити щось чи ні, як, наприклад, використовуючи newItem = trueпотім деякі рядки нижче if (newItem ) then"


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

Чи можете ви сказати, що існують випадки, коли використання прапорів є доброю практикою або навіть необхідною? Чи погоджуєтесь ви, що використання прапорів у коді є ... червоними прапорами і їх слід уникати / ремонтувати; Мені просто подобається виконувати функції / методи, які перевіряють стан у режимі реального часу.


9
Здається, що у MainMa і у мене є різні визначення поняття "прапор". Я думав про препроцесор #ifdefs. Про кого ви питали?
Карл Білефельдт

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

Булеви - це прапори. (Так само цілі числа, так і ...)
Томас Едінг

7
@KarlBielefeldt Я вважаю, що ОП переглядає булеві чи цілі змінні, яким ви присвоюєте значення в певних місцях, а потім внизу ви перевіряєте, потім в іншому, щоб зробити щось чи ні, як, наприклад, використовуючи newItem = trueпотім деякі рядки нижчеif (newItem ) then
Tulains Córdova

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

Відповіді:


41

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

bool capturing, processing, sending;

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

if(capturing && sending){ // we must be processing as well
...
}

Виявилося, були ситуації, коли припущення у твердженні if вище було хибним.

Прапори, як правило, складаються з часом, і вони приховують фактичний стан класу. Ось чому їх слід уникати.


3
+1, "слід уникати". Я додам щось про "але прапори необхідні в деяких ситуаціях" (дехто може сказати "необхідне зло")
Тревор Бойд Сміт

2
@TrevorBoydSmith На моєму досвіді це не так, вам потрібно лише трохи більше, ніж середня сила мозку, яку ви використали б для прапора
герцогство

У вашому іспиті це повинен був бути один перерахунок, що представляє стан, а не 3 булеви.
user949300

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

38

Ось приклад, коли прапори корисні.

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

За допомогою прапорів назвати цей метод легко:

var password = this.PasswordGenerator.Generate(
    CharacterSet.Digits | CharacterSet.LowercaseLetters | CharacterSet.UppercaseLetters);

і навіть його можна спростити:

var password = this.PasswordGenerator.Generate(CharacterSet.LettersAndDigits);

Без прапорців, який би був підпис методу?

public byte[] Generate(
    bool uppercaseLetters, bool lowercaseLetters, bool digits, bool basicSymbols,
    bool extendedSymbols, bool greekLetters, bool cyrillicLetters, bool unicode);

називається так:

// Very readable, isn't it?
// Tell me just by looking at this code what symbols do I want to be included?
var password = this.PasswordGenerator.Generate(
    true, true, true, false, false, false, false, false);

Як зазначається в коментарях, іншим підходом було б використання колекції:

var password = this.PasswordGenerator.Generate(
    new []
    {
        CharacterSet.Digits,
        CharacterSet.LowercaseLetters,
        CharacterSet.UppercaseLetters,
    });

Це набагато читабельніше порівняно з набором trueта false, але все ж має два недоліки:

Основним недоліком є ​​те, що для того, щоб дозволити комбіновані значення, CharacterSet.LettersAndDigitsви , наприклад , пишете щось подібне у Generate()методі:

if (set.Contains(CharacterSet.LowercaseLetters) ||
    set.Contains(CharacterSet.Letters) ||
    set.Contains(CharacterSet.LettersAndDigits) ||
    set.Contains(CharacterSet.Default) ||
    set.Contains(CharacterSet.All))
{
    // The password should contain lowercase letters.
}

можливо переписати так:

var lowercaseGroups = new []
{
    CharacterSet.LowercaseLetters,
    CharacterSet.Letters,
    CharacterSet.LettersAndDigits,
    CharacterSet.Default,
    CharacterSet.All,
};

if (lowercaseGroups.Any(s => set.Contains(s)))
{
    // The password should contain lowercase letters.
}

Порівняйте це з тим, що у вас є, використовуючи прапори:

if (set & CharacterSet.LowercaseLetters == CharacterSet.LowercaseLetters)
{
    // The password should contain lowercase letters.
}

Другий, дуже незначний недолік полягає в тому, що незрозуміло, як би поводився метод, якщо його викликали так:

var password = this.PasswordGenerator.Generate(
    new []
    {
        CharacterSet.Digits,
        CharacterSet.LettersAndDigits, // So digits are requested two times.
    });

10
Я вважаю, що OP рефлексує на булеві чи цілі змінні, яким ви присвоюєте значення в певних місцях, а потім внизу ви перевіряєте, потім в іншому, щоб зробити щось чи ні, як, наприклад, використовуючи newItem = trueпотім деякі рядки нижчеif (newItem ) then
Tulains Córdova

1
@MainMa Мабуть, є третя: версія з 8 булевими аргументами - це те, про що я подумав, коли читав "прапори" ...
Izkata

4
Вибачте, але IMHO - це ідеальний випадок для ланцюжка методів ( en.wikipedia.org/wiki/Method_chaining ). Крім того, ви можете використовувати масив параметрів (повинен бути асоціативним масивом або картою), де будь-який запис у цьому масиві параметрів ви опускаєте, використовує для цього параметра поведінку за замовчуванням. Зрештою, виклик через ланцюжок методів або масиви параметрів може бути таким же стислим і експресивним, як бітові прапори, також, не у кожній мові є бітові оператори (я насправді люблю бінарні прапори, але використовував би методи, які я щойно згадав).
герцогство, яке відбулося

3
Це не дуже OOP, чи не так? Я зробив би інтерфейс ala: String myNewPassword = makePassword (randomComposeSupplier (новий RandomLowerCaseSupplier (), новий RandomUpperCaseSupplier (), новий RandomNumberSupplier)); за допомогою String makePassword (постачальник <Character> charSupplier); і постачальник <Character> randomComposeSupplier (постачальник <Character> ... постачальників); Тепер ви можете повторно використовувати своїх постачальників для виконання інших завдань, складати їх будь-яким способом, який вам подобається, і спростити метод createPassword, щоб він використовував мінімальний стан.
Діббеке

4
@Dibbeke Поговоріть про королівство іменників ...
Філ

15

Величезний функціональний блок - запах, а не прапори. Якщо ви встановите прапор у рядку 5, тоді перевіряйте лише прапор на лінії 354, це погано. Якщо встановити прапор на лінії 8 і перевірити прапор на рядку 10, це добре. Також один-два прапори на блок коду добре, 300 прапорів у функції погано.


10

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

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


6

Ні, прапори не є поганими або злими, які потрібно реконструювати за будь-яку ціну.

Розгляньте виклик Java Pattern.compile (String regex, int flags) . Це традиційна бітмаска, і вона працює. Погляньте на константи в Яві, і де б ви не побачили купу 2 n, ви знаєте, що там є прапори.

В ідеальному світі, що реконструюється, замість цього можна використовувати EnumSet, де константи замість значень перераховуються, і як написано в документації:

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

У досконалому світі цей виклик Pattern.compile стає Pattern.compile(String regex, EnumSet<PatternFlagEnum> flags).

Все, що сказано, його все ще прапори. Працювати з Pattern.compile("foo", Pattern.CASE_INSENSTIVE | Pattern.MULTILINE)цим набагато простіше, ніж було б мати Pattern.compile("foo", new PatternFlags().caseInsenstive().multiline())чи якийсь інший стиль, намагаючись зробити те, що є насправді і корисними прапорами.

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

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


5

Я припускаю, що ми говоримо про прапори в підписах методу.

Використання одного прапора досить погано.

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

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

if (flag)
   DoFlagSet();
else
   DoFlagNotSet();

Це поганий розподіл проблем, і ви можете нормально знайти шлях.

У мене зазвичай два окремі методи:

public void DoFlagSet() 
{
}

public void DoFlagNotSet()
{
}

Це матиме більше сенсу для назв методів, застосовних до проблеми, яку ви вирішуєте.

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


3

Прапори та більшість тимчасових змінних мають сильний запах. Швидше за все, їх можна буде відновити і замінити методами запитів.

Переглянуто:

Прапори та змінні temp під час вираження стану повинні бути відновлені до методів запитів. Значення стану (булеві, int та інші примати) майже майже завжди повинні бути приховані як частина деталей реалізації.

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


2

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

Прапори можуть чудово працювати, якщо

  • Ви визначили їх у відповідному обсязі. Я маю на увазі, я маю на увазі, що область не повинна містити жодного коду, який їх не повинен / не повинен змінювати. Або принаймні код захищений (наприклад, його не можна викликати безпосередньо ззовні)
  • Якщо потрібно обробляти прапори ззовні, і якщо їх багато, ми можемо кодувати обробник прапорів як єдиний спосіб безпечної зміни прапорів. Цей обробник прапора може сам інкапсулювати прапори та методи їх зміни. Потім його можна зробити однотонним і потім поділитись між класами, яким потрібен доступ до прапорів.
  • І нарешті для ремонту, якщо прапорів занадто багато:
    • Не потрібно говорити, що вони повинні слідувати розумним називанням
    • Повинно бути задокументовано, що є дійсними значеннями (можливо, з перерахуваннями)
    • Потрібно задокументувати, ЯКІЙ КОД ВІДМОВИТЬ кожну з них, а також З ЯКІМ УМОВОМ приведе до прапорця певного значення.
    • ЯКИЙ КОД КОНСУМЕНТУЄ їх і ЩО ПОВЕДЕННЯ призведе до певної цінності

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

Поки ці речі є на місці, я думаю, що це не призведе до безладу.


1

З питання я припустив, що QA означає прапор (глобальні) змінні, а не біти параметра функції.

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


0

Я не думаю, що ніщо не є абсолютним злом у програмуванні ніколи.

Є ще одна ситуація, коли прапори можуть бути в порядку, про які тут ще не говорилося ...

Розглянемо використання закривань у цьому фрагменті Javascript:

exports.isPostDraft = function ( post, draftTag ) {
  var isDraft = false;
  if (post.tags)
    post.tags.forEach(function(tag){ 
      if (tag === draftTag) isDraft = true;
    });
  return isDraft;
}

Внутрішня функція, передана "Array.forEach", не може просто "повернути істину".

Значить, вам потрібно тримати державу назовні прапором.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.