Clean Code
No universo da programação, frequentemente nos deparamos com o termo: Clean Code ou Código Limpo.
Mas o que exatamente é um “código limpo”? Quais características são necessárias para obtê-lo?
Escrever um código limpo significa escrever códigos de um jeito que conseguimos entendê-lo sem complicação.
Isso não apenas simplifica a manipulação do código, mas também facilita a colaboração entre o time. No fim das contas, todo desenvolvimento e manutenção do sistema também se torna mais fácil.
De acordo com “Uncle Bob”, em seu livro “Código Limpo: Habilidades Práticas do Software Ágil”, existem algumas boas práticas fundamentais para alcançar a clareza do código.
Vamos conhecê-las, a seguir:
Utilizar os princípios SOLID:
O Clean Code e os princípios SOLID compartilham o objetivo de melhorar a qualidade do software, tornando-o legível, organizado, extensível e fácil de manter.
Neste episódio do #HipstersPontoTube sobre Clean Code e Solid, o host Paulo Silveira bate um papo com o Alberto Sousa para discutir se é necessário seguir essas práticas à risca para desenvolver um bom projeto.
Possuir nomes significativos
Nomes descritivos ajudam a entender a finalidade de uma parte do código sem a necessidade de comentários explicativos.
Para ilustrar, considere o código a seguir:
public static double conv(double tC) {
double tF = (tC * 9 / 5) + 32;
return tF;
}
Temos que nos esforçar para entender o que o código acima faz. Podemos melhorar o entendimento apenas adicionando nomes significativos para as variáveis e para o método:
public static double converterCelsiusParaFahrenheit(double temperaturaCelsius) {
double temperaturaFahrenheit = (temperaturaCelsius * 9 / 5) + 32;
return temperaturaFahrenheit;
}
Agora, fica claro qual é o propósito do código, sem a necessidade de se lembrar de fórmulas ou realizar pesquisas adicionais. Isso economiza tempo e evita confusões desnecessárias.
Priorizar o uso de funções pequenas
Escrever métodos ou funções pequenas e focadas em uma única tarefa é fundamental para manter o código claro e seguir o princípio da responsabilidade única (SRP).
Para ilustrar, considere o código a seguir:
public class Main {
public static void main(String[] args) {
int[] numeros = {1, 2, 3, 4, 5};
int soma = 0;
for (int numero : numeros) {
soma += numero;
}
double media = (double) soma / numeros.length;
if (media > 3) {
System.out.println("A média é maior que 3");
} else {
System.out.println("A média é menor ou igual a 3");
}
}
}
Apesar do uso de nomes descritivos, a legibilidade poderia ser melhorada dividindo as tarefas em funções distintas, cada uma com sua descrição. Por exemplo:
public class Main {
public static void main(String[] args) {
int[] numeros = {1, 2, 3, 4, 5};
int soma = calcularSoma(numeros);
double media = calcularMedia(numeros);
verificarEMostrarResultado(media);
}
public static int calcularSoma(int[] numeros) {
int soma = 0;
for (int numero : numeros) {
soma += numero;
}
return soma;
}
public static double calcularMedia(int[] numeros) {
return (double) calcularSoma(numeros) / numeros.length;
}
public static void verificarEMostrarResultado(double media) {
if (media > 3) {
System.out.println("A média é maior que 3");
} else {
System.out.println("A média é menor ou igual a 3");
}
}
}
Embora o código tenha ficado maior, ganhamos em legibilidade e segmentação. Qualquer pessoa que precise alterar a maneira como a média é exibida à pessoa usuária, só precisa modificar o método verificarEMostrarResultado
.
Isso demonstra como funções pequenas podem facilitar a manutenção e a compreensão do código.
Evitar comentários desnecessários
O código deve ser autoexplicativo, com nomes significativos e estrutura lógica clara. Comentários excessivos podem tornar o código poluído e difícil de manter.
Para ilustrar, considere o código a seguir:
public class R {
private double w;
private double h;
// Método para calcular a área
public double calc() {
return w * h;
}
}
Os nomes curtos para as variáveis dificultam o entendimento, fazendo necessário o uso de comentários no nosso código, deixando o nosso código sujo. Então, podemos resolver isso adicionando nomes descritivos e removendo os comentários:
public class Retangulo {
private double largura;
private double altura;
public Retangulo(double largura, double altura) {
this.largura = largura;
this.altura = altura;
}
public double calcularArea() {
return largura * altura;
}
}
Pronto, perceba como facilitou o entendimento. Qualquer pessoa desenvolvedora que ler este código consegue assimilar o que cada parte faz.
Evitar complexidade
A complexidade desnecessária pode aumentar a chance de erros e tornar o código difícil de manter. Um exemplo de código complexo para fazer algo simples, como somar dois números, seria:
public void soma() {
Scanner scanner = new Scanner(System.in);
System.out.print("Digite o primeiro número: ");
String num1String = scanner.nextLine();
System.out.print("Digite o segundo número: ");
String num2String = scanner.nextLine();
boolean validInput = false;
double num1 = 0;
double num2 = 0;
while (!validInput) {
num1 = Double.parseDouble(num1String);
num2 = Double.parseDouble(num2String);
validInput = true;
}
double soma = num1 + num2;
System.out.println("A soma dos números é: " + soma);
scanner.close();
}
Repare que é feita uma verificação da entrada, para só depois convertê-la em double.
Poderíamos simplesmente considerar que é esperado que o usuário digite um double e fazer o tratamento de exceções relacionado a isso:
import java.util.InputMismatchException;
import java.util.Scanner;
public class SimpleSumWithErrorHandling {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
try {
System.out.print("Digite o primeiro número: ");
double num1 = scanner.nextDouble();
System.out.print("Digite o segundo número: ");
double num2 = scanner.nextDouble();
double soma = num1 + num2;
System.out.println("A soma dos números é: " + soma);
} catch (InputMismatchException e) {
System.out.println("Erro: Por favor, digite números válidos.");
} finally {
scanner.close();
}
}
}
Executamos a lógica desejada de forma rápida e fácil de compreender. Imagine que precisamos somar agora 3 variáveis, ao invés de duas.
É mais fácil modificar o segundo código do que o primeiro. O segundo código é um exemplo de código limpo.
Fazer o mínimo de argumentos
Funções e métodos devem ter o mínimo possível de argumentos. Isso melhora a legibilidade e a facilidade de uso.
No exemplo a seguir, note como cadastrar uma pessoa colaboradora é complexo ao passar muitos parâmetros:
public static void cadastrarFuncionario(String nome, int idade, String cargo, double salario, String endereco, String cidade, String cep, String telefone, String email) {
// Lógica de cadastro do funcionário aqui...
}
Ao chamar esse método, é difícil entender qual parâmetro utilizar em que lugar, podendo confundi-los, por exemplo.
Uma boa alternativa seria criar uma classe para representar o funcionário, outra para o endereço, e mais uma para o contato. Assim, faremos a divisão:
public static void cadastrarFuncionario(Funcionario funcionario, Endereco endereco, Contato contato){
}
Dessa forma, conseguimos agrupar as informações para que seja possível usar menos argumentos. Essa é uma boa prática
Evitar código com repetição
A repetição torna o código difícil de manter, pois quando há mudanças necessárias elas precisam ser aplicadas em múltiplos lugares.
Então, extraia código repetido em funções ou métodos para promover a reutilização e a manutenção eficiente.
Um exemplo disso:
public static void main(String[] args) {
int numero1 = 5;
int numero2 = 7;
// Cálculo do fatorial para o primeiro número
int resultado1 = 1;
for (int i = 1; i <= numero1; i++) {
resultado1 *= i;
}
System.out.println("Fatorial de " + numero1 + ": " + resultado1);
// Cálculo do fatorial para o segundo número
int resultado2 = 1;
for (int i = 1; i <= numero2; i++) {
resultado2 *= i;
}
System.out.println("Fatorial de " + numero2 + ": " + resultado2);
}
Como calculamos o fatorial mais de uma vez, extraímos o código para uma função, evitando repetições de cálculo no código:
public static void main(String[] args) {
int numero1 = 5;
int numero2 = 7;
// Cálculo e impressão do fatorial para o primeiro número
calcularEImprimirFatorial(numero1);
// Cálculo e impressão do fatorial para o segundo número
calcularEImprimirFatorial(numero2);
}
// Função para calcular e imprimir o fatorial de um número
public static void calcularEImprimirFatorial(int numero) {
int resultado = 1;
for (int i = 1; i <= numero; i++) {
resultado *= i;
}
System.out.println("Fatorial de " + numero + ": " + resultado);
}
Dessa forma, se precisarmos calcular novamente o fatorial de outro número, não precisaremos repetir código. Basta chamar a função de calcular fatorial novamente. Isso facilita o desenvolvimento.
Vantagens de deixar o código limpo
Ao implementar cada um desses princípios e práticas, você não apenas irá melhorar a qualidade do seu código, mas também facilitará a compreensão, a manutenção e a colaboração no desenvolvimento de software. Tudo isso que discutimos aqui resume uma frase dita por Martin Fowler:
“Qualquer tolo pode escrever código que um computador pode entender. Bons programadores escrevem código que humanos podem entender”.
A programação não se trata apenas de fazer a máquina funcionar, mas também de criar soluções que sejam compreensíveis e colaborativas.