Estruturas e Organização do Código
Introdução
Robert Martin cravou seu nome no mundo da programação e sem dúvidas uma de suas contribuições mais importantes foi o livro clean code. Por mais que se trate de conceitos relativamente simples e muitas vezes julgados como ultrapassados, muitos desenvolvedores utilizam os ensinamentos de Bob buscando sempre trazer qualidade para seus códigos.
Código limpo não é só para o computador, mas principalmente para nós programadores. A legibilidade do código é extremamente importante independente do tamanho do projeto.
Seguindo com a série de artigos sobre o livro clean code, onde no primeiro falo sobre os fundamentos do código limpo e abordo os conceitos de nomenclatura, formatação e comentários, neste artigo irei explorar os temas de funções, estruturas de dados, classes, organização e limites do código.
Press enter or click to view image in full size

Funções
Funções são a primeira linha de organização em qualquer programa. Escrever uma boa função é fundamental para a legibilidade do código.
Bob começa o capítulo sobre funções trazendo exemplos de funções um tanto complicadas de entender. Strings estranhas e chamadas a funções esquisitas misturadas com estruturas de decisão controladas por flags. O código é uma verdadeira bagunça e Robert com apenas algumas extrações de métodos e algumas renomeações faz com que o código transpareça o propósito da função em menos linhas e de forma mais clara.
O que é que faz com que a função seja mais fácil de ler? Como ela pode transmitir seu propósito de maneira clara?
Pequenas!
O primeiro ensinamento é que uma função deve ser pequena. Nesse ponto não existe nenhuma comprovação científica que funções menores são melhores, apenas a experiência ao longo dos anos que lhe trouxe esse entendimento.
Por mais que eu tenha menos de uma década como desenvolvedor, já passei por diversos códigos de diferentes épocas e escritos por diferentes pessoas. Devo concordar com Bob… Funções com milhares de linhas e com dezenas de responsabilidades já foram muito frequentes no meu dia a dia e posso garantir que tasks simples demoravam mais do que deviam, isso porque passava 90% do tempo tentando entender o código enquanto os 10% verdadeiramente passava escrevendo.
Blocos de código
Esse é um tópico um tanto delicado dentro da comunidade e que gera bastante debates, Robert instrui que blocos de instruções devem ter apenas uma linha seguinte, possivelmente uma chamada de função que adicione um valor significativo.
Essa abordagem busca reduzir a complexidade cognitiva e evitar o chamado arrow code, quando múltiplos blocos aninhados tornam o fluxo difícil de acompanhar. Ao extrair trechos de código para funções com nomes significativos, ganhamos clareza e testabilidade. O trade-off é a criação de mais métodos, o que pode ser positivo se eles forem curtos e bem nomeados, mas negativo se houver fragmentação excessiva. Por isso, esse princípio deve ser aplicado com discernimento, sempre equilibrando legibilidade e fluidez do código.

Figura 1
Faça apenas uma coisa
As funções devem fazer uma coisa. Devem fazê-la bem. Devem fazer apenas ela.
A parte difícil da declaração é saber o que é “uma coisa”. O motivo de criarmos uma função é para decompor um conceito maior em uma série de passos no próximo nível de abstração. Precisamos então verificar se todas as instruções dentro da função estão no mesmo nível de abstração.
Essa na verdade é uma tarefa um pouco difícil, você precisa entender bem a lógica do que você está desenvolvendo para aninhar a sequência de chamadas de forma que você leia o código de cima para baixo (Regra Decrescente) e ele faça sentido.
Um bom exemplo que vi certa vez é utilizando a criação de um documento HTML a partir de chamadas de métodos.

