Mostrando postagens com marcador fundamentos. Mostrar todas as postagens
Mostrando postagens com marcador fundamentos. Mostrar todas as postagens

15 de mar. de 2012

Lanço uma Exceção ou não? Depende.

Esses dias estava conversando na aula de Aspectos Avançados sobre o tratamento de erros em métodos.

Tipicamente e basicamente, existem duas formas de lidar com isso no Java: com exceções ou sem exceções. Simples assim.

É possível usar, tradicionalmente, um código de erro no retorno, ou retornar um valor nulo ou equivalente, que é a melhor opção (e única) para linguagens que não possuem estruturas de Exceção.

Ou, a cada parâmetro inválido ou problema na computação de um resultado, lançar uma Exceção apropriada a ser tratada (ou não) pelo chamador.

Tá, e daí? Quais são as diferenças práticas?

Bem, claramente o uso de Exceções enriquece o tratamento, já que podem ser personalizadas e enquanto objetos podem carregar diversas informações sobre o erro. Por exemplo, uma mesma exceção de acesso a um SGBD pode carregar, além da mensagem de erro, o código de erro SQL, a instrução submetida ao banco, a versão do banco, etc.

Por outro lado, sendo objetos, criar e lançar Exceções tem um custo de processador, para criar, inicializar e destruir o objeto, e memória, para armazená-lo. Ou seja, usar Exceções implica em um decréscimo de performance.

Não, não sou contra o uso de Exceções, não me entenda mal. Como pensador de projetos de sistemas quero colocar que devemos avaliar os prós e contras de diversas abordagens e, baseado nesses prós e contras, tomar decisões.

Neste caso, temos (mais) um problema de "cobertor curto": maior riqueza de informação (bom), maior uso de recursos (cpu, mem, disk, ou seja, ruim), menor riqueza de informação (ruim), menor uso de recursos (bom). Enfim, é um dilema.

Para ilustrar uma diferença de performance no uso ou não de Exceções criei um pequeno programa para calcular volume. Ele simplesmente multiplica três inteiros, desde que sejam positivos e aí está o tratamento de erro, zero ou números negativos não são permitidos. O código-fonte está aqui: ExPerfTest.java

Na primeira abordagem, um parâmetro zero ou negativo invalida o cálculo e sempre retorna 0 como volume. Ou seja, se eu chamar volume(10, -20, 30) ele retorna 0, não lança Exceções. No meu Notebook, a execução deste método 100 mil vezes com valor inválido é realizada em 8 milissegundos em média. A seguir está o método:
public static int volume(int a, int b, int c) {
    return a < 0 ? 0 : b < 0 ? 0 : c < 0 ? 0 : a * b * c;
}
Na segunda abordagem, um parâmetro zero ou negativo invalida o cálculo e lança uma Exceção. Ou seja, se eu chamar volume(10, -20, 30) ele cria (new) e lança (throw) um objeto da classe Exception. No meu Notebook, a execução deste método 100 mil vezes com valor inválido é realizada em 190 milissegundos em média. A seguir está o método:
public static int volume2(int a, int b, int c) throws Exception {
    if (a < 0 || b < 0 || c < 0) throw new Exception("número inválido");
    return a * b * c;
}
Claro, tenho que deixar claro que, neste projeto, há uma sobrecarga da própria instrumentação (em especial o Late Binding da Interface). Na prática esses tempos são menores, mas aproximadamente na mesma proporção, o que não muda o fato de Exceções reduzirem a performance. O projeto inteiro (no NetBeans) pode ser baixado neste link: exception-perf-test.zip

Finalizando, é importante deixar claro que a redução de performance é visível em várias chamadas ao método. Executando o mesmo método com e sem exceções apenas 3 vezes a diferença é praticamente inexistente ( < 1ms ). Estes dados indicam que métodos de acesso aleatório ou pontual podem lançar exceções sem problemas. Para métodos que são acessados frequentemente ou recorrentemente, e provavelmente podem receber dados inválidos, pode ser escolhido uma implementação sem Exceção.

Cada caso é um caso, mas só um lembrete: Exceções são para situações Excepcionais. Se a Exceção acontece com frequência, ela não é Excepcional, concorda?

Quero agradecer ao Everton e Cristiano pela conversa em aula e que me levou a criação deste Post, obrigado.
Abraços,
Márcio Torres

26 de set. de 2010

Documentar faz bem

Um recurso que às vezes não é aproveitado pelos desenvolvedores é a documentação.

Bem, já disse em outros posts que o código "fala", em outras palavras, um código bem escrito faz os outros programadores entenderem a intenção.

Também sou adepto do ditado que diz: "Sempre que for adicionar um comentário, pense: 'Como eu posso escrever este código para que este comentário não seja necessário?'".

