Atualmente identificar Code Smell em nossos projetos é uma tarefa que pode ser designada para ferramentas de analise de código que auxiliam com sugestões de melhorias e fazem um mapeamento do projeto como um todo. Obviamente elas foram criadas por outros desenvolvedores com esse propósito. Mas você consegue enxergar o que é um código que cheira sem depender de tais ferramentas?
Como prometido no artigo anterior, este será focado apenas com exemplos. Vamos aplicar melhorias em nosso código. Vamos usar o “superset” do JavaScript, o querido TypeScript, como código em nossos exemplos. Mas alguns conceitos podem ser aplicados em outras linguagens, como C#, Java, JavaScript, Swift e muitas outras. Obviamente cada linguagem tem suas particularidades, mas o importante é entender o conceito e saber identificar Code Smell em outras também. Então vamos começar!
Uso incorreto do Any
Note o código abaixo e tente entender. Qual o problema desse código?
let x = 10;
let y: number = 11;
let z: any = 12;
Bom para quem está iniciando pode ser difícil identificar, afinal estamos declarando variáveis simples. Mas se você trabalha a algum tempo com o TypeScript ou está estudando já deve ter entendido.
O compilador do TypeScript infere tipos a variáveis quando nós não fazemos isso, ou seja, a primeira variável X está de forma implícita nos dizendo que o seu tipo é number. A segunda variável Y, está de forma explicita nos dizendo de que tipo ela é. Mas let z está nos dizendo que ela pode ser de qualquer tipo (any), sendo que nós sabemos que ela é de um tipo especifico, pois ela recebe um número (que no caso é 12), não concorda?
Mas você deve estar se perguntando: Ok eu entendi, mas isso vai funcionar se eu compilar?
Sim, pode funcionar, mas percebe que não faz sentido esse tipo de declaração? A questão é programar com qualidade!
O código correto ficaria dessa forma:
let x = 10;
let y: number = 11;
let z: number = 12;
Funções idênticas
Novamente analise o código abaixo e veja o que pode estar errado:
function calcularPreco() {
fazerAlgo();
fazerOutraCoisa();
return preco;
}
function buscarPreco() {
fazerAlgo();
fazerOutraCoisa();
return preco;
}
Fica claro que as funções fazem a mesma coisa, estamos repetindo linhas de maneira desnecessária! O que pode ter levado o programador a escrever dessa maneira? Muitos cenários são possíveis. Mas em vez de repetir código, podemos refatorar esse código e deixar ele melhor simplesmente fazendo isso:
function calcularPreco() {
fazerAlgo();
fazerOutraCoisa();
return preco;
}
function buscarPreco() {
return calcularPreco();
}
Bem melhor do que ficar repetindo linhas de maneira desnecessária, não acha? Isso na verdade se encaixa em um principio que muitos desenvolvedores usam, DRY (Don't Repeat Yourself - Não se repita).
Evite o uso de operadores ternários
Eu gosto de como os operadores ternários encurtam uma lógica. Mas temos que ser honestos, existem situações que é difícil de entender a lógica da condição. Já presenciei um desenvolvedor com 15 anos de experiência ter grande dificuldade de ler um operador ternário em uma função. Isso aconteceu porque o primeiro programador que trabalhou nessa parte do projeto, não tomou cuidado ao escolher nomes que faziam sentido e deixou a lógica extensa demais. Veja um exemplo:
function foo(a) {
var b = (a === 'A') ? 'is A' : 'is not A';
// ...
}
O código acima pode exigir alguns minutos de atenção, mas acredite que está bem simples perto de aberrações que eu já presenciei! Quando estava iniciando na área de desenvolvimento, era difícil entender o que acontecia em códigos parecidos. Depois de tempo de prática e estudo com certeza fica mais fácil entender, mas nunca subestime outros desenvolvedores que não seguem boas práticas! Mas esse é um exemplo bem simples. No dia a dia do trabalho a história pode ser outra 😂. Agora veja esse mesmo código aplicado com um simples if/else :
function foo(a) {
var b;
if (a === 'A') {
b = 'is A';
}
else {
b = 'is not A';
}
// ...
}
Fica mais claro o que está acontecendo na verificação! Mas veja esse operador ternário abaixo e tente entender o que está acontecendo, vou deixar como um alerta do que nunca devemos fazer em nosso dia a dia:
int a = b > 10 ? c < 20 ? 50 : 80 : e == 2 ? 4 : 8;
Use Template strings
Isso não é um problema gigantesco, mas é bom destacar. É um recurso que foi implementado para ser usado, principalmente a partir do ECMAScript 2015. Você pode estar acostumado, ou não, a usar esse tipo de concatenação:
function digaOla(nome) {
console.log("ola" + nome);
}
Mas pode começar a usar um maneira bem mais fácil e inteligente. Utilizando do Template String:
function digaOla(nome) {
console.log(`ola ${nome}`);
}
Bem simples e fácil de aplicar!
Evite usar tipos literais booleano
É fortemente recomendado evitar comparar em uma expressão condicional a variável com true ou false :
let algumValor = "0";
// ...
if (algumValor == true) { /* ... */ }
if (algumValorBoleano != true) { /* ... */ }
fazAlgo(!false);
Você analisa o primeiro código e fica confuso exatamente porque estamos comparando o valor da entrada com true, sendo que isso é desnecessário. A estrutura condicional if/else é um recurso que indica quais instruções o sistema deve seguir de acordo com uma expressão booleana, seria um caso diferente se a expressão estivesse comparando com uma string ou um number.
Veja como fica o código refatorado:
if (algumValor && algumValor != "0") { /* ... */ }
if (!algumValorBoleano) { /* ... */ }
fazAlgo(true);
É importante destacar que os nomes que você escolhe para as variáveis são essenciais e ajudam muito! O próximo desenvolvedor que for realizar uma manutenção ou implementar novas funcionalidades vai conseguir entender rapidamente do que se trata e o que exatamente o código faz. Veja outro exemplo bem simples:
if (validarVoucher == true)
//Refactoring
if (validarVoucher)
{
//...
}
A questão é deixar o código o mais claro possível e sem brechas para Bugs!
Operadores Ternários não precisam ser aninhados
Vamos de novo abordar esse tema. Mas prometo que será breve. E vou deixar a própria documentação do Sonarqube explicar:
"Só porque você pode fazer algo, não significa que você deva, esse é o caso com operações ternárias aninhadas. Aninhar operadores ternários resulta no tipo de código que pode parecer claro como o dia quando você o escreve, mas seis meses depois deixará outros desenvolvedores que irão realizar uma manutenção no código (ou pior - você no futuro) coçando a cabeça e xingando. Em vez disso, escolha pelo lado da clareza e use outra linha para expressar a operação aninhada como uma instrução separada."
Bom nem temos muito o que comentar. Evite expressões ternárias que retornam dessa maneira:
function getReadableStatus(job) {
return job.isRunning() ? "Running" : job.hasErrors() ? "Failed" : "Succeeded ";
}
// Exemplo 2
return a < b ? -1 : a > b ? 1 : 0;
Veja que é uma expressão chata de ler. Você tem que tirar alguns minutos, parar e analisar com calma para entender o que o desenvolvedor queria verificar aqui. Mas agora veja esse mesmo código refatorado:
function getReadableStatus(job) {
if (job.isRunning()) {
return "Running";
}
return job.hasErrors() ? "Failed" : "Succeeded";
}
//Exemplo 2
if(a < b) {
return -1;
} else if (a > b) {
return 1;
}
return 0;
Ainda temos um expressão ternária mas bem curta e fica fácil entender o que ela está fazendo! Com uma simples refatoração deixamos o código bem mais legível.
Funções devem ter poucos parâmetros
Funções (métodos) necessitam de parâmetros em alguns casos. Mas existem alguns cuidados que devemos tomar. Nunca devemos sobrecarregar uma função com mais do que realmente é necessário! Veja um exemplo:
function fazAlgo(param1, param2, param3, param4, param5) {
// ...
}
A função do exemplo acima tem muitas responsabilidades. Pode ser que infelizmente o desenvolvedor não tenha tido tempo ou simplesmente não se importou com o número de parâmetros passados. Mas sempre tente reduzir isso em seu código:
function fazAlgo(param1, param2, param3) {
// ...
}
A função tendo apenas três parâmetros vai ter menos responsabilidades! E seu código vai ficar mais fácil de compreender!
Agora veja este outro exemplo:
postLetter(string country, string town, string postcode, string streetAddress, number appartmentNumber, string careOf)
Veja que são propriedades que fazem parte de um endereço, então poderíamos criar uma classe Address. É bem melhor passar o objeto como parâmetro nesses casos:
public address: Address;
postLetter(address)
Não use números mágicos
O que seria um número mágico? Um número que não temos certeza do que ele faz o número está lá mas não vemos um significado para ele. Apenas sabemos que se ele está no código ele é importante 😂.
Pense na seguinte situação: Você acabou de receber um projeto e analisa uma função como essa do exemplo abaixo:
potentialEnergy(mass: number, height: number): number {
return mass * height * 9.81;
}
Agora surge uma pergunta. O que significa o número 9.81? Bom se você já se deparou em uma situação assim deve ter ficado frustrado pois teve que realizar a depuração do código e entender o contexto para que ficasse mais claro a importância do número “mágico” naquela função. Veja o código depois da refatoração:
static const GRAVITATIONAL_CONSTANT = 9.81;
potentialEnergy(mass: number, height: number): number {
return mass * height * GRAVITATIONAL_CONSTANT;
}
Agora sim sabemos do que se trata claramente o número!
Veja mais um exemplo:
seconds = num_days * 24 * 60 * 60
Se você tem uma experiência de anos na área de desenvolvimento de software já sabe do que se trata. Mas imagine um Desenvolvedor Júnior que acabou de entrar no projeto, pode ser confuso para ele. Abaixo está uma maneira melhor de escrever esse código:
seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE
São exemplos simples, mas que podemos aplicar nas situações que vamos encontrando nos códigos que estamos trabalhando.
Conclusão
Vimos exemplos do que podemos fazer para limpar nosso código e evitar os Code Smell. Mas existem muitos outros exemplos para você estudar, praticar e evitar em seu código. Basta pesquisar mais afundo e verá sugestões e princípios que já são colocados em prática e que dão resultados! E pode ter certeza que isso não é uma tarefa fácil! É necessário dedicação para fazer tudo isso. Mas é bom reforçar. Um software que segue as boas práticas tem muito mais chances de continuar em produção do que um que não segue!
Fico por aqui nesse post! Qualquer dúvida ou crítica construtiva fico à disposição para contato.
Referências: