Código Limpo - Devo alterar o literal 1 para uma constante?

14

Para evitar números mágicos, geralmente ouvimos que devemos dar um nome significativo a um literal. Tal como:

//THIS CODE COMES FROM THE CLEAN CODE BOOK
for (int j = 0; j < 34; j++) {
    s += (t[j] * 4) / 5;
}

-------------------- Change to --------------------

int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for (int j = 0; j < NUMBER_OF_TASKS; j++) {
    int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
    int realTaskWeeks = (realdays / WORK_DAYS_PER_WEEK);
    sum += realTaskWeeks;
}

Eu tenho um método fictício como este:

Explique: Suponho que tenho uma lista de pessoas para servir e, por padrão, gastamos US $ 5 para comprar apenas comida, mas quando temos mais de uma pessoa, precisamos comprar água e comida, precisamos gastar mais dinheiro, talvez US $ 6. Vou mudar meu código, por favor , foque no literal 1 , minha pergunta sobre isso.

public int getMoneyByPersons(){
    if(persons.size() == 1){ 
        // TODO - return money for one person
    } else {
        // TODO - calculate and return money for people.
    }

}

Quando pedi a meus amigos que revisassem meu código, um disse que dar um nome ao valor 1 produziria um código mais limpo e o outro disse que não precisamos de um nome constante aqui porque o valor é significativo por si só.

Então, minha pergunta é Devo dar um nome para o valor literal 1? Quando um valor é um número mágico e quando não é? Como posso distinguir o contexto para escolher a melhor solução?

Jack
fonte
De onde personsvem e o que descreve? Seu código não possui comentários, portanto é difícil adivinhar o que está fazendo.
Hubert Grzeskowiak
7
Ele não fica mais claro que 1. Eu mudaria "tamanho" para "contar". E moneyService é estúpido, pois deve poder decidir quanto dinheiro devolver, dependendo da coleção de pessoas. Então, eu passava pessoas para o getMoney e deixava que ele resolvesse casos excepcionais.
Martin Maat
4
Você também pode extrair a expressão lógica da sua cláusula if para um novo método. Talvez chame isso de IsSinglePerson (). Dessa forma, você extrair um pouco mais do que apenas que uma variável, mas também tornar a cláusula if um pouco mais legível ...
selmaohneh
2
Talvez essa lógica seja mais adequada em moneyService.getMoney ()? Haveria algum momento em que você precisaria ligar para getMoney no caso de uma pessoa? Mas eu concordaria com o sentimento geral de que 1 é claro. Números mágicos são números que você tem que coçar a cabeça perguntando como o programador chegou a esse número .. ieif(getErrorCode().equals(4095)) ...
Neil

Respostas:

23

Não. Nesse exemplo, 1 é perfeitamente significativo.

No entanto, e se persons.size () for zero? Parece estranho que persons.getMoney()funciona para 0 e 2, mas não para 1.

user949300
fonte
Eu concordo que 1 é significativo. Obrigado, a propósito, atualizei minha pergunta. Você pode vê-lo novamente para ter minha ideia.
Jack
3
Uma frase que ouvi várias vezes ao longo dos anos é que os números mágicos são constantes sem nome que não sejam 0 e 1. Embora eu possa pensar em exemplos onde esses números devem ser nomeados, é uma regra bastante simples seguir 99% dos Tempo.
precisa
@ Baldrickk Às vezes, outros literais numéricos também não devem ser escondidos atrás de um nome.
Deduplicator
@Duplicator algum exemplo vem à mente?
precisa
@Baldrickk Ver amon .
Deduplicator
18

Por que um pedaço de código contém esse valor literal específico?

  • Esse valor tem um significado especial no domínio do problema ?
  • Ou esse valor é apenas um detalhe de implementação , onde esse valor é uma conseqüência direta do código circundante?

Se o valor literal tiver um significado que não esteja claro no contexto, será útil dar um nome a esse valor por meio de uma constante ou variável. Mais tarde, quando o contexto original for esquecido, o código com nomes de variáveis ​​significativos será mais sustentável. Lembre-se de que o público do seu código não é primariamente o compilador (o compilador funcionará alegremente com código horrível), mas os futuros mantenedores desse código - que apreciarão se o código é um pouco autoexplicativo.

  • Em seu primeiro exemplo, o significado de literais, como 34, 4, 5não é aparente a partir do contexto. Em vez disso, alguns desses valores têm um significado especial no domínio do problema. Portanto, era bom dar nomes a eles.

  • No seu segundo exemplo, o significado do literal 1é muito claro a partir do contexto. Introduzir um nome não é útil.

De fato, a introdução de nomes para valores óbvios também pode ser ruim, pois oculta o valor real.

  • Isso pode ocultar bugs se o valor nomeado for alterado ou estiver incorreto, especialmente se a mesma variável for reutilizada em partes de código não relacionadas.

  • Um pedaço de código também pode funcionar bem para um valor específico, mas pode estar incorreto no caso geral. Ao introduzir abstração desnecessária, o código não está mais obviamente correto.

Não há limite de tamanho para literais "óbvios" porque isso depende totalmente do contexto. Por exemplo, o literal 1024pode ser totalmente óbvio no contexto do cálculo do tamanho do arquivo, ou o literal 31no contexto de uma função hash, ou o literal padding: 0.5emno contexto de uma folha de estilo CSS.

amon
fonte
8

Existem vários problemas com esse trecho de código, que, a propósito, podem ser abreviados assim:

public List<Money> getMoneyByPersons() {
    return persons.size() == 1 ?
        moneyService.getMoneyIfHasOnePerson() :
        moneyService.getMoney(); 
}
  1. Não está claro por que uma pessoa é um caso especial. Suponho que exista uma regra comercial específica que indique que receber dinheiro de uma pessoa é radicalmente diferente de receber dinheiro de várias pessoas. No entanto, eu tenho que ir e olhar dentro de ambosgetMoneyIfHasOnePerson e getMoney, na esperança de entender por que existem casos distintos.

  2. O nome getMoneyIfHasOnePerson não parece certo. Pelo nome, eu esperaria que o método verifique se existe uma única pessoa e, se for esse o caso, receba dinheiro dele; caso contrário, não faça nada. No seu código, não é isso que está acontecendo (ou você está fazendo a condição duas vezes).

  3. Existe algum motivo para retornar um List<Money> uma coleção em vez de uma coleção?

Voltando à sua pergunta, porque não está claro por que existe um tratamento especial para uma pessoa, o dígito um deve ser substituído por uma constante, a menos que exista outra maneira de tornar as regras explícitas. Aqui, um não é muito diferente de qualquer outro número mágico. Você pode ter regras comerciais informando que o tratamento especial se aplica a uma, duas ou três pessoas ou apenas a mais de doze pessoas.

Como posso distinguir o contexto para escolher a melhor solução?

Você faz o que torna seu código mais explícito.

Exemplo 1

Imagine o seguinte pedaço de código:

if (sequence.size() == 0) {
    return null;
}

return this.processSequence(sequence);

Aqui zero é um valor mágico? O código é bastante claro: se não houver elementos na sequência, não vamos processá-lo e retornar um valor especial. Mas esse código também pode ser reescrito assim:

if (sequence.isEmpty()) {
    return null;
}

return this.processSequence(sequence);

Aqui, não mais constante, e o código é ainda mais claro.

Exemplo 2

Pegue outro pedaço de código:

const result = Math.round(input * 1000) / 1000;

Não leva muito tempo para entender o que ele faz em linguagens como JavaScript que não possuem round(value, precision)sobrecarga.

Agora, se você deseja introduzir uma constante, como seria chamada? O termo mais próximo que você pode obter é Precision. Então:

const precision = 1000;
const result = Math.round(input * precision) / precision;

Melhora a legibilidade? Pode ser. Aqui, o valor de uma constante é bastante limitado, e você pode se perguntar se realmente precisa executar a refatoração. O bom aqui é que agora, a precisão é declarada apenas uma vez; portanto, se ela mudar, você não corre o risco de cometer um erro como:

const result = Math.round(input * 100) / 1000;

alterando o valor em um local e esquecendo de fazê-lo no outro.

Exemplo 3

A partir desses exemplos, você pode ter a impressão de que os números devem ser substituídos por constantes em todos os casos . Isso não é verdade. Em algumas situações, ter uma constante não leva à melhoria do código.

Pegue o seguinte pedaço de código:

class Point
{
    ...
    public void Reset()
    {
        x, y = (0, 0);
    }
}

Se você tentar substituir zeros por uma variável, a dificuldade seria encontrar um nome significativo. Como você o nomearia? ZeroPosition? Base? Default? A introdução de uma constante aqui não aprimora o código de forma alguma. Isso tornaria um pouco mais longo, e exatamente isso.

Tais casos são contudo raros. Portanto, sempre que encontrar um número no código, faça um esforço para descobrir como o código pode ser refatorado. Pergunte a si mesmo se existe um significado comercial para o número. Se sim, uma constante é obrigatória. Se não, como você nomearia o número? Se você encontrar um nome significativo, isso é ótimo. Caso contrário, é provável que você encontre um caso em que a constante seja desnecessária.

Arseni Mourzenko
fonte
Eu acrescentaria 3a: não use a palavra dinheiro em nomes de variáveis, use quantidade, saldo ou algo parecido. Uma coleção de dinheiro não faz sentido, coleções de valores ou saldos faz. (OK, eu tenho uma pequena coleção de dinheiro estrangeiro (ou melhor, moedas), mas isso é uma questão diferente de não programação).
Bent
3

Você pode criar uma função que use um único parâmetro e retorne quatro vezes dividido por cinco que ofereça um alias limpo ao que faz enquanto ainda estiver usando o primeiro exemplo.

Estou apenas oferecendo minha própria estratégia usual, mas talvez eu também aprenda algo.

O que estou pensando é.

// Just an example name  
function normalize_task_value(task) {  
    return (task * 4) / 5;  // or to `return (task * 4) / NUMBER_OF_TASKS` but it really matters what logic you want to convey and there are reasons to many ways to make this logical. A comment would be the greatest improvement

}  

// Is it possible to just use tasks.length or something like that?  
// this NUMBER_OF_TASKS is the only one thats actually tricky to   
// understand right now how it plays its role.  

function normalize_all_task_values(tasks, accumulator) {  
    for (int i = 0; i < NUMBER_OF_TASKS; i++) {  
        accumulator += normalize_task_value(tasks[i]);
    }
}  

Desculpe se estou fora da base, mas sou apenas um desenvolvedor de javascript. Não tenho certeza de qual composição justifiquei isso, acho que nem tudo precisa estar em uma matriz ou lista, mas .length faria muito sentido. E o * 4 tornaria o bem constante, pois sua origem é nebulosa.

etisdew
fonte
5
Mas o que esses valores 4 e 5 significam? Se um deles precisar mudar, você poderá encontrar todos os locais em que o número é usado em um contexto semelhante e atualizar esses locais adequadamente? Esse é o ponto crucial da pergunta original.
Bart van Ingen Schenau
Penso que o primeiro exemplo foi bastante autoexplicativo, pelo que pode não ser o que posso responder. A operação não deve mudar no futuro, quando você precisar, eu escreveria uma nova para realizar o que é necessário. Os comentários são o que eu acho que mais beneficiariam esse objetivo. É uma ligação de uma linha para reduzir no meu idioma. Quão importante é tornar absolutamente compreensível para o leigo?
etisdew
@BartvanIngenSchenau A função inteira é nomeada incorretamente. Eu preferiria um nome de função melhor, um comentário com uma especificação sobre o que a função deve retornar e um comentário por que * 4/5 alcança isso. Nomes constantes não são realmente necessários.
precisa saber é o seguinte
@ gnasher729: Se as constantes aparecerem exatamente uma vez no código fonte de uma função bem nomeada e bem documentada, talvez não seja necessário usar uma constante nomeada. Assim que uma constante aparecer várias vezes com o mesmo significado, dar um nome a essa constante garante que você não precise descobrir se todas essas instâncias do literal 42 significam a mesma coisa ou não.
Bart van Ingen Schenau
No centro disso, se você espera, pode precisar alterar o valor sim. Eu mudaria para uma const para representar a variável, no entanto. Eu não acho que é o caso e isso deve ser simplesmente uma variável intermediária que reside no escopo acima dele. Eu não quero copiar e dizer fazer de tudo um parâmetro para uma função, mas se puder mudar, mas raramente eu o tornaria um parâmetro interno para essa função ou escopo de objeto / estrutura em que você está trabalhando para fazer essa alteração sair o escopo global sozinho.
etisdew
2

Esse número 1, poderia ser um número diferente? Poderia ser 2 ou 3, ou existem razões lógicas pelas quais deve ser 1? Se precisar ser 1, usar 1 é bom. Caso contrário, você pode definir uma constante. Não chame isso de constante. (Eu já vi isso).

60 segundos em um minuto - você precisa de uma constante? Bem, são 60 segundos, não 50 ou 70. E todo mundo sabe disso. Então isso pode permanecer um número.

60 itens impressos por página - esse número poderia ter sido facilmente 59 ou 55 ou 70. Na verdade, se você alterar o tamanho da fonte, ele pode se tornar 55 ou 70. Portanto, aqui é necessária uma constante significativa.

Também é uma questão de quão claro é o significado. Se você escrever "minutos = segundos / 60", está claro. Se você escrever "x = y / 60", isso não está claro. Deve haver alguns nomes significativos em algum lugar.

Há uma regra absoluta: não há regras absolutas. Com a prática, você descobrirá quando usar números e quando usar constantes nomeadas. Não faça isso porque um livro diz isso - até você entender por que diz isso.

gnasher729
fonte
"60 segundos em um minuto ... E todo mundo sabe disso. Portanto, isso pode permanecer um número." - Não é um argumento válido para mim. E se eu tiver 200 lugares no meu código onde "60" é usado? Como encontro os locais em que é usado como segundos a minuto versus outros usos possivelmente "naturais" dele? - Na prática, no entanto, também ouso usar minutes*60, às vezes até hours*3600quando preciso, sem declarar constantes adicionais. Por dias, eu provavelmente escreveria d*24*3600ou d*24*60*60porque 86400está perto do limite, onde alguém não reconhecerá esse número mágico de relance.
JimmyB
@ JimmyB Se você estiver usando "60" em 200 lugares no seu código, é muito provável que a solução esteja refatorando uma função em vez de introduzir uma constante.
klutt
0

Eu já vi uma quantidade razoável de código, como no OP (modificado) nas recuperações de banco de dados. A consulta retorna uma lista, mas as regras de negócios dizem que pode haver apenas um elemento. E então, é claro, algo mudou para 'apenas este caso' para uma lista com mais de um item. (Sim, eu disse várias vezes. É quase como se eles ... nm)

Então, em vez de criar uma constante, eu (no método de código limpo) criaria um método para dar um nome ou esclarecer o que o condicional se destina a detectar (e encapsular como ele o detecta):

public int getMoneyByPersons(){
  if(isSingleDepartmentHead()){ 
    // TODO - return money for one person
  } else {
    // TODO - calculate and return money for people.
  }
}

public boolean isSingleDepartmentHead() {
   return persons.size() == 1;
}
Kristian H
fonte
Muito preferível unificar o manuseio. Uma lista de n elementos pode ser tratada de maneira uniforme, independentemente de n.
Deduplicator
Vi que não é, onde o caso único era, de fato, um valor (marginal) diferente do que teria sido se o mesmo usuário fizesse parte de uma lista de tamanho n. Em um OO bem feito, isso pode não ser um problema, mas, ao lidar com sistemas legados, uma boa implementação de OO nem sempre é viável. O melhor que obtivemos foi uma fachada OO razoável na lógica do DAO.
21918 Kristian H