Mas o que estou falando neste tópico é mais do que apenas inserir comentários, digo usar o método de documentação de código disponível na linguagem na hora de criar uma API.

Aqui Josh Bloch fala como desenhar uma boa API e em um trecho da apresentação diz:

Documente todas classes, interfaces, métodos ...

Também diz:

Reuso é algo mais fácil de falar do que fazer. Fazer requer bom design e muito boa documentação.

Cada linguagem fornece um meio de documentar as classes, interfaces e métodos, no Java tem o JavaDoc no .NET tem o XML Documentation e por aí vai.

No caso do Java, os profissionais que desenvolvem na linguagem já devem ter percebido quando usam um IDE com code completion/assist, como o Eclipse ou NetBens, que aparece a descrição dos métodos, exceções e parâmetros na janela de contexto, sempre que uma classe ou método é autocompletado ou o codeassist é invocado, como na ilustração:



Mãos a obra no Javadoc então

Vou ilustrar com uma aplicação baseada no modelo Transaction Script, onde disponibilizo uma classe de serviço com uma interface para operações de Crédito e classes de dados que representam o retorno desse serviço, ok?

Então eu tenho uma classe de dados que representa uma Analise de Crédito, como abaixo:



public class AnaliseCredito {

    // .. dados sobre a análise de crédito
}


A primeira documentação é sobre a classe, que irá expor detalhes importantes acerca do que as instâncias desta classe irão representar.

Todo início de documentação no padrão Javadoc inicia com /**, seguido de um * por linha e termina com */, como abaixo:



/**
 * 
 * @author Márcio Torres
 */
public class AnaliseCredito {

}


Outras informações pertinentes podem ser a versão e a qual a versão mínima, como segue:



/**
 * 
 * @author Márcio Torres
 * @version 1.0, 09/23/10
 * @since 1.5
 */


De acordo com a Oracle a versão deve usar o formato acima, mas obviamente cada equipe pode chegar em um consenso para isto, eu por exemplo gosto do formato timestamp (2010-09-23 13:55:52).

Ainda falta explicar a classe, como segue:



/**
 * Representa o resultado de uma Análise de Crédito com detalhes importantes
 * acerca de um determinado cliente e o risco a ele associado no que diz
 * respeito a empréstimos e financiamentos.
 * 
 * @author Márcio Torres
 * @version 1.0, 09/23/10
 * @since 1.5
 */
public class AnaliseCredito { ...


Tente sempre respeitar as 80 colunas para escrever esta descrição. Ainda é possível destacar certos trechos, como a tag tt que representa um trecho de código, i, strong, e outras tags HTML. Também é possível indicar a consulta para classes relacionadas através do @see, como abaixo:



/**
 * Representa o resultado de uma Análise de Crédito com detalhes importantes
 * acerca de um determinado cliente e o risco a ele associado no que diz
 * respeito a empréstimos e financiamentos, representados pelas
 * classes Emprestimo e Financiamento
 * 
 * Para analisar o risco use o método {@link #getRisco()}
 * 
 * @author Márcio Torres
 * @version 2010-09-23 13:40:25
 * @see Financiamento
 * @see Emprestimo
 * @since 1.5
 */
public class AnaliseCredito { ...



Como no código acima, ainda é possível estabelecer links para métodos importantes da classe. Embora existam outros detalhes, os que exemplifiquei acima já cobrem o básico, mais detalhes aqui: http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html

Documentar as interfaces e classes é só o começo, é necessário comentar os métodos. Neste caso, é comum nas interfaces esclarecer detalhes do contrato, sendo normalmente mais detalhado, e que também de algum modo dê informações a um programador que venha a implementá-la, como segue o exemplo abaixo de uma interface e uma implementação simples:



public interface CreditoService {

 AnaliseCredito analisaCredito(String cpf);
 
}

public class CreditoServiceImpl implements CreditoService {

 @Override
 public AnaliseCredito analisaCredito(String cpf) {
  if (cpf == null) {
   throw new NullPointerException("O CPF não foi informado (nulo)");
  }
  if (cpf.length() < 11) {
   throw new IllegalArgumentException("O CPF não está completo, use apenas números e sem formato, ex: 11122233344");
  }
  if (cpf.length() > 11) {
   throw new IllegalArgumentException("O CPF tem caracteres a mais, use apenas números e sem formato, ex: 11122233344");
  }
  
  // Verifica o Crédito através de uma integração
  // pode lançar WebServiceException
  // webService.checkCredit(cpf) ... 
  
  AnaliseCredito analise = new AnaliseCredito();
  
  return analise;
 }

}


Documentando a interface:



/**
 * Contrato para as operações relacionadas a <strong>Crédito</strong> e
 * operações financeiras.
 * 
 * @author Márcio Torres
 * @version 2010-09-23 14:13:10
 * @see AnaliseCredito
 * @since 1.5
 */
public interface CreditoService {

 /**
  * Dado um CPF reúne informações pertinentes e traz a resposta na forma
  * de uma instância de {@link AnaliseCredito}.
         * 
         * Para obter uma análise com mais dados use
  * {@link #analiseCompletaCredito(String)}
         *
  * @param cpf
  *   O número do CPF que será utilizado para recolher as informações
  *   sendo apenas número e sem formato, por exemplo: <tt>11122233344</tt>
  * @return
  *   Uma análise de crédito representada pela classe
  *   {@link AnaliseCredito}.
         * @throws NullPointerException 
  *   Se for passado um parâmetro nulo
  * @throws IllegalArgumentException
  *   Se o CPF não tiver 11 números ou for inválido
  */
 AnaliseCredito analisaCredito(String cpf);
 
}



No exemplo foi documentada a interface e o método analisaCredito. Na documentação do método é explicada a pré-condição, pós-condição e efeitos colaterais, bem como o formato do parâmetro esperado e as exceções que podem ser lançadas. A classe de implementação pode ser mais espartana, mas ainda tem que documentar detalhes da implementação, como exceções novas. Segue exemplo:



/**
 * Executa operações relacionadas a <strong>Crédito</strong> e financeiras
 * 
 * @author Márcio Torres
 * @version 2010-09-23 14:24:20
 * @see AnaliseCredito
 * @since 1.5
 */
public class CreditoServiceImpl implements CreditoService {

 /**
  * Dado um CPF reúne informações pertinentes devolvendo
  * uma Análise de Crédito {@link AnaliseCredito}.
         * 
  * @param cpf
  *   O número do CPF que será utilizado para recolher as informações,
  *   por exemplo: <tt>11122233344</tt>
  * @return
  *   Uma análise de crédito
  * @throws NullPointerException 
  *   Se for passado um parâmetro nulo
  * @throws IllegalArgumentException
  *   Se o CPF for inválido
  * @throws WebServiceException
  *   Se houver um problema de comunicação com o subsitema de integração
  */
 @Override
 public AnaliseCredito analisaCredito(String cpf) {
 
     // implementação
     
 }
 
}



Resultados

O resultado da documentação, no IDE Eclipse por exemplo, é o que pode ser visto abaixo:



Ao completar o método fica assim:



E pelo Eclipse é possível exportar como HTML através de File -> Export -> Javadoc. Após concluir, se todas as opções ficarem em default, aparecerá um diretório doc no projeto e os HTML's. Basta usar um Open With -> Web Browser, e mais tarde disponibilizar a documentação em um lugar comum para a equipe.

Veja abaixo como ficou:



Outras linguagens

Outras linguagens disponibilizam o seu meio de documentação, PHP, ActionScript, C#, esta última posso dar um exemplo pois tenho mais contato:

C# com XML Documentation:



namespace Business.Services {

    /// <summary>
    ///     Executa as operações relacionadas a crédito e finanças.
    /// <summary>
    /// <remarks>
    ///     A partir desta classe são disponibilizados ...
    /// </remarks>
    public class CreditoServiceImpl : CreditoService {

