Arquitetura de SoftwareProdutividadeProgramação

10 estratégias para atacar a complexidade no desenvolvimento

A complexidade é um dos maiores desafios no desenvolvimento de software. Muitas vezes, ela surge de forma inesperada, seja por decisões mal planejadas, requisitos pouco claros, falhas de comunicação ou até mesmo pelo excesso de otimização (overengineering).

No outro artigo aqui do site “Simplicidade é tudo que você sempre ‘KISS’ e não sabia!”, exploramos a importância da simplicidade e o princípio KISS (Keep It Simple, Stupid). Neste, vamos focar em práticas concretas para evitar que a complexidade tome conta dos seus projetos.

Estratégias práticas para lidar com a complexidade

O desafio em lidar com partes complexas de soluções é constante e muitas vezes não será possível reduzi-la ou evitá-la da forma que nós queremos ou no tempo que nós desejamos, mas isso não significa que não possamos estabelecer estratégias e (re)pensar práticas e processos para melhorar os projetos em que trabalhos e claro facilitar nossa vida.

Relembrando e reforçando a mensagem do outro post, menos complexidade normalmente representa mais eficiência e menos custos em geral (tempo, dinheiro, energia…) e com isso, claro, menor dor de cabeça.

1. Isole a complexidade

Na prática, isso significa isolar partes complexas do seu código e da sua solução. Se precisar lidar com partes mais complicadas do sistema, encapsule essa complexidade em métodos, funções ou classes, quebrando-a em partes menores e administráveis. Se necessário, também (re)pense as suas camadas de projeto, sempre respeitando os padrões do projeto em questão, o estilo arquitetural e demais estruturas pré-existentes. Isso facilita muito a manutenção e evita que o projeto se torne um labirinto impossível de entender.

2. Faça PoCs antes de adotar uma solução

Sempre que possível, antes de implementar algo novo, invista tempo em Provas de Conceito (PoCs). Testar ferramentas, configurações e abordagens antes de integrá-las ao projeto pode evitar grandes dores de cabeça no futuro. Essa prática ajuda a identificar potenciais problemas e validar a viabilidade da solução.

3. Tenha um ambiente sandbox

Em complemento ao item anterior, manter um ambiente isolado para seus testes e laboratórios (sandbox) permite experimentação sem comprometer o sistema principal. Isso é útil para validar mudanças sem riscos desnecessários e pode ajudar a reduzir a complexidade introduzida por alterações não testadas adequadamente.

Se você é dev, você pode estar pensando que estou falando dos ambientes de development, staging/homolog e production. Esses, certamente já são (ou deveriam ser) pré-requisitos de qualquer projeto e empresa, mas o que quero dizer aqui é que, você como desenvolvedor, pode ir além, e ter o seu próprio ambiente e projeto para experimentar suas ideias e hipóteses, seja na sua próprio computador ou servidor separado. (Dica bônus: o Docker pode ajudar muito aqui! 🙂 )

Isso pode até parecer óbvio para alguns, mas já vi muita gente não se atentar a isso e fazer experimentos em ambientes que outras pessoas estão trabalhando e até mesmo em ambientes produtivos.

4. Reflita sobre a implementação e não esqueça do Code Review

Depois de terminar uma funcionalidade, se pergunte: “Isso poderia ter sido feito de uma forma mais simples e eficiente?” Se a resposta for sim, talvez valha a pena refatorar.

Revisar o código após implementado (code review, pair review, etc.) já é uma prática comum em muitas empresas, mas vale lembrar aqui de não deixar esse processo cair no esquecimento. Além disso, é sempre bom lembrar de fazer o review também sob a perspectiva da complexidade, fazendo perguntas como:

  • O código criado/alterado está seguindo boas práticas e está respeitando a estrutura do projeto existente?
  • A complexidade inserida realmente é necessária?
  • Foram adicionados novos frameworks ou bibliotecas? São realmente necessários?
  • Forma adicionados testes para funcionalidades criadas? ou os testes foram adaptados as alterações?

e pode soar como óbvio demais, mas o ponto que quero destacar aqui, é que revisitar as soluções depois de um tempo de maturação pode trazer resultados interessantes e surpreendentes. É comum vermos casos em que os times estão tão focados em entregar os projetos, que a solução não teve tempo de colaboração e maturação suficiente.

5. Use testes automatizados a seu favor

A dificuldade em testar um código pode ser um indicativo de que ele está excessivamente complexo. Implementar testes automatizados não apenas melhora a qualidade, mas também força uma estruturação mais organizada do código.

Além disso, testes de unidade claros e bem definidos certamente podem contribuir muito com o gerenciamento de regras e fluxos dos projeto, melhorando também a visão das partes mais complexas e garantindo maior cobertura de código e das regras de negócio.

6. Prefira soluções pragmáticas a teóricas

Muitas vezes, ficamos presos ao que parece ser a “solução perfeita”, mas a melhor solução é aquela que resolve o problema de forma eficiente dentro do contexto real do projeto.

A teoria traz muitas ideias e exemplos interessantes, que podem ser usados como referência, mas seja cauteloso ao tentar aplicar/replicar aprendizados de exemplos em projetos práticos, principalmente em aplicações que já estão em uso. Seja ponderado e analítico nas modificações e refatorações. Implemente somente aquilo de fato vai contribuir para a solução. Embora muitos problemas sejam parecidos, toda aplicação tem suas particularidades e seu contexto (empresa, segmento, ferramentas, versões, cultura, conhecimento do time, etc.)

