Являются ли переменные флага абсолютным злом? [закрыто]


47

Являются ли переменные флага злыми? Являются ли перечисленные ниже переменные глубоко аморальными, и стоит ли их использовать?

«Булевы или целочисленные переменные, которым вы присваиваете значение в определенных местах, а затем внизу, вы проверяете, затем в другом, что-то делаете или нет, как, например, используя newItem = trueзатем несколько строк ниже if (newItem ) then»


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

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


9
Кажется, у нас с MainMa другое определение «флаг». Я думал о препроцессоре #ifdefs. О ком ты спрашивал?
Карл Билефельдт

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

Booleans - это флаги. (Так же целые, так и ...)
Томас Эдинг

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

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

Ответы:


41

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

bool capturing, processing, sending;

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

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

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

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


3
+1, "следует избегать". Я бы добавил кое-что о «но флаги необходимы в некоторых ситуациях» (некоторые могут сказать «неизбежное зло»)
Тревор Бойд Смит

2
@TrevorBoydSmith По моему опыту это не так, вам просто нужно чуть больше, чем средняя мощность мозга, которую вы бы использовали для флага
dukeofgaming

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

Вы можете столкнуться с проблемой, с которой я сталкиваюсь сейчас. Помимо покрытия всех возможных состояний, два приложения могут использовать один и тот же флаг (например, загрузка данных клиента). В этом случае только один Uploader будет использовать флаг, отключить его и удачи в поиске проблемы в будущем.
Алан

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 ). Кроме того, вы можете использовать массив параметров (должен быть ассоциативным массивом или картой), где любая запись в этом массиве параметров Вы опускаете, использует поведение значения по умолчанию для этого параметра. В конце концов, вызов через цепочку методов или массивы параметров может быть таким же кратким и выразительным, как битовые флаги, также не у каждого языка есть битовые операторы (я на самом деле люблю двоичные флаги, но вместо этого использовал бы методы, которые я только что упомянул).
dukeofgaming

3
Это не очень ООП, не так ли? Я бы сделал интерфейс аля: String myNewPassword = makePassword (randomComposeSupplier (new RandomLowerCaseSupplier (), new RandomUpperCaseSupplier (), new RandomNumberSupplier)); с помощью String makePassword (Supplier <Character> charSupplier); и Supplier <Character> randomComposeSupplier (Supplier <Character> ... поставщики); Теперь вы можете повторно использовать своих поставщиков для других задач, составлять их любым удобным для вас способом и упростить свой метод generatePassword, чтобы он использовал минимальное состояние.
Диббеке

4
@Dibbeke Разговор о царстве существительных ...
Фил

15

Огромный функциональный блок - это запах, а не флаги. Если вы установите флаг в строке 5, тогда проверьте только флаг в строке 354, тогда это плохо. Если вы установите флаг в строке 8 и проверьте флаг в строке 10, это нормально. Кроме того, один или два флага на блок кода - это хорошо, 300 флагов в функции - это плохо.


10

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

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


6

Нет, флаги - это не плохо и не зло, которое нужно рефакторизовать любой ценой.

Рассмотрим вызов Java Pattern.compile (строковое регулярное выражение, int flags) . Это традиционная битовая маска, и она работает. Посмотрите на константы в java и везде, где вы видите группу из 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

Флаги и большинство временных переменных являются сильным запахом. Скорее всего, они могут быть реорганизованы и заменены методами запросов.

После доработки:

Флаги и временные переменные при выражении состояния должны быть реорганизованы в методы запросов. Значения состояния (логические значения, целые числа и другие примитивы) должны почти всегда скрываться как часть деталей реализации.

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


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», не может просто «вернуть true».

Следовательно, вы должны держать государство снаружи с флагом.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.