Mudanças simples x Soluções simples

Mudanças simples x Soluções simples
maniche
maniche

Compartilhe

O processo de entrega de uma funcionalidade nova envolve implementar ou corrigir algo que o nosso sistema atualmente não comporta. Dados os problemas que o programa resolve nesse instante, existem diversas maneiras distintas de resolvê-los, todas válidas, algumas mais limpas e mais simples do que outras.

O gráfico a seguir mostra uma situação onde nosso programa não resolve o problema atual que desejamos atacar, onde:

a) vermelho: representa variações do sistema que não resolveriam meu problema; b) amarelo: minha situação atual; c) verde: soluções possíveis.

Banner da Escola de Inovação e Gestão: Matricula-se na escola de Inovação e Gestão. Junte-se a uma comunidade de mais de 500 mil estudantes. Na Alura você tem acesso a todos os cursos em uma única assinatura; tem novos lançamentos a cada semana; desafios práticos. Clique e saiba mais!
![Soluções possiveis para um problema](assets/mudancas-simples-x-solucoes-simples/1-300x251.png)

Mas dentro das soluções possíveis, existem aquelas mudanças que são simples de serem executadas, por exemplo incluir um "if" ou simplesmente fazer um copy e paste de código, um pequeno passo, uma mudança simples, um baby step. Mudanças simples que resolvem o problema, representadas em verde escuro aqui:

![Mudanças simples](assets/mudancas-simples-x-solucoes-simples/2.png)

Por fim, existem as soluções mais simples para o problema. A solução mais simples pode ser extrair uma classe ou utilizar uma biblioteca que executa a lógica para nós, por exemplo. No diagrama a seguir as soluções mais simples são representadas em azul:

![Soluções simples](assets/mudancas-simples-x-solucoes-simples/3-300x252.png)

A execução desses passos, baby steps, traz o código para dentro das mudanças simples que resolvem o problema. Mas a sequência dos mesmos pode nos levar para diversos lugares diferentes: alguns mais próximos das soluções mais simples, outros cada vez mais distantes das mesmas, nos deixando com um débito técnico cada vez maior.

Pior ainda, a adoção cega de baby steps ou de mudanças simples sem nenhuma prática para garantir a qualidade pode levar a uma situação onde seja impossível chegar em uma solução simples: seu sistema vira uma grande bola de lama.

Essa situação é o inferno de qualquer sistema: passos simples não são mais capazes de manter as coisas simples.

![Uso puro de baby steps](assets/mudancas-simples-x-solucoes-simples/Diagramalindo-389x1024.png)

Em algumas situações, o baby step pode implicar em uma das melhores soluções, mas para isso é necessário o domínio da tecnologia, do negócio, design e linguagem, algo que nem sempre é a realidade do desenvolvedor quando escreve uma funcionalidade:

![Baby step e design](assets/mudancas-simples-x-solucoes-simples/Diagramalindo-3.png)

Como é difícil executar um passo-a-passo muito suave e chegar em uma solução simples qualquer, adicionamos mais uma técnica ao processo: além de executar as tarefas em pedaços pequenos (mas não mínimos), refatoramos nosso código, para simplificá-lo.

Cada passo de simplificação através da refatoração coloca nosso código mais próximo do sistema ideal: o mais simples de todos que resolve nossos problemas.

![Refatorando](assets/mudancas-simples-x-solucoes-simples/Diagramalindo-2-1024x853.png)

Com a refatoração contínua, cada passo que damos ao implementar uma nova feature nos mantém ainda próximos das soluções simples.

Mais importante do que fazer passos minúsculos (baby steps) é fazer passos que fazem sentido para levá-lo em direção a melhor solução (e não somente a mudança mais simples). Para isso não podemos nos esquecer de refatorar. E não só refatorações de baixo nível (como renomear variáveis, mover métodos, etc), mas sim refatorações de alto nível (como abstrair comportamentos).

Imagine o seguinte trecho de código, responsável por calcular o salário de um funcionário. Dependendo do cargo do funcionário, o algoritmo para o cálculo é diferente.

 public class CalculoDeSalario {

public CalculoDeSalario(RegrasFiscais regrasFiscais, ...) { // ... }

public double calcula(Funcionario f) { if(f.getCargo() == Cargo.VENDEDOR) { // calcula salario do vendedor usando regras fiscais, etc }

if(f.getCargo() == Cargo.GERENTE) { // calcula salario do gerente usando regras fiscais, etc } } } 

Agora suponha que um novo cargo apareceu na empresa e precisamos calcular o salário das pessoas que farão o trabalho desse novo cargo. O código mais simples que faria isso acontecer, seria:

 public class CalculoDeSalario {

public CalculoDeSalario(RegrasFiscais regrasFiscais, ...) { // ... }

public double calcula(Funcionario f) { if(f.getCargo() == Cargo.VENDEDOR) { // calcula salario do vendedor usando regras fiscais, etc }

if(f.getCargo() == Cargo.GERENTE) { // calcula salario do gerente usando regras fiscais, etc }

if(f.getCargo() == Cargo.MARKETING) { // calcula salario do marketing usando regras fiscais, etc } } } 

Mas essa não é a melhor solução que resolveria o problema. A cada novo if inserido nesse código, mais longe estamos da melhor solução. O design fica cada vez mais rígido. E pior: refatorar esse trecho de código fica cada vez mais difícil: até mesmo o segundo if que foi adicionado ao código já havia o afastado da melhor solução!

Ao implementar uma nova funcionalidade, o programador deve observar o design atual da sua aplicação. Se o design atual já provê uma maneira fácil de implementar a funcionalidade, então vá em frente. Caso contrário, o programador deve refatorar o design para permitir que você possa adicionar essa funcionalidade da maneira mais simples possível.

No exemplo acima, uma solução seria criar uma estratégia (do padrão de projeto Strategy) para calcular o salário de acordo com o cargo. Cada cargo seria responsável por devolver o seu algoritmo de cálculo. Por exemplo:

 public class CalculoDeSalario {

public CalculoDeSalario(RegrasFiscais regrasFiscais, ...) { // ... }

public double calcula(Funcionario f) { AlgoritmoDeCalculoDeSalario calculadora = f.getCargo().getAlgoritmoDeCalculo(regrasFiscais); return calculadora.calcula(); } } 

Se essa é a melhor solução, é difícil dizer. Mas com certeza ela é uma solução melhor do que as anteriores. Mas, veja que após essa refatoração, implementar o cálculo de salário para o novo cargo é muito mais fácil. Agora posso voltar aos meus baby steps, já que a implementação corrente está em um lugar seguro.

Toda essa experiência se resume a uma frase que surgiu em uma conversa entre Guilherme Silveira e Maurício Aniche:

A mudança mais simples para resolver um problema não é necessariamente a solução mais simples para resolvê-lo.

Veja outros artigos sobre Inovação & Gestão