Pergunta

é literais embutir sempre aceitável? , mas eu 'm pensando especificamente de 'cordas mágicas' aqui.

Em um projeto grande, temos uma tabela de opções de configuração como estes:

Name         Value
----         -----
FOO_ENABLED  Y
BAR_ENABLED  N
...

(Centenas deles).

A prática comum é chamar uma função genérica para testar uma opção como esta:

if (config_options.value('FOO_ENABLED') == 'Y') ...

(Claro, esta mesma opção pode precisam ser verificados em muitos lugares no código do sistema.)

Ao adicionar uma nova opção, eu estava considerando a adição de uma função para esconder a "string mágica" como esta:

if (config_options.foo_enabled()) ...

No entanto, colegas pensaram que eu tinha ao mar foi e opôs-se fazer isso, preferindo a embutir porque:

  • Isso é o que fazemos normalmente
  • Ela torna mais fácil ver o que está acontecendo quando a depuração do código

O problema é que eu posso ver o seu ponto! Realisticamente, nós nunca vamos mudar o nome das opções, por qualquer razão, então a única vantagem que eu posso pensar para a minha função é que o compilador iria pegar qualquer erro de digitação como fo_enabled (), mas não 'FO_ENABLED'.

O que você acha? Eu perdi todas as outras vantagens / desvantagens?

Foi útil?

Solução

if (config_options.isTrue('FOO_ENABLED')) {...
}

Restringir o seu check codificado Y a um lugar, mesmo que isso signifique escrever uma classe wrapper para seu mapa.

if (config_options.isFooEnabled()) {...
}

Pode parecer tudo bem até que você tenha 100 opções de configuração e 100 métodos (por isso aqui você pode fazer um julgamento sobre o crescimento futura aplicação e necessidades antes de decidir sobre a sua implementação). Caso contrário, é melhor ter uma classe de cordas estáticas para nomes de parâmetro.

if (config_options.isTrue(ConfigKeys.FOO_ENABLED)) {...
}

Outras dicas

Se eu usar uma corda uma vez no código, eu geralmente não se preocupar com tornando-se um lugar constante.

Se eu usar uma corda duas vezes no código, eu vou consideram tornando-se uma constante.

Se eu usar uma seqüência de três vezes no código, eu vou certamente fazê-lo uma constante.

Eu percebo a questão é antiga, mas ele veio na minha margem.

AFAIC, a questão aqui não foi identificado com precisão, seja na questão, ou as respostas. Esqueça 'harcoding cordas" ou não, por um momento.

  1. O banco de dados tem uma tabela de referência, contendo config_options. O PK é uma string.

  2. Existem dois tipos de PKs:

    • identificadores significativos, que os usuários (e desenvolvedores) ver e usar. Estes PKs é suposto ser estável, que pode ser invocado.

    • colunas Id sem sentido que os usuários nunca devem ver, que os desenvolvedores tem que estar ciente de, e de código ao redor. Estes não pode ser invocado.

  3. É comum, normal, para escrever código usando o valor absoluto de um IF CustomerCode = "IBM" ... PK significativo ou IF CountryCode = "AUS" etc.

    • referenciando o valor absoluto de um PK sem sentido não é aceitável (devido à auto-incremento; lacunas sejam alteradas; valores que estão sendo substituídos por atacado)
      . .
  4. A sua mesa de referência usa PKs significativas. Fazendo referência a essas strings literais no código é inevitável. Escondendo o valor irá fazer a manutenção mais difícil; o código não literal é; seus colegas estão certos. Além disso, há a função redundante adicional que mastiga ciclos. Se houver um erro de digitação no literal, você encontrará logo que fora durante os testes Dev, muito antes UAT.

    • centenas de funções para centenas de literais é um absurdo. Se você fizer implementar uma função, então Normalize seu código, e fornecer uma única função que pode ser usado para qualquer um dos centenas de literais. Nesse caso, estamos de volta a um literal nu, e a função pode ser dispensado.

    • o ponto é, a tentativa de esconder o literal não tem valor.
      .

  5. Ele não pode ser interpretado como "hardcode", que é algo bem diferente. Eu acho que é onde o problema é, identificar essas construções como "codificado". Ele está apenas fazendo referência a um meaningfull PK literalmente.

  6. Agora, a partir da perspectiva de qualquer segmento de código somente, se você usar o mesmo valor algumas vezes, você pode melhorar o código através da captação da string literal em uma variável e, em seguida, usando a variável no resto do bloco de código. Certamente não uma função. Mas isso é uma questão de eficiência e boas práticas. Mesmo que não muda o IF CountryCode = @cc_aus efeito

Na minha experiência, esse tipo de problema está mascarando um problema mais profundo:. Falha fazer OOP real e seguir o princípio de DRY

Em poucas palavras, capturar a decisão no momento da inicialização por uma definição adequada para cada ação dentro as declarações if, e depois jogar fora tanto a config_options e os testes em tempo de execução.

Detalhes abaixo.

O uso de amostra foi:

if (config_options.value('FOO_ENABLED') == 'Y') ...

o que levanta a pergunta óbvia: "O que está acontecendo no reticências?", Especialmente tendo em conta a seguinte declaração:

(Claro, esta mesma opção pode precisam ser verificados em muitos lugares no código do sistema.)

Vamos supor que cada um desses valores config_option realmente correspondem a um conceito de domínio único problema (ou estratégia de implementação).

Em vez de fazer isso (várias vezes, em vários lugares ao longo do código):

  1. Faça um string (tag),
  2. Encontre a sua correspondente outra string (valor),
  3. Teste esse valor como um booleano equivalente,
  4. Com base nesse teste, decidir se deseja executar alguma ação.

