Object Calisthenics em Java:

12/1/2025Michael

9 Regras para Melhorar (ou Complicar) Seu Código Orientado a Objetos

Object Calisthenics é um conjunto de 9 regras proposto por Jeff Bay no livro The ThoughtWorks Anthology. Essas regras visam reforçar boas práticas de design orientado a objetos (OO) por meio de restrições intencionais, incentivando códigos mais coesos, expressivos e manuteníveis. Elas funcionam como um "exercício" para desenvolvedores, forçando-os a repensar hábitos comuns e adotar abordagens mais disciplinadas.

No entanto, no mundo real do desenvolvimento Java... com suas bibliotecas robustas, frameworks como Spring e padrões como Java EE ou Jakarta EE, nem todas as regras se aplicam de forma universal. Algumas podem aprimorar o código, enquanto outras podem introduzir complexidade desnecessária, especialmente em cenários de alta performance, streams ou programação funcional integrada ao Java (via lambdas e Stream API desde o Java 8).

Este artigo estende a análise original, adaptando-a ao ecossistema Java. Apresento uma visão crítica de cada regra, com exemplos em código Java, opiniões baseadas em experiências profissionais e ponderações técnicas. Incluo contrapontos sobre como essas regras interagem com features modernas do Java, como records (Java 14+), sealed classes (Java 17+) e o ecossistema de testes com JUnit.

Nota: Esta é uma visão pessoal, fundamentada em princípios de OO, Domain-Driven Design (DDD) e práticas modernas em Java. Use essas regras como ferramenta de aprendizado, não como dogmas absolutos.

1. Apenas um Nível de Indentação por Método

Descrição: Limite a indentação a um único nível por método. Isso incentiva a decomposição de lógicas complexas em métodos menores, promovendo o princípio de responsabilidade única (SRP do SOLID) e facilitando a leitura e os testes.

Exemplo em Java: Em vez de um método com loops aninhados:

public void processOrders(List<Order> orders) {
    for (Order order : orders) {
        if (order.isValid()) {
            for (Item item : order.getItems()) {
                // Processamento aninhado
            }
        }
    }
}

Divida em métodos auxiliares para manter um nível de indentação.

Opinião:
Tenho ressalvas significativas. A motivação é boa: reduzir complexidade e melhorar linearidade, mas em Java, com streams e lambdas, fluxos funcionais como orders.stream().filter(Order::isValid).forEach(this::processItems) naturalmente evitam indentação profunda sem criar micrométodos. Aplicar rigidamente pode levar a classes poluídas com métodos não reutilizáveis, complicando a navegação no código.

Ponderação Técnica:
Considere isso uma diretriz, não uma regra rígida. Em cenários de lazy evaluation (ex.: Stream API) ou padrões como Optional/Result, indentação mínima ocorre organicamente. Use ferramentas como SonarQube para medir complexidade ciclomática em vez de contar indentação. No Java, priorize legibilidade sobre restrições artificiais, especialmente em microsserviços onde performance importa.

2. Não Use o ELSE

Descrição: Evite blocos else usando retornos antecipados (guard clauses), polimorfismo ou estruturas como mapas. Isso lineariza o fluxo e reduz ramificações.

Exemplo em Java: Em vez de:

if (condition) {
    // Código A
} else {
    // Código B
}

Use:

if (!condition) return; // Ou throw exception
// Código A

Opinião:
Concordo integralmente. Guard clauses simplificam o código, tornando-o mais fluido e fácil de testar. Em Java, isso alinha perfeitamente com exceções checked/unchecked e o padrão de "fail fast".

Ponderação Técnica:
Em casos binários claros (ex.: sucesso/falha em um serviço REST), um else pode melhorar a expressividade. No entanto, com features como pattern matching (Java 21+), o else se torna obsoleto em switches. Evite-o para reduzir complexidade, mas não sacrifique clareza em lógicas mutuamente exclusivas.

3. Envolva Seus Tipos Primitivos e Strings

Descrição: Encapsule primitivos (int, String etc.) em classes dedicadas para adicionar validações e semântica de domínio.

Exemplo em Java: Em vez de String cpf, use:

public record Cpf(String value) {
    public Cpf {
        if (!isValidCpf(value)) throw new IllegalArgumentException("CPF inválido");
    }
    private boolean isValidCpf(String cpf) { /* Validação */ }
}

Opinião:
Concordo plenamente. Isso promove Value Objects no DDD, centralizando regras e evitando "primitive obsession".

Em Java, records facilitam isso desde o Java 14, tornando o encapsulamento leve e imutável.

