sexta-feira, 6 de dezembro de 2013

Propagação Deficiente - Antipattern de exceção

Para entender melhor este antipattern, é necessário entender o que compõe uma exceção.

Exceção Exemplo #1
ApplicationException: Erro ao carregar configuracoes
    at LeitorConfiguracoes.lerArq(LeitorConfiguracoes.java:404)
    at DeamonAplicacao.iniciar(DeamonAplicacao.java:376)
    at java.lang.Thread.run(Thread.java:722)
Caused by: java.lang.NullPointerException
    at LeitorConfiguracoes.parser(LeitorConfiguracoes.java:120)
    at LeitorConfiguracoes.lerArq(LeitorConfiguracoes.java:386)
    ... 2 more
Identificando os componentes da exceção:
  • Classe: ApplicationException
  • Mensagem: Erro ao carregar configuracoes
  • Stack Trace: desde LeitorConfiguracoes.lerArq, até java.lang.Thread.run
  • Caused by: é a nova exceção a partir da linha "Caused by" (inclusive)
Lembrando que a mensagem, e o "caused by" não são obrigatórios.
Veja o outro exemplo abaixo:
Exceção Exemplo #2
java.util.NoSuchElementException
    at LeitorConfiguracoes.parser(LeitorConfiguracoes.java:124)
    at LeitorConfiguracoes.lerArq(LeitorConfiguracoes.java:386)
    at DeamonAplicacao.iniciar(DeamonAplicacao.java:376)
    at java.lang.Thread.run(Thread.java:722)
Identificando os componentes da exceção:
  • Classe: java.util.NoSuchElementException
  • Mensagem: não tem (ela deveria estar na frente do nome da classe da exceção)
  • Stack Trace: desde LeitorConfiguracoes.parser, até java.lang.Thread.run
  • Caused by: não tem
Com base nos exemplos acima, agora mostro o erro mais comum, quando se trata de exceção:
catch (Exception e) {
    log.error(e.getMessage());
}
Ou
catch (Exception e) {
    throw new ApplicationException(e.getMessage());
}
Para a Exceção Exemplo #1, dos 4 elementos que tem a exceção, a expressão "e.getMessage()" representa apenas "Erro ao carregar configuracoes". Ela seria logada da seguinte forma:

17:52:37 [ERROR] Erro ao carregar configuracoes
Somente com esta informação, como é possível fazer um trobbleshooting eficiente? A classe, a Stack Trace e o Caused By são MUITO importantes. A mensagem é apenas um fragmento da exceção.
Para a Exceção Exemplo #2, é muito pior. A exceção não tem mensagem. E apenas encontraríamos uma linha perdida no log com a seguinte aparência.
17:52:37 [ERROR] null
Uma linha assim não ajudaria em nada, praticamente.
Então lembre-se:

  • Em um log de erro, o primeiro parâmetro é uma mensagem, e o segundo parâmetro a exceção (com isso, todos os componentes da exceção são logados)
  • Se for encapsular e lançar a nova exceção, em geral, existe um construtor da exceção onde o primeiro parâmetro é uma mensagem, e o segundo parâmetro é a exceção (parâmetro cause)

catch (Exception e) {
    log.error("Erro na operacao", e);
}
Ou
catch (Exception e) {
    throw new ApplicationException("Erro na aplicacao", e);
}

Captura e Ignora - Antipattern de exceção

Em qualquer sistema, as exceções sempre irão existir, e por motivos diversos:
  • a importação de um arquivo com um dado não esperado, 
  • uma sobrecarga no sistema que provocou um timeout em uma operação, 
  • uma tabela que esqueceram de atualizar no banco, 
  • o disco que encheu, 
  • um arquivo com permissão de somente-leitura,
  • um possível bug,
  • etc etc etc.
Estas ações são indesejáveis e, por mais exaustivamente que se teste o sistema, você nunca terá a garantia que estará livre dos problemas acima. Você dificilmente conseguirá sempre realizar testes para todas estas condições e tantas outras não listadas.
Mas o que temos que fazer afinal?
Tratar a exceção da melhor maneira possível, informando a exceção com precisão no log, e passando uma mensagem precisa ao usuário. Mas as vezes encontramos exatamente o contrário, trechos de código que captura a exceção, e segue em frente como se nada tivesse acontecido.
Não faça isso com sua exceção
Abaixo estão alguns exemplos do que NÃO se deve fazer:
catch (IOException e) {
  return null;
}
Ou
catch (IOException e) { }
Com isso, além de simplesmente retornar nulo (ou não fazer nada), ao invés de manipula-la, ou relançar a exceção, você joga fora de vez qualquer informação referente ao problema que gerou a exceção.
Outras maneiras de esconder a exceção:
catch (IOException e) {
    log.error("Erro", e);
    return null;
}
Ou
catch (IOException e) {
    e.printStackTrace();
    return null;
}
Ou

catch (IOException e) {
    e.printStackTrace(); // Ou log.error("Erro", e);
}
De maneira similar as situações anteriores, a única diferença é que a exceção, pelo menos, foi logada, mas o fluxo de execução continua normalmente. Isto nem sempre está errado, mas muitas vezes o programador não insere este código nos pontos apropriados. Ao invés de retornar nulo ou seguir com o fluxo de execução, a exceção deveria ser tratada.

quinta-feira, 28 de novembro de 2013

Logar e Relançar - Antipattern de exceção


Quando se trata uma exceção, o correto é:
  • Ou envolver a exceção em uma nova exceção, passando mais informações;
  • Ou tratá-la, logando a exceção e exibindo ao usuário, quando for o caso. 
Mas nunca os dois (relançar e logar). Considere o Servlet abaixo. Neste exemplo, este é o nível mais alto da operação, então o papel do tratamento de exceção é informar ao usuário (logar e enviar uma mensagem para o Response).

public class ClienteServlet ...
    ...
    public void gravarCliente(ServletRequest req, ServletResponse res) {
        try {
            DAOCliente.gravar(criaClienteVo(req));
        } catch (RuntimeException e) {
            log.error("Erro ao salvar cliente", e);
            res.setAttribute("error", "Erro. Contate suporte.");
        } catch (BusinessException e) {
            log.error("Erro de negócio ao salvar cliente", e);
            res.setAttribute("error", "Erro ao salvar cliente " 
                    + e.getMessage());
        }
    }
}    

Exemplo de Antipattern 1

No caso de um código que loga e relança, como o código abaixo, teremos a saída mostrada na sequência. Note que a exceção é mostrada 2 vezes, mas de maneiras diferentes. 

public class DAOCliente ...
    ...
    public void gravar(ClienteVo cliente) throws BusinessException {
        try {
            ...
            ... COMANDO INSERT
            ...
        } catch (SQLException e) {
            log.error("Erro ao salvar cliente", e);
            throw new BusinessException("Erro no banco", e);
        }
    }
}

13:40:02 [ERROR] Erro ao salvar cliente
java.sql.SQLException: ORA-00001: unique constraint ...
    at oracle.driver...SomeOracleDriverClass()
    at DAOCliente.gravar(DAOCliente.java:50)
    at ClienteServlet.gravarCliente(ClienteServlet.java:85)
13:40:02 [ERROR] Erro de negócio ao salvar cliente
BusinessException: Erro no banco
    at DAOCliente.gravar(DAOCliente.java:63)
    at ClienteServlet.gravarCliente(ClienteServlet.java:85)
Caused by: java.sql.SQLException: ORA-00001: unique constraint ...
    at oracle.driver...SomeOracleDriverClass()
    at DAOCliente.gravar(DAOCliente.java:50)
    ... 2 more

Exemplo de Antipattern 2

Ou ainda como o código abaixo, exatamente a mesma exceção é logada 2 vezes.

public class DAOCliente ...
    ...
    public void gravar(ClienteVo cliente) {
        try {
            ...
            ... COMANDO INSERT
            ...
        } catch (SQLException e) {
            log.error("Erro ao salvar cliente, e);
            throw new e;
        }
    }
}

13:40:02 [ERROR] Erro ao salvar cliente
java.sqlSQLException: ORA-00001: unique constraint ...
    at oracle.driver...SomeOracleDriverClass()
    at DAOCliente.gravar(DAOCliente.java:50)
    at ClienteServlet.gravarCliente(ClienteServlet.java:85)
13:40:02 [ERROR] Erro de negócio ao salvar cliente
java.sql.SQLException: ORA-00001: unique constraint ...
    at oracle.driver...SomeOracleDriverClass()
    at DAOCliente.gravar(DAOCliente.java:50)
    at ClienteServlet.gravarCliente(ClienteServlet.java:85)

Código esperado

O código deve OU relançar uma exceção (caso seja um nível intermediário ou inferior) OU logar e informar ao usuário (caso seja o nível superior). No exemplo abaixo, como não é a classe de nível superior, ela deve apenas relançar a exceção. Com isso, o log fica com apenas uma única ocorrência.

public class DAOCliente ...
    ...
    public void gravar(ClienteVo cliente) {
        try {
            ...
            ... COMANDO INSERT
            ...
        } catch (SQLException e) {
            throw new BusinessException("Erro no banco", e);
        }
    }
}

13:40:02 [ERROR] Erro de negócio ao salvar cliente
BusinessException: Erro no banco
    at DAOCliente.gravar(DAOCliente.java:63)
    at ClienteServlet.gravarCliente(ClienteServlet.java:85)
Caused by: java.sql.SQLException: ORA-00001: unique constraint ...
    at oracle.driver...SomeOracleDriverClass()
    at DAOCliente.gravar(DAOCliente.java:50)
    ... 2 more

terça-feira, 19 de novembro de 2013

Exceção Java, torne-se sua aliada

Quem é que gosta quando a sua aplicação retorna com alguma exceção? Acho que ninguém, não é mesmo? E um atendente que está em um atendimento telefônico tentando registrar uma solicitação do cliente no sistema e acontece algum erro? O atendente e o cliente também não vão gostar nem um pouco de receber a exceção.
Sentimento do usuário quando verifica uma mensagem genérica de erro

E o que deve ser feito? Deve mostrar o problema claro e preciso possível para o atendente ou para o operador, para se tenha total condição de resolver o problema o quanto antes. 
Sempre que acontece um erro, a JVM gera uma classe que contém as informações sobre o problema. Esta classe estende Throwable. Existem 3 tipos destas classes:

  • Exceções verificadas (checked exceptions) - exceções que devem ser tratadas na cláusula throws de um método ou catch de um bloco try. Estas exceções estendem a classe Exception. Problemas de comunicação com sistema externo, ou problemas de entrada do usuário, são alguns dos exemplos destas exceções. 
  • Exceções não-verificadas (unchecked exceptions) - exceções que não devem ser tratadas na cláusula throws de um método ou catch de um bloco try. Estas exceções estendem a classe RuntimeException. Em geral são problemas não esperados, podendo até mesmo indicar um bug no sistema. O mais comum é o NullPointerException
  • Erros - são problemas na JVM que, em geral, não são recuperáveis. Estendem a classe Error. Exemplo: OutOfMemoryError, LinckageError, e StackOverflowError
Mesmo as vezes sendo Error, e estendendo Throwable, o conjunto de todas estas classes, normalmente são chamadas de Exceções (ou Exception).
Diagrama de classes referente as exceções (Throwable)

E é importante entender que a exceção (ou Throwable) tem 4 componentes e TODOS eles são importantes:

  1. Classe - indica qual tipo de erro ocorreu
  2. Mensagem (opcional) - detalha o erro com uma mensagem, quando for o caso
  3. Stack Trace - indica em que ponto do código aconteceu o erro, e toda a pilha de métodos que foi chamada para chegar neste ponto
  4. Cause (opcional) - indica qual foi a exceção original, quando for o caso

segunda-feira, 11 de novembro de 2013

Boas práticas para o uso de logs em Java

O JDK vem com o pacote java.util.logging que tem a biblioteca para tratamento de log nativa e o Log4J do projeto Apache é a opção log mais utilizada. O Log4J tem mais features que o java.util.logging. O Common Logging também da Apache é apenas um wrapper (uma casca) para encobrir o uso de qualquer mecanismo de log, inclusive o Log4J e java.util.logging.

Uma mensagem em qualquer mecanismo de log pode ter níveis de severidade diferentes. Estes níveis se dividem basicamente em:

  • fatal - este nível deve ser utilizado em casos extremos, em geral mensagens que impacta negativamente todo o ambiente (e não apenas uma sessão de um usuário) e exige uma ação não de um operador, mas sim do administrador do ambiente e do arquiteto do sistema
  • error - este nível representa uma exceção Java que ocorreu durante a execução de uma operação de um usuário ou a execução de uma tarefa. Pode-se enviar mensagens neste nível para alguma inconsistência identificada pelo próprio sistema, mesmo que não gere exceção. 
  • warn - este nível é utilizado para alguma informação que seja interessante ser registrada, que pode representar um problema, mas o sistema se recuperou e não deixou de executar a tarefa.
  • info - este nível de mensagem é utilizado para indicar o que é que a aplicação está fazendo em alto nível. 
  • debug - este nível de mensagem, são de baixo nível, onde detalha resultados intermediários de uma tarefa, e são utilizados, normalmente para depuração de um trecho da aplicação
Ata da Reunião - cada anotação tem uma severidade diferente
Não se deve usar a saída padrão (System.out) ou a saída de erro (System.err). Deve sempre usar o log. O log é configurável, você habilita e desabilita seus níveis, permite escrever em diversos destinos diferentes (console, arquivo, banco de dados, rede, etc), seleciona o nível por pacote Java, permite rotacionar, informar data e hora para toda mensagem, entre outras vantagens. Escrever na saída padrão ou de erro não permite nada disso, o que pode tornar a informação volumosa e confusa. 

Quando na aplicação se escreve em níveis mais baixo, principalmente o nível debug, é importantíssimo utilizar isDebugEnabled() por questão de otimização. É normal que, quanto menor o nível, mais mensagens existam, mas é normal que você não queira ver as mensagens de log de todos os módulos da aplicação, mas apenas de alguns. Então deve-se habilitar o log apenas destes módulos.

log.debug("Retorno cliente " + cliente + ", Status=" + status);

No exemplo acima, sempre irá concatenar a string, para chamar o método log.debug, para verificar se está habilitado ou não o log para este pacote Java. Caso esteja, escreve no log normalmente, mas caso não esteja, o Java realizou a concatenação da string desnecessariamente. Agora imagine este tipo de código em um loop e em diversos pacotes diferentes, sendo que o modo debug não está ligado. É uma concatenação desnecessária que ocorre com todos os log.debug. Por questão de otimização, deve-se verificar o nível de log antes de concatenar a string do debug.


if (log.isDebugEnabled()) {
    log.debug("Retorno cliente " + cliente + ", Status=" + status);
}

Logando exceção

Quando o código encontra uma exceção, ele deve:

  • tratar a exceção - se seu código pode tratar a exceção (ex.: falha de rede), então deve fazê-lo; ou
  • relançar a mesma exceção - em caso de exceção unchecked; ou
  • reencapsular a exceção - no caso de exceções checked; ou
  • logar e exibir ao usuário - se em nenhum outro ponto foi possível tratar a exceção, em geral no nível mais alto da stack trace (na Action, ou onMessage, ou no método main), então a mensagem deve ser logada e informada ao usuário.
Sempre que reencapsular a exceção, a nova exceção precisa fazer bom uso dos dois parâmetros que existem nos construtores das exceções:
  • message - informar a atividade que estava tentando ser realizada quando a exceção ocorreu, preferencialmente informando algum parâmetro de entrada para ajudar a identificar o erro; e
  • cause - é a exceção original
Um exemplo de como relançar a exceção pode ser conferido a seguir:


void salvar(Cliente c) {
    try {
        DAOCliente.salvar(c);
    } catch (SQLException ex) {
        throw new AppException(
            "Erro de banco ao salvar cliente " // ação com erro
            + c != null ? c.getNome() : null, // um parâm. de entrada
            ex); // exceção original
    }
}

Da mesma maneira, estes dois parâmetros são de extrema importância durante o log no nível mais alto de erro:

void doGet(HttpServletRequest req, HttpServletResponse resp) {
    try {
        // . . .
        businessClient.salvar(c);
        // . . .
    } catch (AppException ex) {
        log.error(
            "Erro ao tentar cadastrar cliente " // ação com erro
            + req.getParameter("nome"), // um parâmetro de entrada
            ex); // exceção original
    }
}


quarta-feira, 30 de outubro de 2013

Tecnicas de Levantamento de Informações

  1. Entrevista
    • É a interação com o Cliente/Usuário. Consiste na seguinte lógica de organização que possibilita em ótimos resultados:
      • Preparação da entrevista: É importante estudar o material disponível, estabelecer um principal objetivo da entrevista e preparar uma lista de questões.
      • Condução da entrevista: Falar menos que o entrevistado e se possível levar um escriba (responsável por registrar os pontos importantes da entrevista).
      • Finalização da entrevista: Reservar alguns minutos pra resumir e consolidar os principais pontos discutidos/citados, informar quais serão os próximos passos e que será enviado uma ata (explicar que, após aprovado será publicada).
  2. Pesquisa a Manuais e Registros
    • Internos
      • Manuais
      • Relatórios
      • Formulários
      • Bases de Conhecimento
    • Externos
      • Publicações especializadas do negócio
      • Estatísticas governamentais
      • Relatórios de pesquisa e tendências
      • Vendedores, Clientes
      • Internet, Bases de Conhecimento
  1. Questionário
    • É usado geralmente para coletar informações de um grupo, departamento ou até mesmo individualmente. São informações solicitas por escrito, quando se tem dificuldade pra reunir os stakeholders por exemplo.
    • Um bom questionário deve ser bem estruturado e deve ser de fácil entendimento, com perguntas claras e objetivas, por isso exige um bom tempo para criá-lo.
    • Preferencialmente, antes de envia-lo é ideal que faça um teste com algumas pessoas, a fim de obter opiniões e qualidade do mesmo.
  2. Observação
    • Utilização do método "In Loco"
      • Esse processo consiste em acompanhar de perto todos os passos e processos da tarefa em questão. 
    • As principais dúvidas a serem esclarecidas são:
      • Qual o escopo/objetivo do trabalho
      • Qual o resultado final, o que se espera como resultado final?
      • Quem executa cada passo do trabalho?
      • Com quais recursos?
      • Como se faz?
      • Quando se inicia este trabalho e quais são as entradas
      • Quem solicita?

  3. Role Playing
    • Analistas
      • É ideal aprender todo o trabalho e processo (fim a fim) do usuário
    • Vantagens
      • Levanta informações ainda não mapeadas nas demais tecnicas e obtem maior domínio do problema.
      • Conhece os problemas que geralmente os usuários enfrentam.
  4. Brainstorming

    • Grupo de no máximo 10 pessoas (aconselhável) reunidas, trocando informações e idéias com a finalidade de resolver algum problema/propor novas soluções. Quanto maior o número de ideias, melhor vai ser o resultado.
      • O objetivo precisa ser bem definido
      • Modificar as ideias ou combina-las pode ter bons resultados.
      • Debates ou críticas não podem ser permitas, a finalidade é escutar a todos, independente da opinião.
  5. Storyboard
    • São dados gráficos, que tem a finalidade de ajudar a mostrar/ilustrar o processos e ou passos do trabalho, de forma sequencial. Normalmente mostra quais são os atores, quando se inicia cada passo e são quais os objetivos.
  6. Workshop
    • Conduzido por um facilitador (geralmente de 1 a 5 dias), tem a finalidade de se chegar num consenso sobre escopo, riscos e caracteristicas (Casos de Uso, Requisitos, Regras de Negócio, etc).
      • Obs: As decisões são escritas e aprovadas na hora. As mesmas são enviadas a todos os participantes.
  7. JAD (Joint Application Design)

    • Consiste numa dinamica de grupo, que tem como objetivo definir os objetivos e requisitos de um projeto/sistema.
    • Recursos visuais são muito usados e tem como finalidade estimular os sentidos, criar interesses, esclarecer alguns pontos confusos e otimizar o tempo da dinamica. Consegue definir objetivos com mais eficaz, evitando retrocessos.
    • Toda documentação é gerada durante as sessões, assim é possível que os usuários entendam o porque das decisões feitas podendo discutir o conteúdo criado.
    • Cerca de 20 a 60% de melhora na produtividade para especificação de requisitos, se comparado com outros métodos tradicionais.
  8. Protótipos
    • É a demonstração de um modelo ou comportamento de um projeto/solução, muito útil e recomendado para:
      • Obter feedback do cliente
      • Validar alguns requisitos
      • Demonstrar entendimento do escopo
      • Obter aprovação da solução
    • OBS: É importante deixar claro para o cliente que se trata de um protótipo (um esboço desenhado numa folha de papel é muito utilizado por alguns analistas), e não do sistema em si. O intuito é dar um esboço e validar os pontos citados anteriormente. 

terça-feira, 29 de outubro de 2013

Termo de Abertura de Projeto (TAP) - Project Charter (PC)

termo de abertura do projeto (TAP) ou Project Charter (PC) em inglês, é o documento que autoriza formalmente o projeto. Ele concede ao gerente/gestor a autoridade para utilizar os recursos da organização na execução das atividades do projeto.
O termo de abertura do projeto deve abordar, ou referenciar, as seguintes questões:
  • requisitos que satisfazem as necessidades do cliente;
  • objetivos do projeto (que devem ser SMART);
  • propósito ou justificação do projeto;
  • stakeholders do projeto e os seus papéis e responsabilidades;
  • expectativas dos stakeholders;
  • identificação do gestor do projeto. e nível de autoridade do gerente;
  • cronograma macro dos marcos do projeto;
  • premissas, ou pressupostos, organizacionais (fatores considerados verdadeiros, reais ou certos);
  • restrições organizacionais (fatores que limitam as opções da equipe);
  • investimento (orçamento preliminar);
  • constrangimentos e riscos;
  • descrição do sub-produto(s) identificados;
  • milestones identificadas.

Permite assim responder a questões como:

  • O que deve ser feito para atingir o objectivo do projeto?
  • Como deve ser feito?
  • Quem o vai fazer?
  • Quando deve ser feito?