Eu sugiro encapsular o conceito de uma "ação configurável".

Vamos tomar como exemplo (obviamente apenas como hypthetical como FOO_ENABLED ... ;-) que seu código tem de trabalhar em qualquer unidades inglesas ou unidades métricas. Se METRIC_ENABLED é "verdade", converter os dados inseridos pelo usuário de métrica para Inglês para computação interna, e converter de volta antes de resultados exibindo.

Definir uma interface:

public interface MetricConverter {
    double toInches(double length);
    double toCentimeters(double length);
    double toPounds(double weight);
    double toKilograms(double weight);
}

que identifica em um só lugar todo o comportamento associado com o conceito de METRIC_ENABLED.

Em seguida, escreva implementações concretas de todas as maneiras que esses comportamentos estão a ser levadas a cabo:

public class NullConv implements MetricConverter {
    double toInches(double length) {return length;}
    double toCentimeters(double length) {return length;}
    double toPounds(double weight)  {return weight;}
    double toKilograms(double weight)  {return weight;}
}

e

// lame implementation, just for illustration!!!!
public class MetricConv implements MetricConverter {
    public static final double LBS_PER_KG = 2.2D;
    public static final double CM_PER_IN = 2.54D
    double toInches(double length) {return length * CM_PER_IN;}
    double toCentimeters(double length) {return length / CM_PER_IN;}
    double toPounds(double weight)  {return weight * LBS_PER_KG;}
    double toKilograms(double weight)  {return weight / LBS_PER_KG;}
}

Na inicialização tempo, em vez de carregar um monte de valores config_options, inicializar um conjunto de ações configuráveis, como em:

MetricConverter converter = (metricOption()) ? new MetricConv() : new NullConv();

(onde o metricOption() expressão acima é um stand-in para o que one-time-only verificar se você precisa fazer, incluindo a olhar para o valor de METRIC_ENABLED; -)

Então, onde quer que o código teria dito:

double length = getLengthFromGui();
if (config_options.value('METRIC_ENABLED') == 'Y') {
    length = length / 2.54D;
}
// do some computation to produce result
// ...
if (config_options.value('METRIC_ENABLED') == 'Y') {
    result = result * 2.54D;
}
displayResultingLengthOnGui(result);

reescrevê-la como:

double length = converter.toInches(getLengthFromGui());
// do some computation to produce result
// ...
displayResultingLengthOnGui(converter.toCentimeters(result));

Uma vez que todos os detalhes de implementação relacionados com que um conceito agora são embalados de forma limpa, toda a manutenção futura relacionada com METRIC_ENABLED pode ser feito em um só lugar. Além disso, o tempo de execução trade-off é uma vitória; o "overhead" de invocar um método é trivial em comparação com a sobrecarga de buscar um valor de cadeia a partir de um mapa e realizando String # iguais.

Eu acredito que as duas razões que você mencionou, erro de ortografia possível na corda, que não podem ser detectados até que tempo de execução ea possibilidade (embora magro) de uma mudança de nome justificaria a sua ideia.

Além de funções que você pode obter dactilografadas, agora parece que você apenas booleans loja, e se você precisa armazenar um int, uma corda etc. Eu prefiro usar get_foo () com um tipo, que get_string ( "FOO ") ou get_int (" FOO ").

Eu realmente deveria usar constantes e literais não codificados duro.

Você pode dizer que eles não serão alterados, mas você nunca pode saber. E é melhor para torná-lo um hábito. Para usar constantes simbólicas.

Eu acho que existem duas questões diferentes aqui:

  • No projeto atual, a convenção de usar cordas codificados já está bem estabelecido, então todos os desenvolvedores que trabalham no projeto estão familiarizados com ele. Pode ser uma convenção sub-óptima para todas as razões que foram listados, mas todos familiarizados com o código pode olhar para ele e sabe instintivamente o que o código é suposto fazer. Alterar o código para que, em certas partes, ele usa a funcionalidade "novo" vai tornar o código um pouco mais difícil de ler (porque as pessoas terão de pensar e lembrar o que a nova convenção faz) e, portanto, um pouco mais difícil de manter. Mas eu acho que a mudança ao longo de todo o projeto para a nova convenção seria potencialmente ser proibitivamente caro, a menos que você pode rapidamente roteiro a conversão.
  • Em um Novo do projeto, constantes simbólicas são a maneira IMO, por todas as razões listadas. Especialmente , porque qualquer coisa que faz com que os erros do compilador de captura em tempo de compilação que poderiam ser capturados por um ser humano em tempo de execução é uma convenção muito útil para estabelecer.

Eu também prefiro uma classe de configuração fortemente tipado se for utilizado durante todo o código. Com métodos devidamente nomeadas você não perder qualquer legibilidade. Se você precisa fazer conversões de strings para outro tipo de dados (decimal / float / int), você não precisa repetir o código que faz a conversão em vários lugares e pode armazenar em cache o resultado até a conversão só ocorre uma vez. Você já tem a base deste no lugar já então eu não acho que seria preciso muito para se acostumar com o new maneira de fazer as coisas.

Outra coisa a considerar é a intenção. Se você estiver em um projeto que requer localização cordas codificados duros podem ser ambíguos. Considere o seguinte:

const string HELLO_WORLD = "Hello world!";
print(HELLO_WORLD);

A intenção do programador é clara. Usando uma constante implica que esta string não precisam ser localizada. Agora olhe para este exemplo:

print("Hello world!");

Aqui nós não têm tanta certeza. Será que o programador realmente não quero isso string a ser localizada ou se o programador esquecer de localização, enquanto ele estava escrevendo esse código?

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top