Todos os artigos
18 min de leitura

Boas Práticas de Programação: O Contrato Implícito do Código Profissional

Código é comunicação. Antes de ser instrução para a máquina, é contrato entre engenheiros — presentes e futuros. Este artigo disseca os princípios que separam código funcional de código excepcional.

Existe uma diferença fundamental entre código que funciona e código que é bom. O primeiro resolve um problema imediato. O segundo resolve o problema imediato e continua resolvendo à medida que o sistema evolui, a equipe muda e os requisitos se transformam. Após mais de vinte anos construindo sistemas de missão crítica, aprendi que a qualidade do código não é ornamento — é infraestrutura.

O Princípio da Menor Surpresa

Todo sistema de software é lido muito mais vezes do que é escrito. A proporção real, medida em ambientes de produção com bases de código maduras, é frequentemente de dez para um ou superior. Isso significa que o custo cognitivo de leitura domina o custo total de desenvolvimento. Boas práticas, em sua essência, são técnicas para reduzir esse custo cognitivo.

O princípio da menor surpresa diz que um componente deve se comportar exatamente como um leitor experiente esperaria, sem que esse leitor precise consultar documentação, testes ou implementações internas. Quando você nomeia um método calculateTax, ele deve calcular imposto — não calcular imposto e persistir no banco de dados e emitir um evento de auditoria. Cada responsabilidade adicional é uma surpresa silenciosa que corrói a confiança no código.

Nomeação como Arquitetura

A escolha de nomes não é cosmética. Em linguagens orientadas a objeto, o namespace de uma classe é seu contrato público. Um nome preciso elimina a necessidade de comentários: UserAuthenticationService diz o que faz, AuthService gera ambiguidade, Manager não diz absolutamente nada.

A regra que aplico sistematicamente: se você não consegue nomear uma classe ou método sem usar "and" ou "or", é um sinal de que ela viola o Single Responsibility Principle. ProcessAndValidatePayment deve ser decomposta. saveOrUpdateUser esconde dois fluxos de controle distintos que merecem representações explícitas.

Imutabilidade como Padrão

O estado mutável é a principal fonte de bugs em sistemas concorrentes e distribuídos. A estratégia mais eficaz que conheço é inverter o padrão: tornar imutabilidade o default e mutabilidade a exceção explícita que requer justificativa.

Em Java moderno, isso significa preferir record para transferência de dados, final em campos de instância, coleções imutáveis via List.of(), Map.of(), Collections.unmodifiableList(). Em Kotlin, o design da linguagem já reflete esse princípio: val vs var, data class com copy() para transformações.

Design by Contract e Fail Fast

Precondições explícitas eliminam classes inteiras de bugs. O padrão que uso invariavelmente:

public PaymentResult processPayment(PaymentRequest request) {
    Objects.requireNonNull(request, "PaymentRequest cannot be null");
    Preconditions.checkArgument(request.amount().compareTo(BigDecimal.ZERO) > 0,
        "Payment amount must be positive, got: %s", request.amount());
    Preconditions.checkState(this.gateway.isAvailable(),
        "Payment gateway is not available");

return executePaymentPipeline(request); }

Cada verificação de pré-condição serve dois propósitos: protege o invariante do método e documenta as expectativas do chamador. Quando essas expectativas são violadas, o sistema falha imediatamente com uma mensagem precisa em vez de propagar silenciosamente um estado inválido até um ponto de falha distante e difícil de diagnosticar.

Tratamento de Erros como Primeira Classe

O fluxo de erro é tão importante quanto o fluxo feliz. Um padrão que observo frequentemente em código problemático é o tratamento de exceções como afterthought — blocos catch que apenas loggam e re-lançam, ou que engolam exceções silenciosamente.

Em Java com Spring, o modelo que prefiro:

@ExceptionHandler(PaymentProcessingException.class)
public ResponseEntity<ErrorResponse> handlePaymentFailure(
    PaymentProcessingException ex, HttpServletRequest request) {

log.error("Payment processing failed: requestId={}, reason={}", request.getHeader("X-Request-Id"), ex.getMessage(), ex);

return ResponseEntity .status(HttpStatus.UNPROCESSABLE_ENTITY) .body(ErrorResponse.of(ex.getErrorCode(), ex.getMessage(), request.getRequestURI())); }

Complexidade Ciclomática e Decomposição

Complexidade ciclomática é uma métrica que conta o número de caminhos linearmente independentes através de um método. Minha regra empírica: acima de 10, o método deve ser decomposto. Acima de 15, é quase certo que existe lógica de negócio não articulada embutida em estruturas de controle que deveriam ser polimorfismo ou tabelas de decisão.

Testes como Especificação Executável

O teste unitário correto não verifica implementação — verifica comportamento observável. O padrão Given-When-Then estrutura testes como especificações legíveis:

@Test
void shouldRejectPaymentWhenCardIsExpired() {
    var expiredCard = CreditCard.builder()
        .number("4111111111111111")
        .expirationDate(YearMonth.of(2020, 1))
        .build();

var request = PaymentRequest.of(expiredCard, Money.of(99.99, "BRL")); var result = paymentService.processPayment(request);

assertThat(result.isFailure()).isTrue(); assertThat(result.getError().code()).isEqualTo(ErrorCode.EXPIRED_CARD); }

Dívida Técnica como Decisão Deliberada

Dívida técnica não é inherentemente ruim. É ruim quando é acidental, não documentada e acumulada sem intenção consciente. A solução é tornar a dívida visível: use TODO com rastreamento, FIXME com tickets, HACK com explicação do porquê e condições para remoção.

Um código excelente não é código sem dívida. É código cuja dívida é conhecida, comunicada e gerenciada como qualquer outro ativo do sistema.