       /// <summary>
       ///     Checa o crédito dado um CPF ...
       /// </summary>
       /// <param name="cpf">
       ///     CPF para consultar ....
       /// </param>
       /// <seealso cref="AnaliseCompletaCredito">
       ///     Veja o método AnaliseCompletaCredito
       /// </seealso>
       /// <exception cref="System.ArgumentException">
       ///     Lançada quando o CPF não é válido
       /// </exception>
       /// <exception cref="System.ArgumentNullException">
       ///     Lançada quando o CPF não é nulo
       /// </exception>
       public AnaliseCredito AnaliseCredito(String cpf) {
         ...


Acho que é isso. A implementação no exemplo não foi perfeita, mas penso ter coberto o objetivo de apresentar a documentação e espero ter ficado entendido a necessidade da mesma.

Abraço, até a próxima.

26 de abr. de 2010

Java passa por valor

Salve

No melhor estilo Nerd, neste fim de semana, em um BoF*, rolou o assunto de como as linguagens passam as variáveis para os métodos (functions, procedures, ). Estavam presentes programadores PHP, C e Java (eu).


Tudo ia bem, até por que entendo pouco de PHP e C, foi então que chegamos nas linguagens orientadas a objeto (eu sei que PHP é orientada a objetos -tem suporte pelo menos- mas quem usa?).

A discussão rola na internet também, e a pergunta que não quer calar é:

"Java passa as variáveis por valor ou por referência?"

Obs.: Passar por valor é às vezes chamado de "Passar por cópia".

Como funciona no Java -e maioria de linguagens orientadas a objetos-:

Imagine a seguinte situação:

int x = 12;
int y = x;

System.out.println(x);
System.out.println(y);

Este caso é um exemplo com tipos primitivos, e a resposta será:

12
12

E se decorrer eu adicionar:

y = 24;

System.out.println(x);
System.out.println(y);

A resposta será:

12
24

Sim, na linha em que declaramos y = x, a variável y é colocada na pilha e logo após recebe uma cópia do valor de x, então tenho isso na pilha:

x = 12
y = 12

A alteração de uma, não influencia a alteração de outra, isto é passagem por valor. Então se y = 24, x continua sendo 12. y obteve uma cópia dos bits de x.



Contudo, a situação se complica um pouco quando falamos de objetos, imagine a situação;

Cliente x = new Cliente();
x.setNome("Juliana Paes");

Cliente y = x;

x.setNome("Débora Secco");

System.out.println(x.getNome());
System.out.println(y.getNome());

A resposta será:

Débora Secco
Débora Secco

AHA! Então passou por referência! NÃO, passou por cópia.



Quando falamos em referência, referência = variável, não o Objeto no Heap. O que aconteceu neste caso foi o seguinte:

Memória ----------------------------------------+
Stack -------+                                  |
x = EF4EA3   |                                  |
y = EF4EA3   |                                  |
                                                |
Heap ----------------------------------------+  |
+EF4EA3---------+                            |  |
| Cliente       |                            |  |
| Juliana Paes  |                            |  |
+---------------+                            |  |
------------------------------------------------+

As variáveis contém uma referência para o objeto no heap, que tem um ID indeterminável (só a JVM sabe, provavelmente um número de 32 ou 64 bits), logo na declaração das variáveis:

x = referência para objeto EF4EA3 no Heap
y = x    //y = EF4EA3

Ou seja, y tem uma cópia do valor de x, o mesmo caso da cópia dos bits, que é a referência, o "id", do objeto. Para ilustar imagine esta situação:

Cliente x = new Cliente();
x.setNome("Juliana Paes");

Cliente y = x;

x = new Cliente();
x.setNome("Débora Secco");

System.out.println(x.getNome());
System.out.println(y.getNome());

Qual é a saída? Bem, está abaixo:

Débora Secco
Juliana Paes

Dois objetos foram criados, usando a variável x. No primeiro instante, y obteve uma cópia do valor de x, uma referência para Juliana Paes. Mais tarde, x recebeu um novo cliente, logo, uma nova referência a outro objeto no Heap, e esta alteração no valor de x não altera o valor de y, isto é o que quero dizer com passagem por valor. 

Cada variável tem um valor, que é a referência a um objeto, não o próprio objeto (isto é bem básico).



Na passagem para métodos, acontece o mesmo:

// em algum lugar 
Cliente x = new Cliente();
x.setNome("Juliana Paes");

changeCliente(x);

System.out.println(x.getNome());

//


public void changeCliente(Cliente x)  {
    x = new Cliente();
    x.setNome("Débora Secco");
}

Qual é a saída do console?

Ora, Juliana Paes, claro. Existem duas variáveis com o nome x, uma fora do método, e outra, local, dentro do método. A mudança da variável interna, não afeta a externa, afinal, ela tem uma cópia do valor.



// em algum lugar 
int x = 18;

change(x);

System.out.println(x);

//


public void change(int x)  {
    x = 9;
}

A saída é 18.



Algumas linguagens permitem usar, alternativamente, a passagem por referência, como a C#.

Ilustrando:

// em algum lugar 
int x = 18;

change(x);

Console.WriteLine(x);
//

public void change(ref int x)  {
    x = 9;
}

A saída é 9. A diferença foi o uso da palavra-chave ref antes de declarar o parâmetro.



Em VB.NET, para passar por valor (default) seria assim:

Sub Change(ByVal x As Integer())
    // faz algo
End Sub 

E por referência:

Sub Change(ByRef x As Integer())
    // faz algo
End Sub 


Obs. 1: Não conheço muito a sintaxe do VB, se alguém detectar um erro notifique-me por favor.
Obs. 2: Não entrei em detalhes como o de objetos distribuídos, em que o objeto é serializado e enviado uma cópia de um espaço de memória para outro (ex.: de uma JVM para outra). Isto é uma condição excepcional.
Obs. 3: Relacionado à obs 2, é por isso que usamos interfaces locais sempre que possível, e interfaces remotas só quando for estritamente necessário (EJB, etc).


Resumindo, Java passa por valor, não por referência. 


Este assunto é complicado de explicar, confuso e difícil de entender geralmente, mas seu conhecimento é um fundamento.

Dúvidas, críticas, correções, sugestões, fiquem a vontade.


E, falando em referências, leitura recomendada: