Por que revisamos código?

Durval Carvalho
10 min readSep 23, 2023

--

Você já parou para pensar por que revisamos código no nosso dia a dia de trabalho? Às vezes, revisamos o código de um colega que busca uma segunda opinião, ou de forma mais formal, analisamos um Pull Request ou Merge Request que aguarda aprovação. A resposta para essa pergunta pode variar, mas inclui diversos motivos essenciais. Neste artigo, exploraremos as razões por trás da revisão de código e como essa prática contribui para o desenvolvimento de software de alta qualidade. Além disso, discutiremos como a revisão de código pode ser mais eficaz por meio da criação de um guia ou mapa para orientar os revisores em suas análises.

Por que revisamos código?

Revisamos código por diversas razões fundamentais. Primeiramente, a revisão de código é essencial para identificar e corrigir bugs, assegurando que erros sejam resolvidos antes de afetar o software. Além disso, ela busca aprimorar a qualidade do código, otimizando a estrutura, nomenclatura, documentação e desempenho. Também é crucial para garantir que o código atenda precisamente aos requisitos do projeto. Por fim, a revisão de código promove o aprendizado e crescimento profissional, permitindo que desenvolvedores troquem conhecimentos e aprimorem suas habilidades.

No entanto, revisar código também é um processo intrinsecamente subjetivo, onde cada profissional traz sua própria experiência e perspectiva para a análise. A subjetividade pode se manifestar na interpretação do estilo de codificação, no julgamento da eficácia do design de software e na importância atribuída à documentação e aos comentários. Essa diversidade de opiniões pode, por vezes, tornar as revisões um desafio, mas é também uma oportunidade valiosa para o enriquecimento das práticas de desenvolvimento.

Um guia/mapa de revisão de código é essencial para lidar com a subjetividade inerente ao processo. Ele estabelece diretrizes claras e critérios específicos para os revisores, abordando aspectos como estilo de codificação, nomenclatura, documentação e requisitos do projeto, garantindo uma avaliação mais objetiva do código.

Guia/Mapa de revisão de código

Um guia ou mapa de revisão de código não se assemelha a uma receita de bolo detalhada, com procedimentos a seguir de forma milimétrica e sequencial. Em vez disso, consiste em pontos-chave que devem ser considerados durante uma revisão de código, fornecendo diretrizes flexíveis e não prescritivas para orientar o processo. Isso permite uma abordagem mais adaptável e colaborativa à revisão de código, lembrando os aspectos essenciais a serem avaliados, sem impor um roteiro rígido e sem abrir mão da subjetividade e da experiência do revisor.

O que analisar?

  1. Rigidez

A rigidez do código pode ser comparada a uma camisa de força de tipos autoritários, na qual o código funciona apenas para um cenário específico, um tipo determinado e uma situação particular. A rigidez é a característica que torna um software difícil de modificar. Cada alteração provoca uma cascata de mudanças subsequentes em módulos dependentes. Isso significa que o código carece de elasticidade, tornando complicada a adaptação a novos requisitos. Portanto, é importante avaliar a rigidez do código durante a revisão, identificando áreas onde a flexibilidade e a adaptabilidade precisam ser aprimoradas.

2. Fragilidade

A fragilidade do código é um problema que, de certa forma, está interligado com a rigidez, embora possua uma característica distintiva. Assim como a rigidez, a fragilidade força os programadores a realizar modificações no sistema, mesmo quando a tarefa inicialmente não parece ser trabalhosa. No entanto, o que diferencia a fragilidade é que as transformações realizadas no código não são necessariamente exigidas em módulos diretamente relacionados, mas, frequentemente, ocorrem em áreas conceitualmente divergentes do sistema.

Esses problemas muitas vezes são sinais de fragilidade do software, também conhecida como fragilidade do código. Durante a revisão de código, é importante questionar se existem cenários ou situações em que exceções podem ocorrer e não estão sendo adequadamente considerados. Isso envolve avaliar a capacidade do código de lidar com mudanças inesperadas e identificar áreas onde melhorias podem ser feitas para tornar o software menos frágil.

3. Imobilidade

A imobilidade no código ocorre quando é difícil reutilizar partes do software em outros projetos ou mesmo dentro do mesmo projeto. Isso ocorre devido a uma cadeia de dependências associadas à parte desejável do código, o que torna a separação do código desejável do indesejável uma tarefa demorada e trabalhosa. Para evitar esse problema, é essencial construir software de forma que seja fácil extrair e reutilizar componentes internos em novos ambientes, promovendo a mobilidade do código. Isolar regras de negócio em componentes separados é um exemplo desse princípio, permitindo a reutilização eficaz e promovendo qualidades como a manutenibilidade e a testabilidade do software.

Duplicar regras de negócio em diversos sistemas é uma prática a ser evitada. Em vez disso, essas regras devem ser definidas, isoladas e mantidas em um único componente e, em seguida, compartilhadas (reutilizadas) em todos os sistemas que requerem essa regra de negócio, usando, por exemplo, serviços web. Essa abordagem não apenas facilita o desenvolvimento e teste das regras de negócio, mas também proporciona consistência e manutenção em todos os sistemas que as utilizam. Isolar regras de negócio dentro de componentes promove várias qualidades de software, como manutenibilidade, testabilidade, modularidade e segurança. Portanto, promover a mobilidade do código é essencial para construir software de qualidade e evitar práticas inadequadas.

4. Complexidade

A complexidade do software se manifesta em diferentes formas, sendo importante distinguir três tipos básicos: complexidade essencial, complexidade acidental e complexidade incidental.

Complexidade Essencial: Essa forma de complexidade está intrinsecamente relacionada à complexidade do domínio que estamos tentando modelar. Envolve regras de políticas de negócios que governam os processos empresariais. A complexidade essencial não pode ser simplificada, pois está diretamente ligada à natureza das regras de negócios. Por exemplo, se as regras de negócios exigem 15 etapas distintas para processar uma reivindicação, não podemos simplificá-las reduzindo o processo a algumas etapas básicas. A complexidade essencial é inevitável e é a razão fundamental pela qual os engenheiros de software têm empregos.

Complexidade Acidental: Enquanto a complexidade essencial é valiosa e inerente ao domínio, a complexidade acidental é contraproducente e decorre da falta de compreensão, educação e treinamento adequados, bem como de comunicação deficiente entre a área de negócios e a engenharia de software. Ela se manifesta em arquiteturas, designs e códigos ruins, bem como em processos de engenharia de software inadequados. A única maneira de eliminar a complexidade acidental é abordar suas causas por meio de educação, treinamento e coaching.

Complexidade Incidental: Esse tipo de complexidade é particularmente desafiador e prejudicial. Refere-se ao aumento da complexidade resultante da proliferação de ferramentas e tecnologias de infraestrutura, que muitas vezes são necessárias para uma única aplicação. Isso ocorre devido à filosofia Unix de “fazer apenas uma coisa e fazê-la bem”, onde cada ferramenta resolve um problema específico. Embora essas ferramentas evoluam para melhorar suas capacidades, raramente se concentram em eliminar complexidade. Por exemplo, o Git é mais avançado que o Subversion, mas é mais complexo. Lidar com a complexidade incidental é um desafio, pois muitas ferramentas e tecnologias podem ser necessárias para uma única aplicação, e simplificar essa complexidade requer esforço adicional.

Para enfrentar a complexidade no desenvolvimento de software, é fundamental adotar uma abordagem equilibrada. Ao lidar com a complexidade essencial, aceitamos que ela é intrínseca ao domínio e uma parte necessária do nosso trabalho como engenheiros de software. Para a complexidade acidental, investimos em educação, treinamento e melhoria contínua para reduzir a sua ocorrência. Quanto à complexidade incidental, é crucial avaliar cuidadosamente as ferramentas e tecnologias usadas e buscar aquelas que melhor se adequam ao projeto, evitando excessos. Ao adotar essa abordagem consciente, podemos gerenciar e minimizar a complexidade, resultando em sistemas de software mais eficazes e de alta qualidade.

5. Verbosidade

A verbosidade no código é um desafio comum que pode prejudicar a qualidade e a legibilidade do software. Ela se manifesta quando o código contém excesso de detalhes, palavras, linhas desnecessárias ou estruturas complexas que tornam a compreensão do código mais difícil e consomem mais tempo durante a manutenção. Durante a revisão de código, é crucial identificar e abordar a verbosidade, buscando maneiras de simplificar o código, tornando-o mais conciso e eficiente. Isso não apenas melhora a qualidade do software, mas também contribui para uma colaboração mais eficaz dentro da equipe de desenvolvimento.

Para combater a verbosidade, os revisores podem considerar a eliminação de código redundante, a simplificação de estruturas complexas e a adoção de nomes de variáveis e funções mais descritivos. Além disso, o uso adequado de comentários pode ajudar a esclarecer partes complexas do código. Simplificar o código não apenas torna o desenvolvimento mais ágil, mas também reduz a probabilidade de erros e facilita a manutenção a longo prazo. Portanto, abordar a verbosidade é uma prática importante para manter a qualidade do software e promover uma eficiência sustentável no desenvolvimento.

6. Princípio da Responsabilidade Única

O Princípio da Responsabilidade Única sugere que cada parte do seu código (seja uma função, classe, ou módulo) deve ter uma única tarefa bem definida e não deve ser afetada por mudanças não relacionadas. Em outras palavras, evite que o mesmo pedaço de código seja alterado por várias razões diferentes. Isso torna o código mais claro e fácil de manter.