7. Seja moderado e evite o overengineering

Em complemento ao tópico anterior, vale reforçar que nem toda solução precisa ser “altamente rebuscada”, “superescalável” ou altamente sofisticada logo de início.

Um exemplo comum de Overengineering é o uso de Padrões de projeto (Design Patterns) sem qualquer ponderação. Eles são ferramentas úteis, mas forçá-los onde não são necessários pode aumentar a complexidade sem necessidade. Sempre questione se um pattern realmente faz sentido antes de aplicá-lo.

Outro exemplo comum é a criação de serviços distribuídos (ou microsserviços) complexos com recursos como filas, cache, etc. para resolver um problemas raros ou “pequenos”, que por exemplo uma aplicação mais simples também poderia resolver, sem adicionar tanta complexidade.

Pense que toda solução tem um custo inerente, a questão é saber se este custo vale o benefício que irá trazer? Ou irá trazer problemas maiores?

Reflita e pergunte-se se o problema realmente exige isso?

Normalmente, a solução mais simples é a melhor escolha para o momento. Foque no que é necessário agora, sem tentar prever todas as possibilidades futuras. Adapte conforme o projeto evolui.

Busque o equilíbrio entre robustez e simplicidade.

8. Use ferramentas e abordagens de apoio

De modo geral, qualquer ferramenta ou abordagem que nos ajude a clarear os pensamentos e compreender melhor os processos e projetos certamente irão ajudar muito, embora muitas vezes isso passe despercebido.

Ações simples e “tradicionais” como a descrição comum de atividades (escrita em texto mesmo) ou a diagramação de processos (fluxogramas, mapas mentais, UML, MER, BPM, etc.) continuam sendo muito importantes para levantar e mapear dados e fluxos. Não é porque já conhecemos bem um fluxo que isso significa que não precisamos revisá-lo ou melhorá-lo. Acredite, você vai se surpreender com quanta coisa importante a gente esquece ou deixa passar ao recorrer apenas a nosso conhecimento/memória.

Quanto a ferramentas relacionadas a projetos de software e código, opções como o Sonar Lint, Sonar Qube e outras deste tipo certamente vão ajudar muito no gerenciamento da qualidade, cobertura do código e análise de complexidade de algoritmos dos projetos.

Ferramentas de IA generativa, como ChatGPT, Github Copilot, Claude e outros, vem revolucionando o mundo em praticamente todas as áreas. No desenvolvimento de software não é diferente. Em relação a mitigar a complexidade, elas podem contribuir muito para gerar código padronizado, revisar códigos e processos específicos, gerar testes, fornecer explicações, etc. Nesse quesito só vale sempre reforçar que não dá pra confiar 100% em códigos ou textos gerados ou revisados. Após usá-las sempre faça revisão e adapte a necessidade. (Questione sempre!)

9. Busque a colaboração das pessoas

Nenhum software é desenvolvido isoladamente. Muitas vezes, a complexidade surge porque tentamos resolver problemas sozinhos, sem considerar a visão de outras pessoas. Ao buscar colaboração dentro do time, você pode evitar retrabalho, reduzir ambiguidades e encontrar soluções mais simples e eficazes.

Dicas para promover a colaboração:

  • Peça feedback cedo e frequentemente. Compartilhar suas ideias antes de implementá-las pode evitar que você siga por um caminho desnecessariamente complicado.
  • Aproveite as revisões de código. Um olhar externo pode identificar padrões que você talvez não tenha percebido.
  • Faça perguntas. Não tenha medo de perguntar se há uma abordagem mais simples para o problema que está resolvendo.
  • Evite decisões isoladas. Discutir alternativas com colegas pode ajudar a encontrar a melhor solução sem introduzir complexidade desnecessária.

O desenvolvimento de software é um esforço coletivo, e quanto mais aprendemos a colaborar, mais conseguimos simplificar as coisas.

10. Revise e simplifique regularmente

Totalmente alinhado ao que foi descrito no item 4, o ponto que quero destacar aqui, é que revisitar as soluções depois de um tempo de maturação pode trazer resultados interessantes e surpreendentes. É comum vermos casos em que os times estão tão focados em entregar os projetos, que a solução não teve tempo de colaboração e maturação suficiente.

Código escrito há alguns meses pode ser otimizado. Criar o hábito de revisar partes do sistema periodicamente permite identificar oportunidades de melhoria e simplificação. Isso também se aplica às configurações do projeto, garantindo que elas continuem eficientes e adequadas às necessidades do time.

Além das revisões de código e projeto em si, todas as práticas citadas anteriormente são dignas de revisões periódicas. Afinal a melhoria tem que ser contínua.

Simples Assim? Nem Sempre, mas vale a pena!

Manter a simplicidade não significa ignorar a complexidade, mas sim administrá-la da melhor forma. No fim das contas, simplificar exige reflexão, prática e, às vezes, abrir mão de certas abordagens desnecessariamente complicadas.

Se quiser explorar mais sobre o poder da simplicidade e o princípio KISS, confira também o outro artigo aqui do site sobre o assunto:

“Simplicidade é tudo que você sempre ‘KISS’ e não sabia!”

Agora me conta: Quais estratégias você usa para evitar a complexidade nos seus projetos? Qual você acrescentaria? Deixe seu comentário e vamos trocar ideias!

🚀 Seguimos juntos, pelo código e além!

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *