Desenvolvimento Web, Design

Exceções: “checked ou unchecked – Eis a questão!”

Você é designado para procurar um bug num sistema super importante onde você trabalha. Ao ler o código, você percebe um trecho assim:


try {
    controladorUsuario.salvar(diretor);
 }
 catch(Exception e) {}

E agora? Largo tudo, ligo pra minha mãe dizendo que a amo e me jogo da varanda? Calma, ainda não. Você, como bom programador deve consertar o código primeiro. Pensando em como as exceções são tratadas de forma desordenada nos sistemas legados que eu já trabalhei/já fiz, resolvi pesquisar mais sobre a forma correta de se tratar erros/exceções nos nossos  sistema. Há tempos já vinha pensando sobre o assunto e resolvi escrever no blog agora, como uma forma de difundir essas técnicas. Neste post, eu dou algumas dicas e conceitos sobre exceções que pude coletar em alguns livros de peso na área de desenvolvimento de Software. Aí vão elas:

Checked ou unchecked?

Primeiramente, vamos aprender/rever o conceito de exceções checked (checadas) e unchecked (não checadas) que servirão de base para alguns argumentos mais a frente neste post.

Checked:  São exceções que obrigatoriamente devem ser tratadas de alguma forma. Ou seja, o programador é obrigado a fazer algo com ela – tratar com um bloco try-catch (e suas combinações) ou relançar com throws na assinatura do método. Note que esse tipo de exceção não existe na maioria das linguagens. Por exemplo, Ruby e C# não possuem o conceito de checked exceptions.

Unchecked: São exceções que o programador não é obrigado a tratar.  Elas podem ser tratadas, mas o ponto é que isto não é obrigatório! Caso não sejam tratadas,  são automaticamente relançadas para a camada acima até achar alguém em alguma camada que a trate – em última instância, o erro é lançado na tela ou pior, estoura silenciosamente e pára. Em Java, essas exceções são subclasses de RuntimeException (exceções de tempo de execução).

Ok então. Mas, qual das duas usar? “Se a maioria das linguagens não tem exceções checadas, é porque não são realmente muito importantes” você talvez pense.  Esse é um assunto que dá muito pano pra manga e vários escritores técnicos bem reconhecidos já opinaram sobre isso. Alguns contra, outros a favor. Mas, gosto de ver pelo seguinte ponto de vista: Isso depende muito do que você quer. Pense por um momento no conceito de capturar exceções. É uma chance que o programador tem de se recuperar de uma falha na execução do programa. Quando você lança uma exceção checada, você está na verdade deixando bem explícito pra quem for cliente do seu código que ele tem sim uma chance de se recuperar da falha que ocorreu.  Por exemplo, ao tentar sacar R$50,00 numa conta com R$10,00 de saldo, você deve lançar algo do tipo SaldoInsuficienteException – checada. Dessa forma, não tem perigo de o programador deixar essa falha vazar para a camada acima e você o dá a chance de redirecionar o usuário para uma página de erro mais amigável. Agora, caso haja um incêndio no prédio onde o seu banco de dados está e nessa mesma hora o usuário tentou persistir um novo saldo, o que fazer? Bom, não tem como se recuperar, tem? Normalmente não.  Você não quer obrigar o programador a tratar um erro que não tem mais o que fazer, quer? Então lance uma exceção unchecked. São justamente essas 2 perguntas que você deve se fazer. “Existe uma chance de se recuperar da falha? ” e/ou “Quero dar uma chance de se recuperar dessa falha?”. É por isso que exceções como a famosa NullPointerException são não-checadas. Mesmo que você trate dentro do bloco catch para que o procedimento seja repetido ou algo assim, não vai mudar o fato que você está tentando acessar uma referência nula. Simplesmente ocorreu um erro de programação.

Cada um no seu quadrado

É importante respeitar o nível de abstração dos seus componentes. Tipo,  você já deve ter visto algo como (assuma que essa essa exceção é checked):


class TelaSalvarConta {

...

    try {

        conta.salvar();

   }

    catch(ConcurrentSerializationException exc) {...}

Aqui temos uma classe de alto nível manipulando um objeto de domínio e lançando uma exceção de baixíssimo nível. Isso é uma exposição indevida de seus objetos e significa que você quebrou o encapsulamento violentamente. Essa exceção deve ter vindo de há muito tempo sendo relançada pela própria API  até chegar a camada mais alta do sistema. Isso significa que a assinatura do método salvar() da classe Conta tem uma exceção (throws) com esse mesmo nome: ConcurrentSerializationException. Absurdo, não? Isso jamais devia ter acontecido. Em algum ponto, algum programador da API deveria ter feito:


catch(ConcurrentSerializationException exc) {

    throw new ObjectAlreadyInTransactionException(exc);

}

Sendo ObjectAlreadyInTransactionException uma exceção não-checada, não seria necessário o programador declarar uma exceção na assinatura do método, além disso, se ele desejar tratar essa exceção mesmo sem ser obrigado a isso, ele tem uma exceção mais próxima da camada de negócios.

Em resumo, trate cada exceção de acordo com a camada do sistema a qual ela pertence.

Evite sujeiras

E por sujeira eu quero dizer exceções checadas. Por exemplo, você gosta da forma como a API IO de Java trata as exceções? Todas elas são checadas! Muitas vezes eu quero fazer algum teste bem bobo no qual eu preciso ler um arquivo ou um properties e adivinha… sim, preciso de um try-catch pra um erro que não há recuperação.  Lembre-se que o tratamento de exceções por meio de try-catch impossibilita melhoria de performance no seu código pela JVM. Isso é muito importante. Sem contar que deixa seu código sujo e “fedido”. Ou então você precisa de mais uma exceção declarada na assinatura do método, o que quebra o encapsulamento (dando dicas pra o cliente do código o que aquele método faz) e dificulta a flexibilidade da API impondo regras a quem quiser herdar aquele método.  Faça o máximo para sempre lançar exceções não checadas, exceto (como já falado) se existir uma forma de se recuperar do erro e você quiser realmente obrigar o cliente a fazer isso. Um aspecto em que você sai ganhando é que em exceções não checadas você pode simplesmente deixar que elas cheguem a parte mais “alta” da aplicação e tratá-las de forma centralizada. Por exemplo, num ambiente web com filtros.  Senão utilizando algum framework de AOP.

Os erros são amigos

É de partir o coração ver algumas pessoas fazerem o seguinte ao tratar erros:


catch(Exception e) {}

ou


catch(Exception e) {

throw new Exception("Erro!");

}

Esses dois exemplos mostram código terrível e jamais devem ser usados. No primeiro o programador simplesmente ignora o erro e este se perde no limbo. Se você for forçado a tratar o erro no seu código, você deve tentar ao menos logar a exceção. Jamais ignore-a completamente como no primeiro caso.  Uma boa abordagem é tentar relançar a exceção em forma de RuntimeException, dando um throw new RuntimeException(e) – ou qualquer outra exceção do tipo RuntimeException que se adeque semanticamente ao erro. Dessa forma, você pára de impor que as camadas acima tenham de tratar ou declarar a exceção. Repito, jamais ignore exceções no catch.

O segundo exemplo é melhor que o primeiro mas ainda assim não é o ideal. Se um outro desenvolvedor está testando sua aplicação e se depara com uma mensagem de exceção dessa? Tipo… o que aconteceu que deu erro? Pois é, você pode ser mais específico e criar uma exceção mais específica para o erro, assim também como uma mensagem mais explicativa. Lembre sempre disso: “Programe sempre como se o cara que fosse  manter seu código fosse um psicopata violento que sabe onde você mora”.

Conclusão

Mostrei aqui algumas dicas de boas práticas sobre como lidar com exceções. Não é nada muito difícil, realmente. Apenas exige esforço constante do desenvolvedor para que o código seja facilmente compreendido. Pense sempre se deve realmente usar uma exceção checada. Na maioria dos casos uma exceção não checada dá conta do recado de forma mais sucinta. Não misture as abstrações das exceções. Exceções de baixo nível em baixo nível, as de alto nível em alto nível. E sempre que for tratar uma exceção, verifique se está tratando no nível correto de abstração. Se estiver, utilize mensagens explicativas sobre o erro ocorrido. E caso encontre aberrações (como a mencionada no começo do post), não se jogue da varanda. Respire fundo e comece a aplicar o que aprendeu.

Padrão
Design, Design Patterns, Lógica

Tua hierarquia não irá te salvar garoto – Strategy

Imagine que hoje seu chefe chegou inspirado e te deu a missão de desenvolver o protótipo de um jogo de esportes. Nesse jogo, existem vários jogadores de modalidades distintas. De forma que todos eles possuem capacidades físicas inerentes aos esportes que praticam. Tipo, um jogador de futebol consegue correr muito rápido e pular razoavelmente alto. Um jogador de vôlei, por outro lado, não corre tanto assim, mas pula muito alto. Você, como excelente desenvolvedor em Orientação a Objetos pensa: “Já matei! Mais um problema manjado de herança!”. Bom, não necessariamente está errado. Você provavelmente vai surgir com uma solução. Mas, é esta solução a melhor? É a mais flexível, mais fácil de testar e de melhor manutenção?

Vamos lá, a primeira coisa a se fazer se você quer criar uma hierarquia de tipos é criar uma classe ‘pai’. Para jogadores parece bem óbvio… hum… Jogador. Vamos assumir para fins didáticos que a classe Jogador tem apenas 2 métodos: correr() e pular(), ambos abstratos. Em seguida, bolamos as classes filhas, digamos: JogadorFutebol e JogadorHoquei. No caso, teríamos:

public abstract class Jogador {
     public abstract void correr();
     public abstract void pular();
}
public class JogadorFutebol extends Jogador {
   public void correr() {
       System.out.println("correr como jogador de futebol...");
   }
   public void pular() {
       System.out.println("Pular como jogador de futebol...");
   }
}

public class JogadorHoquei extends Jogador {
    public void correr() {
        System.out.println("Correr de patins...");
    }
public void pular() {
        System.out.println("Não pular...");
    }
}

Legal! Isso funciona! Então beleza, agora seu chefe chega e diz que seus clientes estão loucos ensandecidos para que o jogo dê suporte a tênis! Bom, agora você vai ter apenas que criar mais uma classe filha de Jogador, só que dessa vez o jogador corre muito e não pula (o cliente pediu isso especificamente =P). Daí, resolve criar a classe JogadorTenis, que… “peraí! Eu posso herdar o corre() do jogador de futebol e o pula do jogador de hóquei e vou economizar um monte de código!” Hehe, tá, mas existem alguns problemas aí. Muitas linguagens nem mesmo suportam herança múltipla! Outra, herança múltipla pode te trazer outros problemas seríssimos. “Ah, então eu escolho uma das duas pra herdar e o outro método eu copio. =]” Nossa! Eu não sei como eu penso nessas coisas… Mas é uma idéia terrível! Primeiro, você não deve herdar classes com o objetivo de economizar linhas de código! Isso escapa completamente o objetivo de OO. Você herda quando uma classe realmente têm um relacionamento “IS-A”. Ou seja,  quando ela for uma especialização da classe pai e esta for uma generalização das filhas. No caso, JogadorTenis não é um JogadorHoquei nem tampouco um JogadorFutebol. Esqueça economizar linhas de código.

Esse primeiro motivo deve bastar, mas eu ainda completo com o segundo. Você ainda assim estaria duplicando código de outras classes. Por exemplo, se você resolve criar uma classe JogadorTenis que ‘extends’ Jogador, e implementa o método correr() como escrito na classe JogadorFutebol e pular() como na classe JogadorHoquei você estaria duplicando código 2 vezes! E isso é ruim, muito ruim. Imagina se mais tarde é descoberto um erro no método da classe pai? “Copia e cola” seria o seu segundo nome… É… herança parece ser uma má idéia nesse caso aí. Outro problema: caso o jogador se machuque e não consiga mais, digamos, correr. Como fazer a instância de um jogador de futebol executar um correr() diferente dinamicamente? E agora? De fato, você ficaria sem saída. Você poderia fazer vários tipos de jogador de futebol representando os estados do seu JogadorFutebol, sendo que cada uma herdando a classe pai… não, essa não é uma solução elegante. A sua hierarquia iria aumentar, o que significa que a raiz do seu problema ia crescer e assim sucessivamente.  Imagina agora se surge um JogadorPoker. Mas enfim… a ideia está próxima! O que você acha de composição? Pense nos comportamentos que os jogadores podem ter relacionados com as suas ações. Por exemplo, correr como? Pular de que maneira? Correr rápido é uma forma de correr. Não correr poderia ser outra. Correr de patins seria outra… E pular? Pular alto como um jogador de vôlei seria uma forma. Pular baixo seria outra… já deu pra pegar a idéia. Então, podemos encapsular esses comportamentos visto que são eles que mudam (e não os jogadores como estávamos pensando no começo do problema!). Esses comportamentos serão uma hierarquia por si próprios. E a maldita da composição, onde entra na história? Os seus jogadores vão possuir(HAS-A) esses comportamentos. Mais explicitamente, no caso de correr:

public interface AcaoCorrer {
     public void correr();
}
public class CorrerComPatins implements AcaoCorrer {
     public void correr() {
         System.out.println("Correndo com patins...");
     }
}
public class CorrerRapido implements AcaoCorrer {
     public void correr() {
         System.out.println("Correndo rápido como um jogador de futebol...");
     }
}
public class NaoCorrer implements AcaoCorrer {   
     public void correr() {
         System.out.println("Não corre.");
     }
}

public abstract class Jogador {

     AcaoCorrer acaoCorrer;
     AcaoPular acaoPular;

     public Jogador() {
         this.acaoCorrer = new NaoCorrer();
         this.acaoPular = new NaoPular();
     }
     public void correr() {
         this.acaoCorrer.correr();
     }
     public void pular() {
         this.acaoPular.pular();
     }

     public abstract void exibir();
     public void setAcaoCorrer(AcaoCorrer acaoCorrer) {
         this.acaoCorrer = acaoCorrer;
     }
//Foi omitido, mas suponha que existe uma ampla variedade de formas de pular, sendo todas filhas de AcaoPular    
    public void setAcaoPular(AcaoPular acaoPular) {
         this.acaoPular = acaoPular;
    }
}


Funciona do mesmo jeitinho e com mais vantagens:

1 – Temos um design voltado a interfaces ao invés de classes. Favorece a flexibilidade. Quando fazemos um comportamento ser representado por uma interface, ao invés de ser implementado na classe Jogador ou em uma de suas subclasses, estamos de certa forma “liberando” esse comportamento pra fora da classe. O comportamento (ou a hierarquia de comportamentos) está fora da própria classe. Além disso, estamos nos referindo ao comportamento de forma generalista, no caso AcaoCorrer > CorrerComPatins por exemplo. E na implementação apenas mencionamos como atributo o tipo da interface AcaoCorrer e não as reais implementações.

2 – É menos complexo você compor uma classe com outra do que começar uma nova hierarquia. Você estará sujeito a menos erros bobos assim.  Ah sim! E hierarquias podem se tornar coisas do capeta depois de 3 níveis.

3 – Podemos mudar o comportamento dinamicamente ou “on the fly”. Com os métodos setter dos comportamentos, podemos mudar o tipo de comportamento que quisermos quando bem entendermos. Exemplo:


public class Jogo {

    public static void main(String[] args) {

//Um jogador de futebol corre muito e pula normalmente

        Jogador jogadorFutebol = new JogadorFutebol();

        jogadorFutebol.correr();

        jogadorFutebol.pular();

//Um jogador de hoquei corre de patins e não pula

         Jogador jogadorHoquei = new JogadorHoquei();

         jogadorHoquei.correr();

         jogadorHoquei.pular();

//Um jogador de hoquei machucado, não consegue correr nem pular

         jogadorHoquei.setAcaoCorrer(new NaoCorrer());

         jogadorHoquei.correr();

         jogadorHoquei.pular();

    }

}

Note que é possível setar os comportamentos de correr e pular porque o parâmetro esperado no método setter é uma generalização – uma interface. Qualquer classe que implemente a interface em questão (o que significa que esse comportamento saiba correr ou pular) poderá ser passada como novo comportamento do Jogador.  Caso surja um JogadorPoker, ele será composto por comportamentos já existentes, NaoCorrer e NaoPular. Logo, não haverá duplicação alguma de código.

Todos esses passos mostrados acima compõem o conhecido padrão de projeto Strategy. Esse padrão está catalogado como um dos 23 Design Patterns do “Gang of Four” ou “GoF”.  Da próxima vez que você se deparar com um problema que tenha essa natureza de encapsular vários algoritmos ou comportamentos num mesmo objeto, lembre-se do padrão Strategy, que pode “mudar as vísceras” de um objeto.  Calma! Não saia por aí dizendo que conheceu um jeito de modelar objetos que mudou a sua vida (principalmente se você descobriu isso aqui nesse blog, nesse caso pesquise mais um pouco =P ). Esta é uma solução para esse tipo de problema, NÃO PARA TODOS.  Por isso, use-a com equilíbrio.

Existem várias APIs famosas que usam Strategy, como JPA (que usa 3 algoritmos diferentes de mapear classes a tabelas do BD) e o componente JComponent (na escolha de Borders) do Swing.  Como eu falei, essa solução clássica resolve vários problemas, e se você for do tipo que se depara com muitos, Strategy vai ser algo comum na sua vida. Por isso, ao se deparar com um problema que parece ser facilmente resolvido estendendo classes e/ou criando uma nova hierarquia, pense se composição não seria uma melhor solução. Não apenas você ficará feliz com uma solução elegante e abrangente, mas os programadores que porventura pegarem esse código no futuro também ficarão. =]

Padrão
Experiências, Lógica

Números “aleatórios” – Random

Você já se perguntou como o Math.random() gera um número aleatório ? Já? Bom, eu me perguntava bastante. Tanto que um dia resolvi revirar o que havia por trás dele. Isso já faz algum tempo, mas de qualquer forma, é algo interessante pra postar aqui.

Antes de mais nada não podemos descrever esses números como aleatórios ou randômicos porque é 100% previsível o número que o Math.random() vai gerar. Tá, tudo bem. Pra quem não se apega a detalhes e acha que isso é apenas semântica, vai em frente e chama isso de aleatório. Mas eles não são! São pseudoaleatórios. “Pseudo” porque são previsíveis como já falei. Como assim previsíveis? Veja. Em computação, existem os chamados “Pseudo Random number generators” ou geradores de números pseudo-aleatórios. Esses são baseados em uma equação que, recebendo como parâmetro um valor numérico, retorna uma série numérica. Os números que compõem essa série são normalmente bem diferentes uns dos outros (primos, ímpares, primos entre si, múltiplos de 7, próprio zero e assim por diante), o que podemos dizer que a equação gera números randômicos. O problema é que com o tempo, essa série começa a se repetir.

Mas… peraí! É previsível o número que o Math.random() vai gerar? Hã… sim! Ora, é baseado numa equação. E java.util.Random utiliza LCG  (Linear congruential generator):

 

Equação LCG

Equação LCG

Obviamente existem outras equações pra gerar essas sequências. Inclusive Von Neumann mesmo inventou uma (não muito boa por sinal).

Vamos ver na prática agora, como o Random gera a mesma sequência “aleatória” numa aplicação bem simples:

<code>

</p>

<pre>import java.util.Random;

public class GeradorAleatorio {

    private Random random;

    public GeradorAleatorio(long semente) {
         random = new Random(semente);
     }

     public int gerarInt() {
          return random.nextInt();
     }

     public long gerarLong() {
          return random.nextLong();
     }

     public double gerarDouble() {
          return random.nextDouble();
     }

     public static void main(String[] args) {
           GeradorAleatorio gerador =
           new GeradorAleatorio(10);
           long l = gerador.gerarLong();
           System.out.println(l);
      }
}

</code>

Pode rodar o código quantas vezes quiser. A sequência gerada sempre será “-4972683369271453960” seguido de “4755622236989466036“. Como o Random trata pra te entregar um valor double mais “bonitinho” é outra história.

Enfim, a questão é que a gente acabou de ver que esses números são sempre os mesmos pra o mesmo valor inicial passado (ou seed – semente). Daí surge a pergunta: “Como gerar números imprevisíveis?” Bom, existe uma técnica bem comum que é passar o System.currentTimeMillis() como seed ou valor inicial. Como esse valor corresponde ao número de milissegundos passados desde 1970 pra o tempo atual, esse valor irá variar numa amplitude maior ainda. Tornando-o estatisticamente imprevisível.

<code>
    GeradorAleatorio gerador =
    new GeradorAleatorio(System.currentTimeMillis());
    long l = gerador.gerarLong(); 
    System.out.println("O long aleatório foi: " + l);

</code>
Agora pode rodar o código quantas vezes quiser que o resultado será sempre diferente. Note que em teoria, o número sorteado ainda é previsível. Imagine que o mundo congelou no momento que a semente é passada para o construtor da classe Random e você pôde ver que número foi esse. Jogando ele na equação, você poderia prever com 100% de certeza que número seria sorteado. Mas enquanto o mundo não congelar tendenciosamente para alguns, o truque está seguro.

public class GeradorAleatorio {

 

private Random random;

 

public GeradorAleatorio(long semente) {

random = new Random(semente);

}

 

public int gerarInt() {

return random.nextInt();

}

 

public long gerarLong() {

return random.nextLong();

}

 

public double gerarDouble() {

return random.nextDouble();

}

 

public static void main(String[] args) {

GeradorAleatorio gerador = new GeradorAleatorio(System.currentTimeMillis());

long l = gerador.gerarLong();

System.out.println(l);

}

 

}

Padrão