As variáveis de sinalização são um mal absoluto? [fechadas]

44

As variáveis de sinalização são más? O seguinte tipo de variáveis é profundamente imoral e é perverso usá-las?

"boolean or integer variables that you assign a value in certain places then down below you check then in orther to do something or not, like, for example using newItem = true then some lines below if (newItem ) then"


Lembro-me de fazer alguns projetos em que negligenciei totalmente o uso de sinalizadores e acabei com uma arquitetura / código melhor; 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 com 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 em código é ... sinalizadores e deve ser evitado / refatorado; eu, acabei de fazer as funções / métodos que verificam os estados em tempo real.

    
por dukeofgaming 31.10.2012 / 19:29
fonte

10 respostas

38

O problema que tenho visto ao manter código que faz uso de sinalizadores é que o número de estados cresce rapidamente e quase sempre há estados não manipulados. Um exemplo da minha própria experiência: eu estava trabalhando em algum código que tinha essas três flags

bool capturing, processing, sending;

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

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

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

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

    
por 31.10.2012 / 20:04
fonte
37

Veja um exemplo de quando os sinalizadores são úteis.

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

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

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

Como 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 true e false , mas ainda tem duas desvantagens:

A principal desvantagem é que, para permitir valores combinados, como CharacterSet.LettersAndDigits , você estaria escrevendo algo assim no método 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.
}

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 menor, é 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.
    });
    
por 31.10.2012 / 19:52
fonte
15

Um bloco de função enorme é o cheiro, não as bandeiras. Se você definir o sinalizador na linha 5, verifique apenas o sinalizador na linha 354, e 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 flags em uma função é ruim.

    
por 01.11.2012 / 01:31
fonte
9

Geralmente, 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 muito 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 tive que fazer isso.

    
por 31.10.2012 / 19:55
fonte
6

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

Considere o Java Pattern.compile (String regex, int flags) . Este é um bitmask tradicional e funciona. Olhe para as constantes em java e onde quer que você veja um monte de 2 n você sabe que existem bandeiras por aí.

Em um mundo refatorado ideal, você deve usar um EnumSet onde as constantes são valores em um enum e como a documentação lê:

The space and time performance of this class should be good enough to allow its use as a high-quality, typesafe alternative to traditional int-based "bit flags."

Em um mundo perfeito, essa chamada Pattern.compile se torna Pattern.compile(String regex, EnumSet<PatternFlagEnum> flags) .

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

Os sinalizadores geralmente são vistos quando se trabalha 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. Tentando refatorar esses exemplos em alguma caça às bruxas contra um cheiro de código percebido provavelmente terminará com um código pior do que se um deles aceitasse e entendesse a bandeira.

O problema ocorre quando as pessoas fazem uso indevido de bandeiras jogando-as juntas e criando um conjunto de frankenflags de todos os tipos de sinalizadores não relacionados ou tentando usá-los onde não são sinalizadores.

    
por 31.10.2012 / 21:56
fonte
5

Estou assumindo que estamos falando de sinalizadores dentro de assinaturas de métodos.

Usar uma única bandeira é ruim o suficiente.

Isso não significará nada para seus colegas na primeira vez que eles o virem. Eles terão que examinar 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 o que seu método era.

Passar um sinalizador 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 simples verificação nas linhas de:

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

Essa é uma má separação de interesses e normalmente você pode encontrar uma maneira de contornar isso.

Eu normalmente tenho dois métodos separados:

public void DoFlagSet() 
{
}

public void DoFlagNotSet()
{
}

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

A passagem de vários sinalizadores é duas vezes pior. Se você realmente precisar passar vários sinalizadores, considere encapsulá-los em uma classe. Mesmo assim, você ainda estará enfrentando o mesmo problema, já que seu método provavelmente está fazendo várias coisas.

    
por 31.10.2012 / 20:06
fonte
3

Bandeiras e a maioria das variáveis temporárias são um cheiro strong. O mais provável é que eles possam ser refatorados e substituídos por métodos de consulta.

Revisado:

Sinalizadores e variáveis temporárias ao expressar o estado devem ser refatorados para os métodos de consulta. Os valores de estado (booleanos, ints e outros primativos) devem quase sempre ser ocultados como parte dos detalhes da implementação.

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

    
por 31.10.2012 / 20:09
fonte
2

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

Os sinalizadores podem funcionar 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, pode não ser chamado diretamente de fora)
  • Se houver necessidade de manipular sinalizadores de fora e se houver muitos sinalizadores, poderemos codificar o manipulador de sinalizador como a única maneira de modificar com segurança os sinalizadores. Esse manipulador de sinalizador pode encapsular sinalizadores e métodos para modificá-los. Pode então ser feito singleton e pode ser compartilhado entre classes que precisam de acesso a flags.
  • E, finalmente, para manutenção, se houver muitos sinalizadores:
    • Não é preciso dizer que eles devem seguir uma nomenclatura sensata
    • Deve ser documentado com o que são valores válidos (podem estar com enumerações)
    • Deve ser documentado com QUE CÓDIGO MODIFICARÁ cada um deles e também com QUAL QUE CONDIÇÃO resultará na atribuição de um valor específico à bandeira.
    • QUAL CÓDIGO OS CONSUMIRÁ E QUALQUER COMPORTAMENTO resultará em um valor específico

Se houver muitos sinalizadores, um bom trabalho de design deve preceder, pois os sinalizadores então começam a desempenhar papel-chave 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 isso não levará à confusão.

    
por 01.11.2012 / 10:02
fonte
1

Eu assumi pela questão que o QA estava significando as variáveis flag (globais), e não bits de um parâmetro de função.

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

    
por 01.11.2012 / 12:18
fonte
0

Eu não acho que qualquer coisa seja um mal absoluto na programação, nunca.

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

Considere o uso de closures neste snippet de 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 o "Array.forEach", não pode simplesmente "retornar true".

Portanto, você precisa manter o estado do lado de fora com uma bandeira.

    
por 07.11.2012 / 22:36
fonte