Funções
Funções (e métodos, no contexto de orientação a objetos) são a unidade mais frequente de leitura, teste e manutenção em um sistema. Para uma pessoa que está começando no ambiente Java, “escrever uma função” costuma significar “fazer o código funcionar”. Neste tópico, o objetivo é dar o próximo passo: escrever funções que continuam fáceis de entender e mudar daqui a semanas (ou semestres).
Uma boa regra mental é imaginar que seu código será lido por alguém que não estava com você quando você o escreveu (frequentemente: você mesmo no futuro).
Funções e métodos (e por que a diferença importa)
Seção intitulada “Funções e métodos (e por que a diferença importa)”- Função: pode existir “solta” em algumas linguagens.
- Método: é uma função associada a um objeto/classe e recebe implicitamente um
this.
Em Java, na prática você escreverá métodos, mas os princípios aqui valem para ambos.
Argumentos vs parâmetros
Seção intitulada “Argumentos vs parâmetros”- Parâmetros: variáveis declaradas na assinatura do método.
- Argumentos: valores passados na chamada.
public void saudacao(String nome) { // "nome" é parâmetro System.out.println("Olá, " + nome);}
saudacao("Fulano"); // "Fulano" é argumentoAnalogia: parâmetro é o “molde”; argumento é o “objeto” que você coloca dentro do molde.
Responsabilidade Única (SRP) aplicada a funções
Seção intitulada “Responsabilidade Única (SRP) aplicada a funções”O SRP (Single Responsibility Principle) diz que uma unidade de código deve ter um motivo para mudar. Em função/método, isso vira: um método deve fazer uma coisa só.
Sinais de que um método faz “mais de uma coisa”:
- valida dados, calcula algo e também persiste no banco;
- faz lógica de regra de negócio e ao mesmo tempo formata texto para UI;
- mistura cálculos com IO (disco, rede, console).
Exemplo (ruim → melhor)
Seção intitulada “Exemplo (ruim → melhor)”Ruim (muitas etapas e motivos de mudança no mesmo método):
public void processCustomerData(Customer customer) { if (customer.getName() == null || customer.getName().isEmpty()) { System.out.println("Error: Customer name is missing."); return; } if (customer.getEmail() == null || customer.getEmail().isEmpty()) { System.out.println("Error: Customer email is missing."); return; }
boolean returning = checkIfReturningCustomer(customer); if (returning) { System.out.println("Returning customer found: " + customer.getName()); } else { System.out.println("New customer: " + customer.getName()); }
String address = customer.getAddress(); if (address == null || address.isEmpty()) { System.out.println("Error: Customer address is missing."); return; } System.out.println("Customer address validated: " + address);}Melhor (cada método com uma intenção clara):
public void processCustomerData(Customer customer) { validateCustomerData(customer); checkCustomerStatus(customer); processCustomerAddress(customer);}Isso melhora:
- legibilidade (você “lê a história” do método);
- testabilidade (cada parte isolada);
- manutenção (mudança em validação não quebra status/endereço).
Ordem de leitura: o código como narrativa
Seção intitulada “Ordem de leitura: o código como narrativa”Quando alguém abre a classe, ela deve conseguir ler de cima para baixo como um texto:
- entradas principais (métodos públicos)
- detalhes (métodos privados auxiliares)
Analogia: comece pelo “sumário” e só depois vá aos “capítulos”.
Exemplo preferível:
public class GerenciadorDePedidos { public void processarPedido(String pedido) { if (validarPedido(pedido)) { double valor = calcularValorTotal(pedido); gerarNotaFiscal(pedido, valor); enviarConfirmacao(pedido); } else { System.out.println("Pedido inválido."); } }
private boolean validarPedido(String pedido) { /* ... */ return true; } private double calcularValorTotal(String pedido) { /* ... */ return 0.0; } private void gerarNotaFiscal(String pedido, double valor) { /* ... */ } private void enviarConfirmacao(String pedido) { /* ... */ }}Switches e decisões: quando evitar e o que fazer no lugar
Seção intitulada “Switches e decisões: quando evitar e o que fazer no lugar”switch (ou cadeias de if/else) costumam piorar quando:
- novas opções aparecem com frequência;
- o comportamento muda por “tipo” (ex.:
EmployeeType); - o código replica o mesmo
switchem vários lugares.
Nesses casos, prefira polimorfismo (OO): cada tipo sabe como se comportar.
Em vez de:
public Money calculatePay(Employee e) { switch (e.type) { case COMMISSIONED: return commissionedPay(e); case HOURLY: return hourlyPay(e); case SALARIED: return salariedPay(e); default: throw new InvalidEmployeeType(e.type); }}Prefira:
public abstract class Employee { public abstract Money calculatePay();}E cada subclasse implementa calculatePay().
Benefícios:
- reduz condicionais;
- facilita extensão (novo tipo = nova classe);
- favorece testes unitários por tipo.
Nomes significativos
Seção intitulada “Nomes significativos”Nome de método é um “contrato”. Boas práticas:
- use verbos:
calcularTotal,enviarEmail,removerUsuario; - evite
doStuff,handle,processquando escondem intenção; - se um nome fica enorme, pode ser sinal de que o método faz demais.
Analogia: nome de método é como etiqueta de pasta. Se não diz o conteúdo, você abre (lê) à força.
Parâmetros: quantidade e forma
Seção intitulada “Parâmetros: quantidade e forma”O livro Clean Code recomenda preferir:
- 0 parâmetros (quando possível),
- depois 1,
- depois 2,
- e evitar 3+.
Muitos parâmetros aumentam:
- combinações de uso;
- chance de inversão/erro;
- custo de leitura.
Agrupe parâmetros com um objeto
Seção intitulada “Agrupe parâmetros com um objeto”Ruim:
public void sendEmail(String to, String from, String subject, String body, boolean isImportant) { }Melhor:
public void sendEmail(Email email) { }Isso também ajuda validação (ex.: Email.validate()).
Evite parâmetro booleano
Seção intitulada “Evite parâmetro booleano”Um booleano frequentemente significa “dois comportamentos”.
Ruim:
public void render(boolean isSilent) { /* ... */ }Melhor:
public void render() { /* ... */ }public void renderInSilentMode() { /* ... */ }Isso torna quem chama responsável pela decisão e deixa o método mais previsível.
Efeitos colaterais (side effects)
Seção intitulada “Efeitos colaterais (side effects)”Efeito colateral é quando o método:
- altera estado fora do que parece (variáveis globais/singletons),
- escreve em arquivo/banco/rede,
- muda algo “por baixo dos panos” além do retorno.
Exemplo problemático:
public void updatePhysics() { // ...atualiza posição... RenderServer.update(this); // efeito colateral escondido}Melhor separar intenções:
public void updatePhysics() { /* ... */ }public void updateRender() { /* ... */ }Regra prática: se o método é “de cálculo”, tente mantê-lo puro (sem IO e sem mexer em estado externo).
Parâmetros de saída: evite
Seção intitulada “Parâmetros de saída: evite”Em Java, é comum ver “retornos via array/lista passada por parâmetro”. Isso torna o método menos intuitivo:
public void calculate(int a, int b, int[] result) { result[0] = a + b; result[1] = a - b; result[2] = a * b;}Alternativas melhores:
- retornar um objeto (ex.:
CalculationResult { sum, diff, product }) - retornar uma lista imutável
- ou separar em métodos
Separação comando–consulta (Command–Query Separation)
Seção intitulada “Separação comando–consulta (Command–Query Separation)”Um método deve ser:
- Comando: muda estado (retorno geralmente
void); - Consulta: retorna dado (não muda estado).
Evite misturar:
public Boolean setUsername(String username) { this.username = username; return this.username; // mistura comando e consulta}Prefira:
public void setUsername(String username) { this.username = username;}
public String getUsername() { return this.username;}Isso reduz confusão e torna o código mais previsível.
Erros vs exceções
Seção intitulada “Erros vs exceções”Retornar “códigos de erro” cria ambiguidade e exige checagens em todo lugar.
Evite:
public int divide(int a, int b) { if (b == 0) return -1; return a / b;}Prefira exceção (falha explícita):
public int divide(int a, int b) { if (b == 0) throw new IllegalArgumentException("Divisor cannot be zero"); return a / b;}Quando usar exceção?
- situações excepcionais/invalidas;
- violações de pré-condição;
- falhas de IO.
Quando não usar?
- fluxo normal (ex.: “não encontrou resultado” pode ser
Optionalem alguns casos).
Try/catch: mantenha limpo (extraia tratamento)
Seção intitulada “Try/catch: mantenha limpo (extraia tratamento)”Blocos try/catch “achatam” a leitura. Uma técnica é separar:
- método que faz (e propaga exceção),
- método que decide como tratar.
Exemplo:
public String readFirstLine(Path path) throws IOException { try (BufferedReader br = Files.newBufferedReader(path)) { return br.readLine(); }}
public String getFileContentOrMessage(Path path) { try { return readFirstLine(path); } catch (NoSuchFileException e) { return "Arquivo não encontrado"; } catch (IOException e) { return "Erro ao ler o arquivo"; }}Notas:
try-with-resourcesevita vazamento de recursos.- o método “core” é curto e claro.
Evite repetição (DRY) com bom senso
Seção intitulada “Evite repetição (DRY) com bom senso”Duplicação é cara porque:
- você corrige um bug em um lugar e esquece do outro;
- diferenças pequenas viram divergências grandes.
Estratégias:
- extrair método comum;
- criar uma função de mapeamento/validação reutilizável;
- usar estruturas de dados e polimorfismo ao invés de copiar condicionais.
Cuidado: abstração cedo demais também cria complexidade. Extraia quando o padrão estiver claro.
Checklist rápido para revisar um método
Seção intitulada “Checklist rápido para revisar um método”- O nome diz exatamente o que ele faz?
- Dá para explicar a função sem usar “e” no meio? (“faz X e Y” é suspeito)
- Quantos parâmetros tem? Dá para agrupar?
- Tem efeito colateral escondido?
- O fluxo de leitura é top-down?
- Erros são tratados de forma consistente (exceção,
Optional, validação)?
Leituras e referências
Seção intitulada “Leituras e referências”- Robert C. Martin, Clean Code (capítulo sobre Functions)
- Documentação Java: exceções,
try-with-resources - Princípios SOLID (SRP)