Ponderação Técnica:
Evite overengineering para valores triviais (ex.: um simples int para idade). Use quando há validação ou comportamento associado. Integre com bibliotecas como Jakarta Validation para anotações como @NotNull.

4. Envolva Suas Collections em Classes

Descrição: Crie classes wrapper para coleções, encapsulando comportamentos relacionados.

Exemplo em Java: Em vez de expor List<OrderItem>, use:

public class OrderItems {
    private final List<OrderItem> items = new ArrayList<>();

    public void add(OrderItem item) {
        // Validações específicas
        items.add(item);
    }

    public List<OrderItem> asList() {
        return Collections.unmodifiableList(items);
    }
}

Opinião:
Concordo parcialmente. Faz sentido para coleções com regras (ex.: itens de carrinho com limite de quantidade). Mas em Java, interfaces como Collection e métodos como unmodifiableList já oferecem encapsulamento sem wrappers extras.

Ponderação Técnica:
Use em agregados DDD (ex.: Order com OrderItems). Evite se for apenas um pass-through, para não inflar o código. Com generics e streams, coleções nuas são frequentemente suficientes.

5. No Máximo Duas Variáveis de Instância

Descrição: Limite classes a duas variáveis de instância para promover decomposição e coesão.

Exemplo em Java:
Uma classe Person com nome, idade, endereço e email violaria isso; fragmente em classes compostas.

Opinião:
Discordo veementemente. Essa restrição é arbitrária e ignora modelos de domínio ricos. Em Java, entidades JPA ou records podem precisar de mais atributos sem perder coesão.

Ponderação Técnica:
Foquem em coesão semântica, não em contagem. Métricas como LCOM (Lack of Cohesion in Methods) são melhores. Em apps enterprise, classes com múltiplos campos são normais e gerenciáveis com Lombok ou records.

6. Apenas um Ponto por Linha

Descrição: Siga a Lei de Demeter: evite encadeamentos como order.getCustomer().getAddress().getStreet().

Exemplo em Java:
Divida em métodos delegados em vez de chains longos.

Opinião:
Discordo em parte. Em Java, chains em streams (list.stream().filter(...).map(...)) são idiomáticos e fluídos. Aplicar rigidamente conflita com programação funcional.

Ponderação Técnica:
A lei é sobre acoplamento, não sintaxe. Use chains moderados; evite violações profundas. Ferramentas como Optional ajudam a gerenciar nulls em chains.

7. Não Abrevie

Descrição: Use nomes descritivos, evitando abreviações.

Exemplo em Java:
Em vez de calcTot(), use calculateTotalAmount().

Opinião:
Concordo totalmente. Com IDEs como IntelliJ, nomes longos não são problema. Abreviações confundem, especialmente em equipes internacionais.

Ponderação Técnica:
Exceções para convenções de domínio (ex.: HTTP em APIs). Priorize clareza; nomes são o primeiro nível de documentação.

8. Mantenha Todas as Entidades Pequenas

Descrição: Classes < 50 linhas; pacotes < 10 arquivos.

Opinião:
Discordo da rigidez. Limites artificiais levam a fragmentação excessiva, complicando builds em Maven/Gradle.

Ponderação Técnica:
Entidades pequenas por coesão, não por contagem. Use métricas como complexidade ciclomática. Em monorepos, organização modular é chave.

9. Não Use Getters ou Setters (com ressalvas)

Descrição: Evite expor estado; prefira "Tell, don't ask".

Exemplo em Java:
Use métodos comportamentais em vez de getters/setters.

Opinião:
Discordo da eliminação total de getters em Java, onde são idiomáticos (ex.: beans em Spring). Setters devem ser raros, favorecendo imutabilidade.

Ponderação Técnica:
Evite modelos anêmicos; use records para imutabilidade. Em frameworks, getters são necessários, mas encapsule lógica.

Conclusão

Object Calisthenics é uma ferramenta pedagógica valiosa para Java devs, incentivando reflexão sobre OO.

No entanto, com evoluções como Java 21 (virtual threads, pattern matching), adapte-as ao contexto: DDD, Spring Boot ou microsserviços.

O equilíbrio entre regras e pragmatismo é essencial, priorize código limpo, testável e alinhado ao negócio.

Referências:

  • Bay, Jeff. Object Calisthenics. In: The ThoughtWorks Anthology. ThoughtWorks Inc., 2008.
  • Evans, Eric. Domain-Driven Design: Tackling Complexity in the Heart of Software. Addison-Wesley, 2003.
  • Oracle Java Documentation: Records e Stream API.