As variáveis ​​flag são um mal absoluto? [fechadas]

47

As variáveis ​​de flag são más? Os seguintes tipos de variáveis ​​são profundamente imorais e é mau usá-las?

"variáveis ​​booleanas ou inteiras às quais você atribui um valor em determinados locais e, abaixo, você faz check-in ou então para fazer algo ou não, como, por exemplo, usando newItem = truealgumas linhas abaixo if (newItem ) then"


Lembro-me de fazer alguns projetos nos quais negligenciei totalmente o uso de sinalizadores e acabei com uma melhor arquitetura / código; no entanto, é uma prática comum em outros projetos em que trabalho e, quando o código cresce e os sinalizadores são adicionados, o espaguete de código IMHO também cresce.

Você diria que há casos em que o uso de sinalizadores é uma boa prática ou mesmo necessário ?, ou você concorda que o uso de sinalizadores no código é ... sinalizador vermelho e deve ser evitado / refatorado; eu, apenas inicio funções / métodos que verificam estados em tempo real.

dukeofgaming
fonte
9
Parece que MainMa e eu temos uma definição diferente de "flag". Eu estava pensando no pré-processador #ifdefs. Qual deles você estava perguntando?
Karl Bielefeldt
Esta é realmente uma boa pergunta. Eu me perguntei muito isso, e realmente me vi dizendo "oh, vamos usar uma bandeira" um pouco demais.
Paul Richter
Booleanos são sinalizadores. (Então, são inteiros, são assim ...)
Thomas Eding
7
@KarlBielefeldt Acredito OP está se referindo a booleano ou inteiro variáveis que você atribuir um valor em determinados lugares, em seguida, abaixo você verificar, em seguida, em orther fazer alguma coisa ou não, como, por exemplo, usando newItem = trueem seguida, algumas linhas abaixoif (newItem ) then
Tulains Córdova
1
Considere também a introdução da refatoração de variáveis ​​explicativas neste contexto. Contanto que o método permaneça curto e tenha um número baixo de caminhos, considero isso um uso válido.
Daniel B

Respostas:

41

O problema que vi ao manter o código que faz uso de sinalizadores é que o número de estados cresce rapidamente e quase sempre existem estados não tratados. Um exemplo da minha própria experiência: eu estava trabalhando em algum código que tinha esses três sinalizadores

bool capturing, processing, sending;

Esses três criaram oito estados (na verdade, havia duas outras bandeiras também). Nem todas as combinações de valores possíveis foram cobertas pelo código e os usuários estavam vendo bugs:

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

Aconteceu que havia situações em que a suposição na declaração if acima era falsa.

Os sinalizadores tendem a se acumular com o tempo e ocultam o estado real de uma classe. É por isso que eles devem ser evitados.

Ben
fonte
3
+1, "deve ser evitado". Gostaria de acrescentar algo sobre 'mas bandeiras são necessários em algumas situações' (alguns podem dizer 'um mal necessário')
Trevor Boyd Smith
2
@TrevorBoydSmith Na minha experiência, eles não são, você apenas exigir um pouco mais do que a potência média do cérebro que você usaria para uma bandeira
dukeofgaming
No seu exame, deveria ter sido um enum único representando o estado, não três booleanos.
user949300
Você pode ter um problema semelhante ao que estou enfrentando agora. Além de cobrir todos os estados possíveis, dois aplicativos podem compartilhar o mesmo sinalizador (por exemplo, upload de dados do cliente). Nesse caso, apenas um remetente usará a sinalização, ativá-la e boa sorte em encontrar o problema no futuro.
Alan Alan
38

Aqui está um exemplo quando os sinalizadores são úteis.

Eu tenho um pedaço de código que gera senhas (usando um gerador de números pseudoaleatórios criptograficamente seguro). O chamador do método escolhe se a senha deve ou não conter letras maiúsculas, minúsculas, dígitos, símbolos básicos, símbolos estendidos, símbolos gregos, cirílicos e unicode.

Com sinalizadores, é fácil chamar esse método:

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

e pode até ser simplificado para:

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

Sem sinalizadores, qual seria a assinatura do método?

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

chamado assim:

// 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);

Conforme observado nos comentários, outra abordagem seria usar uma coleção:

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

Isso é muito mais legível em comparação com o conjunto de truee false, mas ainda tem duas desvantagens:

A principal desvantagem é que, para permitir valores combinados, CharacterSet.LettersAndDigitsvocê escreveria algo assim no Generate()método:

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.
}

possivelmente reescrito assim:

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.
}

Compare isso com o que você tem usando sinalizadores:

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

A segunda desvantagem muito pequena é que não está claro como o método se comportaria se fosse chamado assim:

var password = this.PasswordGenerator.Generate(
    new []
    {
        CharacterSet.Digits,
        CharacterSet.LettersAndDigits, // So digits are requested two times.
    });
Arseni Mourzenko
fonte
10
Acredito OP está se referindo a booleano ou inteiro variáveis que você atribuir um valor em determinados lugares, em seguida, abaixo você verificar, em seguida, em orther fazer alguma coisa ou não, como, por exemplo, usando newItem = trueem seguida, algumas linhas abaixoif (newItem ) then
Tulains Córdova
1
@MainMa Aparentemente há um 3º: A versão com 8 argumentos booleanos são o que eu pensei quando li "bandeiras" ...
Izkata
4
Desculpe, mas IMHO, este é o caso perfeito para o encadeamento de métodos ( pt.wikipedia.org/wiki/Method_chaining ). Além disso, você pode usar um array de parâmetros (deve ser um array ou mapa associativo), onde qualquer entrada nesse array de parâmetros você omite usa o comportamento do valor padrão para esse parâmetro. No final, a chamada via encadeamento de métodos ou matrizes de parâmetros pode ser tão sucinta e expressiva quanto sinalizadores de bits; também, nem toda linguagem possui operadores de bits (na verdade, eu gosto de sinalizadores binários, mas usaria os métodos que acabei de mencionar).
Dukeofgaming 31/10/12
3
Não é muito POO, é? Eu faria uma interface ala: String myNewPassword = makePassword (randomComposeSupplier (new RandomLowerCaseSupplier (), new RandomUpperCaseSupplier (), new RandomUpperCaseSupplier (), new RandomNumberSupplier))); com String makePassword (Supplier <Character> charSupplier); e Fornecedor <Personagem> randomComposeSupplier (Fornecedor <Personagem> ... fornecedores); Agora você pode reutilizar seus fornecedores para outras tarefas, compor da maneira que desejar e simplificar o método generatePassword para que ele use o estado mínimo.
Dibbeke
4
@Dibbeke Fale sobre um reino de substantivos ...
Phil
15

Um enorme bloco funcional é o cheiro, não as bandeiras. Se você definir a bandeira na linha 5, verifique apenas a bandeira na linha 354, isso é ruim. Se você definir o sinalizador na linha 8 e verificar o sinalizador na linha 10, tudo bem. Além disso, um ou dois sinalizadores por bloco de código é bom, 300 sinalizadores em uma função são ruins.

nbv4
fonte
10

Normalmente, os sinalizadores podem ser completamente substituídos por algum tipo de padrão de estratégia, com uma implementação de estratégia para cada valor possível do sinalizador. Isso facilita a adição de novos comportamentos.

Em situações críticas de desempenho, o custo da indireção pode surgir e tornar a desconstrução em sinalizadores claros necessários. Dito isto, estou tendo problemas para lembrar de um único caso em que realmente precisei fazer isso.

back2dos
fonte
6

Não, as bandeiras não são ruins ou um mal que deve ser refatorado a todo custo.

Considere a chamada Pattern.compile do Java (String regex, int flags) . Esta é uma máscara de bits tradicional e funciona. Olhe para as constantes em java e onde quer que você veja um monte de 2 n você sabe que há bandeiras lá.

Em um mundo refatorado ideal, seria possível usar um EnumSet onde as constantes são valores em um enum e conforme a documentação diz:

O desempenho de espaço e tempo dessa classe deve ser bom o suficiente para permitir seu uso como uma alternativa segura e de alta qualidade aos tradicionais "sinalizadores de bits" baseados em int.

Em um mundo perfeito, essa ligação para a Pattern.com se torna Pattern.compile(String regex, EnumSet<PatternFlagEnum> flags).

Tudo o que disse, ainda são bandeiras. É muito mais fácil trabalhar com o Pattern.compile("foo", Pattern.CASE_INSENSTIVE | Pattern.MULTILINE)que seria ter Pattern.compile("foo", new PatternFlags().caseInsenstive().multiline())ou algum outro estilo de tentar fazer o que as bandeiras realmente são e são boas.

Os sinalizadores geralmente são vistos ao trabalhar com itens no nível do sistema. Ao fazer interface com algo no nível do sistema operacional, é provável que haja um sinalizador em algum lugar - seja o valor de retorno de um processo, ou as permissões de um arquivo, ou os sinalizadores para abrir um soquete. Tentar refatorar essas instâncias em alguma caça às bruxas contra um cheiro de código percebido provavelmente acabará com um código pior do que se alguém usado aceitasse e entendesse a sinalização.

O problema ocorre quando as pessoas usam indevidamente bandeiras, jogando-as juntas e criando um conjunto de bandeiras franken de todos os tipos de bandeiras não relacionadas ou tentando usá-las onde não são bandeiras.


fonte
5