Figura 2
Podemos dizer que cada uma das funções só faz aquilo que deve fazer pois suas chamadas são aninhadas de forma clara para o nível de abstração em que elas estão. Caso getHead() fosse chamado dentro de getBody() teríamos um problema, pois as chamadas não estariam no mesmo nível de abstração, gerando confusão na leitura.
Parâmetros de funções
De forma geral a quantidade ideal de parâmetros para uma função é zero seguido de um, dois e três, onde se deve evitar ao máximo o uso de três ou mais parâmetros.
Quando uma função parece precisar de mais de dois ou três parâmetros é provável que o conjunto de dados formem uma estrutura de dados que faça sentido para o negócio.
Evite efeitos colaterais
Efeitos colaterais são mentiras. Sua função promete fazer apenas uma coisa, mas faz outras coisas escondidas. São “verdades” enganosas e prejudiciais, que geralmente resultam em acoplamentos temporários. Ou seja, elas não deveriam alterar algo “escondido” no estado do sistema sem o chamador perceber.
Se sua função é para checar se uma senha está correta ela deve fazer apenas isso e não alterar, por exemplo, o estado de “logado” do usuário.
Parâmetro de saída
Parâmetros são comumente interpretado como entradas de uma função. A utilização de parâmetros por referência faz com que o raciocínio de leitura seja interrompido para saber o que a função está fazendo com o parâmetro.
Este é outro assunto que também geram alguns debates na comunidade, mas seguindo o raciocínio de Bob, de modo geral, deve-se evitar parâmetros de saída. Caso sua função precise alterar o estado de algo, mude o estado do objeto que a função pertence.
Separação comando-consulta
As funções devem fazer ou responder algo, mas não os dois. Uma assinatura de método como “public boolean set(String attribute, String value);” pode ser confuso. A ideia muitas vezes é dizer que a operação foi realizada com sucesso, mas com isso estruturas de decisões como “if (set(“username”, “Gasparotto”));” seriam válidas. Por mais que não há erro de sintaxe a leitura acaba ficando confusa, não dá pra saber a princípio o que exatamente a função está fazendo.
Prefira exceções a códigos de erro
Utilizar códigos de erro é uma leve violação da separação comando-consulta pois com isso o código terá validações como “if (deleteUser(user) == OK)”. O problema nesse caso é a necessidade de se ter estruturas onde ao fazer a chamada é necessário imediatamente lidar com o erro. No caso de exceções o tratamento pode ficar separado do resto do código deixando ele mais legível.
Evite repetição
Talvez a regra mais básica entre os programadores. O problema de ter um mesmo código duplicado em diferentes pontos é caso o algoritmo mude, com isso você terá que ter o trabalho de ir em todos os pontos alterar, enquanto que se você abstrair seu código e reutilizar uma mesma função, você estará deixando a manutenção mais fácil para futuras modificações.
Objetos e Estruturas de Dados
Há um motivo para nossas variáveis serem declaradas como “private”. Não devemos deixar que ninguém dependa delas, mas sim das abstrações que manipulam essas variáveis e expõem os comportamentos publicamente. Esse é o pilar do encapsulamento.
Abstração
Considere a diferença entre os dois códigos. Ambas representam interfaces para comunicar o nível do combustível de um carro. Uma expõe os detalhes e a outra esconde.

Ocultar a implementação não é apenas criar funções de acesso para variáveis, o ponto central é abstrair, oferecendo uma interface que te permita manipular a essência dos dados sem que você precise conhecer como eles são armazenados ou calculados.
Antissimetria data/objeto
Na definição de Uncle Bob sobre esse tema ele declara: “objetos e estruturas de dados seguem lógicas opostas”.
- Objetos escondem seus dados e expõem operações significativas sobre eles
- Estruturas de dados expõem os dados e não possuem funções relevantes.
Cada escolha traz uma consequência. Usar objetos facilita adicionar novos comportamentos, mas pode dificultar a criação de novas estruturas. Já as estruturas de dados tornam simples a criação de novos tipos de dados, mas deixam difícil a adição de novos comportamentos.
O perigo dos híbridos
Um erro comum é tentar misturar os dois mundos, criando classes que expõem parte dos dados e, ao mesmo tempo, implementam algumas regras de negócio. Esse “meio-termo” quebra a clareza do design e resulta em código confuso e difícil de manter.
A lei de Demeter
Um módulo não deve enxergar o interior dos objetos que ele manipula
É uma heurística bastante conhecida entre os programadores. Objetos escondem seus dados e expõem as operações. Isso significa que um objeto não deve expor sua estrutura interna por meio dos métodos assessores, pois isso seria expor e não ocultar sua estrutura interna.
- Cada unidade deve ter conhecimento limitado sobre outras unidades: apenas unidades próximas se relacionam.
- Cada unidade deve apenas conversar com seus amigos; Não fale com estranhos.
- Apenas fale com seus amigos imediatos.
Classes
Quando falamos em classes dentro de Clean Code, Robert Martin não está só reforçando conceitos básicos de orientação a objetos. Ele traz uma visão prática sobre como projetar classes que realmente colaborem para a manutenção e evolução saudável do sistema.
Coesão
De acordo com a regra do Single Responsibility Principle (SRP), uma classe deve ter apenas uma responsabilidade (cuidado com a interpretação, trarei um artigo sobre SOLID que comentarei melhor sobre essa regra). Sempre que você perceber que uma classe está crescendo demais, ou que seus métodos tratam de assuntos muito diferentes, é um sinal de que ela está assumindo mais de um papel , quebrando a coesão. Neste cenário é ideal a criação de novas classes, cada uma com suas específicas responsabilidades.
Encapsulamento
Outro ponto essencial é o encapsulamento, classes devem esconder os detalhes internos e expor só o que é necessário. Isso não significa apenas deixar variáveis privadas e criar getters e setters, mas sim pensar em interfaces significativas, que expressem a intenção da classe sem revelar como ela funciona por dentro.
Organização interna da classe
Bob recomenda que a organização da classe siga uma ordem natural:
- Variáveis privadas no topo
- Métodos públicos em seguida, mostrando o que a classe oferece ao mundo
- Métodos privados como suporte
Essa hierarquia ajuda a manter a leitura mais fluida e a evitar aquela caça infinita por atributos e funções dentro de uma classe grande.
Classes pequenas
Assim como funções, classes também devem ser pequenas e focadas, quanto mais compacta e coesa, mais fácil fica de entender, reutilizar e testar.
Uma classe grande demais geralmente indica acúmulo de responsabilidades, o que pode ser um indício da necessidade de se quebrar em classes menores, cada uma cuidando de um pedaço específico da lógica.
Design orientado a objetos
Por fim, vale lembrar que orientação a objetos não é só colocar métodos dentro de classes, o ponto principal é pensar em entidades que colaboram entre si, cada uma cumprindo seu papel dentro de um sistema. O design fica limpo quando as classes interagem de forma clara, sem depender dos detalhes umas das outras.
Organização de Código
Quando pensamos em código limpo, não podemos parar apenas em funções e classes.
Separação de responsabilidades
Um dos maiores erros no design de sistemas é misturar lógica de negócios com outras coisas como detalhes de infraestrutura. Quando a regra de negócio está emaranhada com chamadas de banco e manipulação de UI o sistema se torna complicado de evoluir. A ideia é separar bem, negócio de um lado e infraestrutura do outro.
Camadas e modularidade
Para manter essa separação, entram em cena os padrões de camadas, pacotes e modularização. Organizar o sistema em blocos lógicos (como camada de apresentação, domínio e persistência) como o padrão MVC deixam o código mais escalável e compreensível. Cada módulo faz sua parte e conversa com os outros por meio de interfaces bem definidas, sem “vazar” detalhes internos.
Alta coesão, baixo acoplamento
Um sistema limpo mantém cada parte do sistema focada em um propósito e com dependências mínimas, seguindo a mesma ideia de classes limpas: alta coesão e baixo acoplamento.
- Alta coesão → cada módulo foca em um propósito específico.
- Baixo acoplamento → módulos não dependem demais uns dos outros.
Esse equilíbrio mantém o sistema flexível, fácil de testar e mais fácil de alterar.
Legibilidade
Não basta que o sistema apenas “funcione”, como dito na introdução ele precisa ser entendível pelos desenvolvedores. A organização em camadas, modularidade e clareza do código servem para que qualquer desenvolvedor consiga navegar pelo sistema sem se perder em detalhes.
Design incremental
Algo que todos desenvolvedores aprendem com a experiências e Martin enfatiza é que não adianta tentar prever toda a arquitetura desde o começo. O que realmente funciona é o design incremental, um sistema que vai ganhando forma à medida que aplicamos boas práticas. Esse processo faz com que o design apareça naturalmente, em vez de ser algo imposto de cima para baixo.
Tratando Limites
Todo sistema precisa se integrar com bibliotecas, frameworks, serviços externos, bancos de dados, etc. Essa fronteira entre o código da aplicação e os recursos externos é um tema com bastante discussões dentro da área de design de código.
Uso de bibliotecas e APIs externas
É comum, simples e tentador usar diretamente tudo que uma biblioteca oferece, mas Bob nos traz o ensinamento que isso pode virar uma armadilha. Se depois a biblioteca mudar ou precisarmos usar outra, o impacto sempre será bem alto. Por isso, a recomendação é: sempre encapsular dependências externas em classes ou fachadas próprias (também abordarei esse assunto em artigos futuros sobre SOLID).
Isolamento
Um erro comum é espalhar chamadas diretas a frameworks ou APIs de terceiros por todo o sistema. Isso cria um acoplamento forte e dificulta a manutenção. O ideal é concentrar esse acesso em pontos específicos, deixando o restante do sistema protegido das mudanças externas.
Interfaces próprias
Para garantir um isolamento, criamos interfaces próprias, desse jeito a aplicação passa a depender não dos detalhes da biblioteca, mas da nossa abstração (Inversão de Dependência). Se precisar trocar a dependência, basta ajustar a implementação da interface.
Testabilidade
Um grande benefício de isolar esses detalhes é a facilidade que traz para testes automatizados. Quando dependências externas ficam encapsuladas atrás de interfaces, podemos substituí-las facilmente por mocks em testes unitários. Isso deixa os testes mais rápidos, independentes e confiáveis.
Conclusão
O principal objetivo de Robert Martin, como você pode perceber pelo artigo anterior e este, é que escrever código não deve ser apenas para resolver os problemas técnicos, mas sim conseguir comunicar as ideias e regras de forma clara. Funções boas, classes limpas, estruturas de dados bem pensadas, módulos organizados e limites bem definidos são práticas que tornam o trabalho de programar menos doloroso e mais sustentável.