1 de junho de 2010

IF bom é IF morto!

O título deste post se refere a uma boa prática em programação Orientada a Objetos. Nossa equipe de desenvolvimento já adotou este lema, e sempre que chega um colega novato, fazemos questão de apresentá-lo a esta lei: "É proibido utilizar IFs negociais", ou, melhor ainda: "IF BOM É IF MORTO".

Ao ouvir esta lei, a primeira pergunta óbvia que escutamos é: - "Se não posso utilizar IF, então vou utilizar o quê? Switch-Case?"

A resposta é: Não!

Na verdade, não devemos utilizar nem IFs, nem Switch-Case, nem qualquer outra estrutura condicional em nosso código!

Aqui, vamos chamar toda e qualquer estrutura de decisão - seja IF, Switch-Case, ou qualquer outra - apenas como IF pra facilitar.

E por quê não podemos utilizar IF em nosso código?

Pelo seguinte motivo: queremos nos beneficiar das inúmeras vantagens da Orientação a Objetos, e o IF nos remete à velha e ultrapassada programação estruturada. Aliás, o IF é a própria estruturação do código que desejamos evitar:

SE isso 
       FAÇA aquilo
SENÃO
       FAÇA aquilo outro

SWITCH(numero)
       CASO 1: faça aquilo
       CASO 2: faça aquilo outro
       etc etc
Ou seja, é a própria essência da estruturação, ou pelo menos, a característica mais engessada da programação estruturada! Ela demanda várias manutenções no código, sempre que um novo Caso for identificado. Ora, mas dentre outras coisas, não queremos utilizar a OO para minimizar o impacto das alterações?

Então, tome nota desta Lei: IF BOM É IF MORTO.

O questionamento que escutamos em seguida pelos programadores novatos é: - "Mas se não posso usar nem IF, nem Switch-Case, nem nada parecido, COMO é que vou tratar isso?"

É o que veremos a seguir. A regra do nosso exemplo diz que: o programa deverá receber do usuário uma informação "PF" ou "PJ", indicando se vai tratar de Pessoa Física ou Pessoa Jurídica.

SE for pessoa física, o sistema imprime na tela "Pessoa Física", consulta e grava dados num banco Oracle, envia um e-mail automático para a Empresa X, gera um LOG numa pasta específica, e imprime um Relatório de PF.

SE for pessoa jurídica, o sistema imprime na tela "Pessoa Jurídica", consulta e grava dados num banco DB2, envia 3 e-mails automáticos para a Empresa X, Y, Z, gera um LOG em outra pasta específica, e imprime 2 Relatórios de PJ.

Implementando da maneira estruturada, teríamos o seguinte:
public class  Executavel{
  public static void main(String[] args){
      String tipo = args[0];
      if(tipo.equals("PF")){
          System.out.println("Pessoa Física");
          System.out.println("Consultando Oracle...");
      }
      if(tipo.equals("PJ")){
          System.out.println("Pessoa Jurídica");
          System.out.println("Consultando DB2...");
      }
      GeraMail.trataTipo(tipo);
      TrataLog.grava(tipo);
      Relatorio.imprime(tipo);
  }
}
Um exemplo simples, que recebe o parâmetro do usuário na linha de comando, sem muito tratamento, apenas para ilustrar os malefícios da abordagem estruturada com IFs.

O "arquiteto" que projetou a solução acima, baseou toda a solução nos IFs. Note que, na chamada a GeraMail.trata(tipo), ele precisa enviar a informação do usuário (PF ou PJ), e dentro desta classe vamos ter outra estrutura de IFs para tratar o envio de e-mail. O mesmo acontece com TrataLog e Relatorio, replicando a estrutura de IFs por diversas partes do código.

Ao projetar a "brilhante" solução acima, o "arquiteto genial" pensou: - "Bem, os 2 únicos tipos possíveis neste caso para pessoa é Física ou Jurídica. Então, vou fazer com IFs mesmo". Aí então, 4 meses de trabalho depois, com toda a "arquitetura" pronta e faltando 1 semana para o prazo final da entrega do projeto, ele recebe um telefonema do Cliente, que diz: - "Preciso tratar mais um tipo! Se refere à PJS - Pessoa Jurídica optante pelo Simples, que, no meu requisito, vai possuir regras completamente diferentes dos outros tipos".

Pânico! Desespero! Ele precisa alterar o método main() acima para inserir mais um tratamento, e precisa alterar a classe GeraMail para tratar mais um IF, e precisa dar manutenção também na classe TrataLog, e também na Relatorio, e em mais 7.586 classes do sistema... :-) [...geralmente é o que acontece na prática, além de ter que testar tudo novamente...] Ou seja, muitas noites sem dormir, trabalho no sábado e domingo, aumento enorme da probabilidade de falhas no sistema, e muito provavelmente, mais um projeto que vai estourar custo e prazo!

Tudo porque ele negligenciou o nosso lema: IF BOM É IF MORTO.

Como fazer sem usar IFs? Assim:

Vou criar a interface Pessoa:
 public interface Pessoa{
        public void imprime();
 }
Vou criar a classe PessoaFisica, que vai conter todos os tratamentos que são de responsabilidade de PF (gravar no Oracle, enviar e-mail para X, etc), e a classe PessoaJuridica, que vai tratar regras de PJ (grava no DB2, etc, etc). Ambas as classes vão implementar a interface Pessoa.

Apenas para que possamos rodar o nosso exemplo, e ver a solução funcionando na prática, vou fazer com que as classes PF e PJ exibam na tela suas informações, sem tratar acesso a banco nem como gerar logs, etc (não é assunto deste post).

A classe PessoaFisica.java ficaria assim:
public class PessoaFisica implements Pessoa{
   public void imprime(){
      System.out.println("Pessoa Física");
      System.out.println("Consultando Oracle...");
   }
}
A classe PessoaJuridica.java ficaria assim:
public class PessoaJuridica implements Pessoa{
   public void imprime(){
      System.out.println("Pessoa Jurídica");
      System.out.println("Consultando DB2...");
   }
}
Agora, sem os malditos IFs, a mesma classe Executavel.java do início do exemplo:
public class Executavel{
  public static void main(String[] args){
     Map mapaDePessoas = new HashMap();
     mapaDePessoas.put("PF", new PessoaFisica());
     mapaDePessoas.put("PJ", new PessoaJuridica());
     String tipo = args[0];
     Pessoa pessoa = (Pessoa) mapaDePessoas.get(tipo);
     pessoa.imprime();
  }
}
Compile, rode e ateste a eficácia da solução:

/> java Executavel PF
Pessoa Física
Consultando Oracle...

/> java Executavel PJ
Pessoa Jurídica
Consultando DB2...

Pronto! De acordo com a informação dada pelo usuário, executei regras diferentes sem utilizar 1 único IF (nem Switch-Case...)

E se eu precisar de um novo tipo para Pessoa Jurídica optante pelo Simples - PJS? Aí então eu vou criar esta nova classe PJS, com suas regras específicas, e adicionar uma entrada no Mapa:

mapaDePessoas.put("PJS", new PJS());

E só! Nenhum outro ponto em todo o Projeto precisaria ser alterado!

Por fim, vale ressaltar que a desvantagem do IF é esta propagação de manutenções que ele demanda em caso de alteração. Entretanto, para utilizar o mecanismo da inicialização tardia, podemos fazer o trecho abaixo sem problemas:
public Conexao  getConexao(){
   if(this.conexao == null){
        this.conexao = new Conexao();
   }
   return this.conexao;
}
Você pode estar pensando: - "Mas você disse que é proibido utilizar IFs". Não, eu não disse isso!

O que eu disse foi: é proibido usar IFs negociais! Este não é um IF negocial, ele apenas testa o valor nulo. Ou, como diz a nossa Lei: "IF BOM É IF MORTO". Exatamente! Este IF acima está morto! Ou seja, não há a menor possibilidade de ele vir sofrer alterações futuramente. Na inicialização tardia, eu testo se o valor é nulo, e isto nunca vai ser alterado. O Cliente pode mudar todos os seus requisitos que, ainda assim, o método getConexao() acima vai continuar como está!

O exemplo aqui apresentado é uma implementação do GoF Pattern Strategy. As classes PessoaFisica e PessoaJuridica podem ser entendidas como estratégias que encapsulam seus algoritmos. Pretendo abordar o Strategy em outro post, específico sobre este padrão, para que possamos ver outros exemplos e discutir as abordagens do GoF. Até lá!

(*PS: não deixe de enviar seu comentário, dúvida, crítica, sugestões, discordâncias, perguntas, reclamações...)

26 comentários:

  1. Oi marcone, ótimo post! realmente é necessário eliminar os ifs, veja minha resposta no guj. Forte abraço!!

    ResponderExcluir
  2. Simplesmente, fantástico, a sua explicação! excelente didática, inclusive vou utilizar isto nas minhas aulas!

    ResponderExcluir
  3. Muito bom o texto, Marcone. Gostei muito da maneira como abordou o tópico. Meus parabéns!

    ResponderExcluir
  4. Ademir, Reuel, Cleyton e Marcelo: obrigado pelos comentários, fico feliz em saber que estão acompanhando o blog, e o retorno de vocês é a motivação necessária para continuar.

    Em breve vou postar 2 novos tópicos: "A solução para todos os problemas" sobre design patterns, e mais uma "Pegadinha Java" sobre dar new em Interface. Espero que gostem.

    Voltem sempre e conto com seus comentários! Abraço.

    ResponderExcluir
  5. Marcone,

    Parabéns pelo blog e pelo post!
    Que didática! Como ficou fácil entender o assunto, sendo tratado desta forma!
    Obrigado!

    Abraço,

    ResponderExcluir
  6. Já peguei um sistema assim... tinha mais de 100 IFs! JURO! Mudei "geral" com o uso de interfaces.

    ResponderExcluir
    Respostas
    1. Acredito que tenha sido um sitema legado, pode me ensinar a aplicar isso em sitemas legados sem quebrar tudo e não sair do "padrão" ?

      Excluir
  7. .:N:.
    ________
    Rapaz... muito bom o exemplo, parabens... estou a um bom tempo aq na net procurando uns exemplos interessantes do padrão Strategy e finalmente encontrei um sussa... valeu :) Espero por mais posts hehe.
    See ya!
    ________
    .:N:.

    ResponderExcluir
  8. Cara... eu to no 5º período de Sistemas de Informação e tem muita coisa sobre Análise/Projeto OO que eu tenho um pouco de dificuldade de assimilar. 1) Por que eu não estudo o suficiente; 2) Por não achar textos interessantes assim para me animar a ler mais sobre o assunto.

    Programação, então, nem se fala. Só esse ano eu tomei vergonha na cara e comecei a dar uma atenção maior ao assunto.

    Vou acompanhar seu blog, por que aqui eu encontrei o que precisva o/

    Valeu mesmo.
    Abraços

    ResponderExcluir
  9. Nossa muito bom post, fiz um projetinho aqui no trampo com uns 20 ifs, vou alterar todos.
    Obrigado Marcone por compartilhar essa ideia

    ResponderExcluir
  10. Este comentário foi removido pelo autor.

    ResponderExcluir
  11. Amigos, sei que o tópico diz "JAVA" porem não entendo muito da sintax dessa linguagem, teria a possibilidade de mostrar este exemplo utilizando C#?

    Outra coisa e se nesse meio eu tivesse uma classe PessoaTipo com IDPessoaTipo e descricao(fisica ou juridica) como ficaria?(isso se essa situação é viavel claro)

    Vlw

    ResponderExcluir
    Respostas
    1. Olá, amigo, obrigado pela sua participação!

      Em relação à sintaxe, infelizmente eu não vou poder te ajudar, pois não programo em C#. No livro da GoF você encontra exemplos em C++ e Smalltalk, talvez sejam mais pertinentes pra você.

      Em sua segunda dúvida, você apresentou um cenário bastante diferente! Eu criei o cenário apresentado apenas para demonstrar como se aplica o Strategy, mas é claro que este padrão pode e deve ser utilizado em inúmeros cenários diferentes! Neste ponto eu indicaria, mais uma vez, uma leitura ao livro da GoF; é um excelente livro! Lá você poderá encontrar mais exemplos, em uma discussão mais aprofundada.

      Obrigado novamente pela sua participação,
      bons estudos!

      Abraço,
      Marcone.

      Excluir
    2. JHOL, veja se este link pode te ajudar (contém exemplos em C#)

      http://www.dofactory.com/net/strategy-design-pattern


      att,
      Marcone

      Excluir
  12. Teoricamente, entendi sua explicação, mas ainda não consigo aplicá-la na pratica. Gostaria de saber como ficaria o código a seguir sem if:

    String audio = "";
    txtQtdArquivos.setText(Integer.toString(ar.lisArq.getSize()));
    if (ar.lisArq.getSize() >= 2) {
    int item1 = lstArquivos.getSelectedIndex();
    int item2 = item1 + 1;
    audio = ar.Audio(ar.lisArq.get(item1).toString(), ar.lisArq.get(item2).toString());
    } else if (ar.lisArq.getSize() == 1) {
    audio = "DUBLADO";
    } else if (ar.lisArq.getSize() == lstArquivos.getSelectedIndex()+1) {
    audio = "DUBLADO";
    }

    ResponderExcluir
    Respostas
    1. Oi Anderson, obrigado pela sua participação!

      Basicamente, a ideia é você pegar os valores testados nos seus ifs e colocar como chave em um HashMap; com isso, você delega ao HashMap a responsabilidade de te retornar uma nova instância de uma classe; esta classe terá um método que será responsável por fazer as operações dentro do seu if.

      Por exemplo, você tem o seguinte código:

      if (ar.lisArq.getSize() == 1) {
      audio = "DUBLADO";
      }

      Logo, este valor "1" vai ser uma chave no seu mapa, e ele deverá devolver uma nova instância de uma classe (vamos chamá-la de ClasseUm; por sua vez, ClasseUm implementa a interface ClassePai); esta ClasseUm implementa o método da interface, que vai fazer a atribuição audio="dublado", assim:

      Map mapa = new HashMap();
      mapa.put("1", new ClasseUm());


      Desta forma, eu não preciso testar com if qual classe utilizar, o mapa faz isso pra você! Utilizando o seu exemplo, eu faria:

      ClassePai classe = mapa.get(ar.lisArq.getSize());
      classe.executaMetodo();

      Quando o valor de ar.lisArq.getSize() for igual a 1, o mapa vai te retornar uma instância da ClasseUm; e ao chamar o método "executaMetodo", o que estaria implementado nele? O que você precisa quando o valor for igual a 1, ou seja:

      public void executaMetodo(){
      audio = "DUBLADO";
      // ou então
      setAudio("DUBLADO");
      // etc...
      }


      Este exemplo foi apenas pra você captar a ideia; geralmente, se os seus ifs apenas testam valores, e isso não vai ser alterado nem com mudança de requisitos, talvez não seja o caso de aplicar o padrão... Teríamos que ver com calma seus requisitos, o que certamente seria bastante complicado fazê-lo por aqui... Mas, enfim, espero ter passado a ideia de como este padrão funciona. Em caso de dúvidas, é só mandar outra mensagem, estou à disposição.

      Grande abraço,
      Marcone

      Excluir
  13. Sério que vc acha mais eficiente instânciar 2 objetos em memória, dentro de um terceiro objeto que é o HashMap, à colocar um if else? Ou seja, entupimos a memória, acabamos com a máquina e seus recursos, mas o código ficou "bonito e mais fácil de ler"?

    ResponderExcluir
    Respostas
    1. Não se trata de mais bonito, se trata de mais fácil manutenção. Você leu o post inteiro?

      Excluir
    2. Desculpem ressuscitar o post, mas a questão aqui é que em sistemas de grande porte, com muitas regras e funcionalidades que se alteram a todo instante, o padrão aqui apresentado faz todo sentido. Programar um sistema deste tipo com if/else é absolutamente inviável. E não há diferença no consumo de memória, pois estamos instanciando diversos objetos pequenos ao invés de instanciar um único objeto grande.

      Excluir
  14. Muito bom Marcone, me ajudou a entender melhor o padrão!

    ResponderExcluir
  15. Muito boa a maneira que explica o assunto, irá me ajudar muito, irei parti dai para tirar uma redação sobre o assunto para um trabalho.

    Muito Obrigado!!!

    ResponderExcluir
  16. 2023 e esse post nunca morre, parabéns, eu gostaria de pedir sua permissão para reproduzir este artigo na plataforma Medium, dando os devidos créditos.

    ResponderExcluir