Estou assumindo que estamos falando de sinalizadores nas assinaturas de método.

Usar uma única bandeira já é ruim o suficiente.

Isso não significará nada para seus colegas na primeira vez em que eles o virem. Eles terão que olhar o código fonte do método para estabelecer o que ele faz. Você provavelmente estará na mesma posição alguns meses depois, quando esquecer do que se tratava o seu método.

Passar uma bandeira para o método normalmente significa que seu método é responsável por várias coisas. Dentro do método, você provavelmente está fazendo uma verificação simples nas linhas de:

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

Essa é uma separação pobre de preocupações e você normalmente pode encontrar uma maneira de contornar isso.

Normalmente, tenho dois métodos separados:

public void DoFlagSet() 
{
}

public void DoFlagNotSet()
{
}

Isso fará mais sentido com nomes de métodos aplicáveis ​​ao problema que você está resolvendo.

Passar vários sinalizadores é duas vezes pior. Se você realmente precisa passar vários sinalizadores, considere encapsulá-los em uma classe. Mesmo assim, você continuará enfrentando o mesmo problema, pois seu método provavelmente está fazendo várias coisas.

CodeART
fonte
3

As bandeiras e a maioria das variáveis ​​temporárias são um cheiro forte. Provavelmente, eles poderiam ser refatorados e substituídos por métodos de consulta.

Revisado:

Sinalizadores e variáveis ​​temporárias ao expressar estado devem ser refatorados para consultar métodos. Os valores do estado (booleanos, ints e outros primitivos) devem quase sempre estar ocultos como parte dos detalhes da implementação.

Os sinalizadores usados ​​para controle, roteamento e fluxo geral do programa também podem indicar a oportunidade de refatorar seções das estruturas de controle em estratégias ou fábricas separadas, ou o que for adequado à situação, que continuam a usar os métodos de consulta.

JustinC
fonte
2

Quando falamos de sinalizadores, devemos saber que eles serão modificados ao longo do tempo de execução do programa e que afetarão o comportamento do programa com base em seus estados. Contanto que tenhamos controle puro sobre essas duas coisas, elas funcionarão muito bem.

Bandeiras podem funcionar muito bem se

  • Você os definiu em um escopo apropriado. Por apropriado, quero dizer que o escopo não deve conter nenhum código que não precise / não deva modificá-los. Ou pelo menos o código é seguro (por exemplo, ele não pode ser chamado diretamente de fora)
  • Se houver necessidade de manipular sinalizadores de fora e se houver muitos sinalizadores, podemos codificar o manipulador de sinalizadores como uma única maneira de modificar os sinalizadores com segurança. Esse manipulador de sinalizador pode encapsular sinalizadores e métodos para modificá-los. Em seguida, ele pode ser transformado em singleton e, em seguida, compartilhado entre as classes que precisam acessar os sinalizadores.
  • E, finalmente, para manutenção, se houver muitos sinalizadores:
    • Não há necessidade de dizer que eles devem seguir nomes sensíveis
    • Deve ser documentado com os valores válidos (pode ser com enumerações)
    • Deve ser documentado com QUALQUER CÓDIGO MODIFICARÁ cada um deles e também com QUALQUER CONDIÇÃO resultará na atribuição de um valor específico ao sinalizador.
    • QUAL O CÓDIGO OS CONSUMIRÁ E QUAL O COMPORTAMENTO resultará em um valor específico

Se houver muitas bandeiras, o bom trabalho de design deve preceder, pois as bandeiras começam a desempenhar um papel fundamental no comportamento do programa. Você pode ir para diagramas de estado para modelagem. Esses diagramas também funcionam como documentação e orientação visual ao lidar com eles.

Enquanto essas coisas estiverem no lugar, acho que não levará à bagunça.

Mahesha999
fonte
1

Presumi da pergunta que o controle de qualidade significava variáveis ​​de flag (global) e não bits de um parâmetro de função.

Há situações em que você não tem muitas outras possibilidades. Por exemplo, sem um sistema operacional, você deve avaliar as interrupções. Se uma interrupção ocorrer com muita frequência e você não tiver tempo para fazer uma avaliação longa no ISR, não é apenas permitido, mas às vezes até a melhor prática definir apenas alguns sinalizadores globais no ISR (você deve gastar o mínimo de tempo possível no ISR) e para avaliar esses sinalizadores no loop principal.

vsz
fonte
0

Eu acho que nada é absolutamente um mal na programação.

Há outra situação em que as bandeiras podem estar em ordem, que ainda não foram mencionadas aqui ...

Considere o uso de fechamentos neste snippet Javascript:

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

A função interna, sendo passada para "Array.forEach", não pode simplesmente "retornar true".

Portanto, você precisa manter o estado externo com uma bandeira.

firstdoit
fonte