Por exemplo, imagine uma função que calcula o preço de um produto e também atualiza um banco de dados. Se você precisar mudar a forma como o preço é calculado, isso não deveria afetar a parte que atualiza o banco de dados. O Princípio da Responsabilidade Única diz que você deve separar essas duas tarefas em funções diferentes para evitar conflitos.

Quando você segue esse princípio, seu código se torna mais organizado e menos propenso a erros. Ele também facilita a reutilização de código, porque funções ou classes com uma única responsabilidade podem ser usadas em diferentes partes do projeto sem causar problemas inesperados. Portanto, é uma prática importante para escrever código mais eficiente e fácil de manter.

7. Coesão e Acoplamento

Coesão e acoplamento são conceitos essenciais no design de software que afetam a facilidade ou dificuldade de realizar mudanças em um sistema. Vamos abordar brevemente esses conceitos.

Coesão diz respeito a quão bem as partes de um módulo ou componente de código estão relacionadas internamente. Um alto nível de coesão significa que as partes relacionadas estão agrupadas logicamente e realizam tarefas semelhantes. Isso torna o código mais organizado e fácil de entender. Por outro lado, baixa coesão indica que um módulo pode estar realizando tarefas diversas e complexas, tornando-o menos claro e mais difícil de manter.

Acoplamento refere-se à relação entre diferentes partes do código. Quando partes do código estão fortemente acopladas, uma mudança em uma parte pode afetar a outra. Isso pode tornar o código mais difícil de manter e pode levar a efeitos colaterais não desejados. Por outro lado, um baixo acoplamento indica que as partes do código são independentes umas das outras, o que facilita as mudanças sem afetar outras partes do sistema.

8. Princípio do Aberto/Fechado

O Princípio do Aberto/Fechado é um conceito de design de software que enfatiza que as entidades de código (como classes, funções, módulos, etc.) devem permitir a extensão, mas não a modificação. Em termos simples, isso significa que você deve poder adicionar novos comportamentos a uma entidade sem precisar alterar seu código existente.

Aberto para Extensão: Isso significa que, quando surgir um novo requisito ou funcionalidade, você pode estender a entidade existente adicionando novos componentes ou módulos sem mexer no código já escrito. Isso torna o sistema mais flexível e adaptável a mudanças futuras.

Fechado para Modificação: Este aspecto do princípio defende que você não deve alterar o código existente para adicionar novas funcionalidades. Em vez disso, você deve construir sobre o que já existe, evitando riscos de introduzir erros ou afetar o funcionamento atual.

Em resumo, o Princípio do Aberto/Fechado promove um design de código que é facilmente extensível sem modificar o código existente. Isso ajuda a manter a estabilidade do sistema e a facilitar a adição de novos recursos conforme necessário.

10. Princípio da Substituição de Liskov (LSP)

O Princípio da Substituição de Liskov (LSP) destaca que os objetos de uma classe derivada devem ser capazes de substituir os objetos da classe base em qualquer lugar onde a classe base seja usada, sem causar problemas de comportamento. Isso significa que, se uma classe cliente espera um objeto de uma classe base, você deve ser capaz de fornecer um objeto de uma classe derivada daquela classe base, e o programa deve continuar a funcionar corretamente.

Para cumprir o LSP, é fundamental que as classes derivadas implementem os mesmos comportamentos (métodos) que são esperados pelas classes base ou interfaces. Isso garante que os objetos das classes derivadas possam ser usados em vez dos objetos das classes base sem introduzir erros no funcionamento do programa. O LSP é uma diretriz importante para a criação de hierarquias de classes ou implementação de interfaces, pois promove a compatibilidade e a substituição transparente de objetos em diferentes contextos. Em resumo, o LSP visa criar um código mais flexível e extensível, permitindo a incorporação de novos tipos de objetos sem afetar o código existente.

Concluindo

Ao longo deste artigo, exploramos diversos princípios de design de software que desempenham um papel fundamental na criação de sistemas robustos e flexíveis. Desde o princípio da responsabilidade única, que nos lembra de manter o código focado e coeso, até o princípio da substituição de Liskov, que promove a interoperabilidade de objetos, cada princípio tem sua própria contribuição para a construção de código de alta qualidade.

Entender e aplicar esses princípios não apenas torna nosso código mais limpo e compreensível, mas também facilita a manutenção e a expansão futura dos sistemas. À medida que você avança em sua jornada como desenvolvedor de software, lembre-se de considerar esses princípios em seu trabalho diário. Eles não apenas ajudam a resolver problemas imediatos, mas também estabelecem as bases para um desenvolvimento de software mais eficiente e eficaz. Quando aplicados corretamente, esses princípios não são apenas diretrizes abstratas, mas ferramentas poderosas que o ajudarão a se tornar um desenvolvedor mais habilidoso e a criar sistemas de software mais sólidos e adaptáveis.

--

--