MindView Inc.

Pensando em Java, 3ª ed. Revisão 4.0


[ Viewing Hints ] [ Book Home Page ] [ Free Newsletter ]
[ Seminars ] [ Seminars on CD ROM ] [ Consulting ]

Anterior Próximo Página Inicial Índice Conteúdo

15: Descobrindo Problemas



Antes de C ser padronizada como ANSI C, tinhamos uma piadinha: “Meu código compila, então, deveria rodar!” (Ha ha!)



Era engraçada apenas se você entendia C, porque, na época, o compilador C aceitava quase qualquer coisa; C era realmente uma “linguagem assembly portável” criada para se verificar a possibilidade de se desenvolver um sistema operacional portável (Unix) que pudesse ser movido de uma arquitetura de máquina para outra sem ser necessário reescrevê-lo na linguagem assembly da nova máquina. Portanto, C foi criada, na realidade, como um efeito colateral da construção do Unix e não como uma linguagem de programação de uso geral.Comentários (em inglês)



Pelo público alvo de C ser o de programadores de sistemas operacionais em assembly, era implicito que estes programadores sabiam o que estavam fazendo e não precisavam de redes de segurança. Por exemplo, programadores assembly não precisavam que o compilador verificasse o tipo dos argumentos e seu uso, e se eles decidissem utilizar um tipo de dados de um modo diferente que o originalmente idealizado, eles certamente deviam ter uma boa razão para isso e o compilador não se poria no caminho. Assim, ter seu programa C pré-ANSI compilado era apenas o primeiro passo no longo processo de desenvolvimento de um programa sem bugs. Comentários (em inglês)



O desenvolvimento do C ANSI com regras mais rígidas sobre o que o compilador deveria aceitar, veio depois de muitas pessoas utilizarem C para outros projetos além de escrever sistemas operacionais. e, após o aparecimento do C++, que aumentou grandemente suas chances de obter um programa rodando decentemente uma vez compilado. Muito destas melhorias vieram através de verificação de tipo forte e estática (strong static type checking): “forte” porque o compilador previne que você abuse dos tipos, “estatica” porque ANSI C e C++ realizam a verificação de tipo em tempo de compilação.Comentários (em inglês)



Para muitas pessoas (inclusive eu), a melhoria foi tão dramática que parecia que a verificação de tipo forte e estática era a resposta para uma grande parte de nossos problemas. Realmente, uma das motivações para o Java era que a verificação de tipo de C++ não era forte o suficiente (principalmente porque C++ tinha que ser compatível com C, e então ficou acorrentado a suas limitações). Assim, Java deu um passo além, tomando vantagem dos benefícios da verificação de tipo, e, desde que Java possui mecanismos de verificação de linguagem que existem em tempo de execução (C++ não possui; o que foi deixado para ser feito em tempo de execução é basicamente linguagem assembly - muito rápido, mas sem auto-conhecimento), não fica restrito apenas a verificação de tipo estática.[86] Comentários (em inglês)



Parece, entretanto, que os mecanismos de verificação de linguagem pode nos levar apenas até este ponto no nosso desafio de desenvolver um programa que funciona corretamente. C++ nos proporcionou programas que funcionam muito mais cedo do que programas em C, mas frequentemente ainda apresentam problemas tais como estouro de memória e bugs súbitos e ocultos. Java avançou muito no sentido de resolver tais problemas, mesmo que ainda seja possível escrever um programa em Java com bugs estrondosos. Além do mais (apesar das incríveis reclamações sobre performance feitas sempre pelos críticos da Sun), todas as redes de segurança de Java adicionaram processamento extra aos programas, então, às vezes, nos envolvemos no desafio de conseguir que nossos programas Java rodem com a velocidade suficiente para uma necessidade específica (embora normalmente seja mais importante ter um programa que funciona do que um que rode a uma determinada velocidade). Comentários (em inglês)



Esse capítulo apresenta ferramentas para resolver problemas que o compilador não é capaz de resolver. Em sentido, não é suficiente para a criação de programas robustos, logo nós estamos usando coisas além do compilador para criar um sistema de compilação e escrita de código mais inteligente, que saiba o que um programa está e não está suposto a fazer.Comentários (em inglês)



Um dos maiores avanços é a incorporação de unidades de teste automatizadas. Isto significa escrever testes e incorporá-los no sistema construtor que compila seu código e executa os testes toda vez, como se os testes fossem parte do processo de compilação (você começará a confiar neles como se eles fossem). Para este livro, um sistema prático de teste foi desenvolvido para assegurar a correção das saídas de programa (e para mostrar as saídas diretamente na listagem do código), mas em realidade o sistema padrão de teste JUnit também será usado quando apropriado. Para ter certeza que o teste seja automático, testes são executados como parte do processo de construção usando Ant, uma ferramenta de código-aberto que também se tornou um padrão prático em desenvolvimento Java, e CVS, outra ferramenta de código-aberto que mantêm um repositório contendo todos os seus códigos para um projeto em particular. Comentários (em inglês)



JDK 1.4 introduziu um mecanismo de asserção para ajudar na verificação de código em tempo de execução. Um dos maiores incentivos ao uso das asserções é o Design by Contract (DBC), uma maneira formalizada de descrever a correção de uma classe. Em conjunto com teste automatizado, DBC pode ser uma ferramenta poderosa. Comentários (em inglês)



Algumas vezes testes individuais não são suficientes, e você precisa localizar problemas em um programa que roda, mas não roda direito. No JDK 1.4, a API logging foi introduzida para permitir que você relate facilmente informações a respeito de seu programa. Esta é uma melhoria significativa a adição e remoção de declarações println( ) em se tratando de localizar um problema, e esta seção fornecerá detalhes suficientes para lhe dar uma base completa desta API. Este capítulo também provê uma introdução a depuração, mostrando as informações como um debugger típico pode fornecê-las para ajudar você na descoberta de problemas sutís. Finalmente, você aprenderá sobre perfis e como descobrir gargalos que fazem seus programas rodar tão vagarosamente. Comentários (em inglês)

Teste individual



Uma recente realização na prática da programação é o valor dramatico do teste individual. Este é o processo de construção de testes integrados dentro de todo o código que você cria e executando estes testes todas as vezes que você faz a construção. Desta forma, o processo de construção pode verificar mais que só erros de sintaxe, pois você o ensina como verificar também erros de semântica. Linguagens de programação do estilo C, e C++ em particular, tipicamente valorizam performance mais que segurança na programação. A razão por que o desenvolvimento de programas em Java é mais rápido que em C++ (por alto duas vezes mais rápido, em muitas avaliações) é por causa que Java tem segurança de rede: artifícios como o coletor de lixo e a checagem de verificação de tipos. Pela integração do teste individual no seu processo de construção, você pode extender esta segurança de rede, resultando em desenvolvimento mais rápido. Você pode também ser ousado nas alterações que você faz, refazer mais facilmente seu código quando você descobre falhas de projeto ou implementação, e em geral produzir um produto melhor, mais rapidamente. Comentários (em inglês)



O efeito do teste individual no desenvolvimento é tão significativo que ele é usado por toda parte neste livro, não somente para validar o código no livro, mas também para exibir a saída esperada. Minha própria experiência com teste individual começou quando eu entendi que, para garantir a correção do código em um livro, todo programa naquele livro deveria ser extraído automaticamente e organizado dentro de uma árvore de código, junto com um sistema apropriado de construção. O sistema de construição usado neste livro é um Ant (descrito mais tarde neste capítulo), e depois de você o instalar, você pode apenas digitar ant para construir todo o código para o livro. O efeito da extração automática e o processo de compilação na qualidade do código do livro foi tão imediata e dramática que ele brevemente se tornou (em minha mente) um requisito para qualquer livro de programação—como pode você validar um código que você não compilou? Eu também descobri que se eu quisesse fazer mudanças extensas, eu poderia fazê-las usando localizar-e-substituir através do livro ou apenas pela adequação do código ao redor. Eu sabia que se eu introduzisse uma falha, o extrator de código e o sistema de construção forçaria a sua retirada. Comentários (em inglês)



Como os programas vão se tornando mais complexos, eu também achei que havia um sério furo em meu sistema. Estar habilitado a compilar programas com sucesso é claramente um primeiro passo importante, e para um livro publicado isso parece um passo bastante revolucionário; normalmente por causa das pressões de publicação, é bastante típico randomizar a abertura de um livro de programação e tentar descobrir uma falha de código. Contudo, eu continuo recebendo mensagens de leitores reportando problemas semânticos em meu código. Estes problemas poderiam ter sido descobertos somente pela execução do código. Naturalmente, eu entendi isto e tomei alguns prévios e hesitantes passos avante implementando um sistema que tornaria automática a execução de testes, mas eu tive de sucumbir a agenda de publicação, sabendo o tempo todo que definitivamente havia algo errado com meu processo e que voltaria a me morder na forma de relatórios embaraçosos de bugs (no mundo do código aberto ,[87] embaraço é um dos principais fatores de motivação para aumentar a qualidade do código da pessoa!). Comentários (em inglês)



O outro problema era que eu não tinha uma estrutura para o sistema de teste. Eventualmente, eu começava ouvindo sobre teste individual e JUnit, os quais provem o básico para uma estrutura de teste. Eu achei que as versões iniciais da JUnit eram intoleráveis pois elas requeriam que o programador escrevesse muito mais código para criá-lo que o mais simples teste de acompanhamento. Versões mais recentes reduziram significativamente o código necessário pelo uso da reflexão, então elas são muito mais satisfatórias. Comentários (em inglês)



Eu precisei resolver outro problema, contudo, e que era validar a saída de um programa e mostrar a saída validada no livro. Eu havia colecionado reclamações regulares de que eu não mostrei saídas suficientes do programa no livro. Imaginei que o leitor estaria executando os programas enquanto lendo o livro, e poucos leitores o fizeram e se beneficiaram com isto. Uma razão oculta para esta atitude, contudo, foi que eu não tinha maneira de testar se a saída mostrada no livro estava correta. Por experiência, eu sabia que com o passar do tempo, algo aconteceria de forma que as saídas não seriam corretas por muito tempo (ou, eu não as obteria corretas em primeiro lugar). O framework simples de teste mostrado aqui não só captura saídas de programa-e muitos programas neste livro produzem saídas de console-mas ele também compara-as com a saída esperada que é impressa no livro como parte da listagem do código fonte, assim leitores podem ver qual será a saída e também sabe que esta saída foi verifica pelo processo de construção, e que elas podem verificar a si mesmas.Comentários (em inglês)



Eu quis ver se o sistema de teste seria tanto fácil quanto simples de usar, aplicando o princípio "faça a coisa mais simples que possivelmente funcionaria", da metodologia Extreme Programming, como ponto de partida, e então evoluindo o sistema conforme as demandas de uso. (Adicionalmente, eu queria tentar reduzir o montante de código de teste em uma tentativa de impor mais funcionalidade e menos código para as apresentações de tela.) O resultado[88] é o framework simples de teste descrito a seguir. Comentários (em inglês)

Um framework simples de teste



O primeiro tento deste framework[89] é verificar a saída de exemplos neste livro. Você já tem visto linhas como esta



private static Test monitor = new Test();




no começo de muitas classes que contêm um método main( ). A tarefa do objeto monitor é interceptar e salvar uma cópia de saída padrão e erro padrão dentro de um arquivo texto. Este arquivo é então usado para verificar a saída de um programa exemplo pela comparação dos conteúdos do arquivo com a saída esperada. Comentários (em inglês)



Nós iniciamos definindo as exceções que serão lançadas por este sistema de teste. A exceção de proposta geral para a biblioteca é a classe básica para as outras. Note que ela extende RuntimeException de forma que exceções verificadas não estão envolvidas:



//: com:bruceeckel:simpletest:SimpleTestException.java
package com.bruceeckel.simpletest;

public class SimpleTestException extends RuntimeException {
  public SimpleTestException(String msg) {
    super(msg);
  }
} ///:~




Um teste básico é verificar se o número de linhas enviadas ao console pelo programa é o mesmo do número de linhas esperado:



//: com:bruceeckel:simpletest:NumOfLinesException.java
package com.bruceeckel.simpletest;

public class NumOfLinesException
extends SimpleTestException {
  public NumOfLinesException(int exp, int out) {
    super("Os números de linhas de saída e "
      + "de saída esperada não são os mesmos.\\n" +
      "esperado: <" + exp + ">\\n" +
      "saída:   <" + out + "> linhas)");
  }
} ///:~




Ou, o número de linhas pode estar correto, mas uma ou mais linhas podem não coincidir:



//: com:bruceeckel:simpletest:LineMismatchException.java
package com.bruceeckel.simpletest;
import java.io.PrintStream;

public class LineMismatchException
  extends SimpleTestException {
  public LineMismatchException(
    int lineNum, String expected, String output) {
    super("A linha " + lineNum +
      " de saída não confere com a saída esperada\\n" +
      "esperada: <" + expected + ">\\n" +
      "saída:   <" + output + ">");
  }
} ///:~




Este sistema de teste funciona pela interceptação do console de saída usando a classe TestStream para substituir os consoles padrão de saída e erro:



//: com:bruceeckel:simpletest:TestStream.java
// Utilitário simples para teste de saídas de programa. Intercepta
// System.out para imprimir ambos para o console e buffer em arquivo.
package com.bruceeckel.simpletest;
import java.io.*;
import java.util.*;
import java.util.regex.*;

public class TestStream extends PrintStream {
  protected int numOfLines;
  private PrintStream
    console = System.out,
    err = System.err,
    fout;
  // Para armazenar linhas enviadas para System.out ou err
  private InputStream stdin;
  private String className;
  public TestStream(String className) {
    super(System.out, true); // Autoflush
    System.setOut(this);
    System.setErr(this);
    stdin = System.in; // Salva para restaurar em dispose()
    // Substitue a versão padrão por uma que 
    // produz automaticamente entradas conforme solicitado:
    System.setIn(new BufferedInputStream(new InputStream(){
      char[] input = ("teste\\n").toCharArray();
      int index = 0;
      public int read() {
        return
          (int)input[index = (index + 1) % input.length];
      }
    }));
    this.className = className;
    openOutputFile();
  }
  // public PrintStream getConsole() { return console; }
  public void dispose() {
    System.setOut(console);
    System.setErr(err);
    System.setIn(stdin);
  }
  // Isto escreverá sobre o antigo arquivo Output.txt:
  public void openOutputFile() {
    try {
      fout = new PrintStream(new FileOutputStream(
        new File(className + "Output.txt")));
    } catch (FileNotFoundException e) {
      throw new RuntimeException(e);
    }
  }
  // Sobrescreve todos os possíveis métodos print/println methods para enviar do 
  // console de saída interceptado para ambos, o console e 
  // o arquivo Output.txt:
  public void print(boolean x) {
    console.print(x);
    fout.print(x);
  }
  public void println(boolean x) {
    numOfLines++;
    console.println(x);
    fout.println(x);
  }
  public void print(char x) {
    console.print(x);
    fout.print(x);
  }
  public void println(char x) {
    numOfLines++;
    console.println(x);
    fout.println(x);
  }
  public void print(int x) {
    console.print(x);
    fout.print(x);
  }
  public void println(int x) {
    numOfLines++;
    console.println(x);
    fout.println(x);
  }
  public void print(long x) {
    console.print(x);
    fout.print(x);
  }
  public void println(long x) {
    numOfLines++;
    console.println(x);
    fout.println(x);
  }
  public void print(float x) {
    console.print(x);
    fout.print(x);
  }
  public void println(float x) {
    numOfLines++;
    console.println(x);
    fout.println(x);
  }
  public void print(double x) {
    console.print(x);
    fout.print(x);
  }
  public void println(double x) {
    numOfLines++;
    console.println(x);
    fout.println(x);
  }
  public void print(char[] x) {
    console.print(x);
    fout.print(x);
  }
  public void println(char[] x) {
    numOfLines++;
    console.println(x);
    fout.println(x);
  }
  public void print(String x) {
    console.print(x);
    fout.print(x);
  }
  public void println(String x) {
    numOfLines++;
    console.println(x);
    fout.println(x);
  }
  public void print(Object x) {
    console.print(x);
    fout.print(x);
  }
  public void println(Object x) {
    numOfLines++;
    console.println(x);
    fout.println(x);
  }
  public void println() {
    if(false) console.print("println");
    numOfLines++;
    console.println();
    fout.println();
  }
  public void
  write(byte[] buffer, int offset, int length) {
    console.write(buffer, offset, length);
    fout.write(buffer, offset, length);
  }
  public void write(int b) {
    console.write(b);
    fout.write(b);
  }
} ///:~




O construtor de TestStream, após chamar o construtor da classe básica, primeiro salva referências a saída padrão e erro padrão, e então redireciona ambos os streams para o objeto TestStream. Os métodos static setOut( ) e setErr( ) ambos tomam um argumento PrintStream. As referências System.out e System.err são desconectadas de seus objetos normais e então são conectadas no objeto TestStream, então TestStream pode também ser um PrintStream (ou equivalente, alguma coisa herdada de PrintStream). A referência a saída padrão original PrintStream é capturada na refeência do console dentro de TestStream, e toda saída ao console é interceptada, e é enviada para o console original tanto quanto ao arquivo de saída. O método dispose( ) é usado para atribuir referências de retorno padrão de I/O para seus objetos originais quando TestStream tiver terminado com elas. Comentários (em inglês)



Para teste automático de exemplos que requerem entrada do usuário no console, o construtor redireciona as chamadas para o dispositivo padrão de entrada. O dispositivo padrão de entrada está armazenado em uma referência de forma que dispose( ) possa restaurá-lo a seu estado original. Usando System.setIn( ), uma classe interna anônima é configurada para dirigir qualquer pedido introduzido pelo programa sob teste. O método read( ) da classe interna produz as letras “test” seguidas de um caractere de nova linha. Comentários (em inglês)



TestStream sobrescreve uma variedade de métodos PrintStream print( ) e println( ) para cada tipo. Cada um destes métodos escreve tanto a saída “padrão” quando o arquivo de saída. O método expect( ) pode então ser usado para testar se a saída produzida pelo programa coincide com as saídas esperadas providas como argumentos por expect( ). Comentários (em inglês)



Estas ferramentas são usadas na classe Test:



//: com:bruceeckel:simpletest:Test.java
// Simple utility for testing program output. Intercepts
// System.out to print both to the console and a buffer.
package com.bruceeckel.simpletest;
import java.io.*;
import java.util.*;
import java.util.regex.*;

public class Test {
  // Bit-shifted so they can be added together:
  public static final int
    EXACT = 1 << 0, // Lines must match exactly
    AT_LEAST = 1 << 1, // Must be at least these lines
    IGNORE_ORDER = 1 << 2, // Ignore line order
    WAIT = 1 << 3; // Delay until all lines are output
  private String className;
  private TestStream testStream;
  public Test() {
    // Discover the name of the class this
    // object was created within:
    className =
      new Throwable().getStackTrace()[1].getClassName();
    testStream = new TestStream(className);
  }
  public static List fileToList(String fname) {
    ArrayList list = new ArrayList();
    try {
      BufferedReader in =
        new BufferedReader(new FileReader(fname));
      try {
        String line;
        while((line = in.readLine()) != null) {
          if(fname.endsWith(".txt"))
            list.add(line);
          else
            list.add(new TestExpression(line));
        }
      } finally {
        in.close();
      }
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    return list;
  }
  public static List arrayToList(Object[] array) {
    List l = new ArrayList();
    for(int i = 0; i < array.length; i++) {
      if(array[i] instanceof TestExpression) {
        TestExpression re = (TestExpression)array[i];
        for(int j = 0; j < re.getNumber(); j++)
          l.add(re);
      } else {
        l.add(new TestExpression(array[i].toString()));
      }
    }
    return l;
  }
  public void expect(Object[] exp, int flags) {
    if((flags & WAIT) != 0)
      while(testStream.numOfLines < exp.length) {
        try {
          Thread.sleep(1000);
        } catch (InterruptedException e) {
          throw new RuntimeException(e);
        }
      }
      List output = fileToList(className + "Output.txt");
      if((flags & IGNORE_ORDER) == IGNORE_ORDER)
        OutputVerifier.verifyIgnoreOrder(output, exp);
      else if((flags & AT_LEAST) == AT_LEAST)
        OutputVerifier.verifyAtLeast(output,
          arrayToList(exp));
      else
        OutputVerifier.verify(output, arrayToList(exp));
    // Clean up the output file - see c06:Detergent.java
    testStream.openOutputFile();
  }
  public void expect(Object[] expected) {
    expect(expected, EXACT);
  }
  public void expect(Object[] expectFirst,
    String fname, int flags) {
    List expected = fileToList(fname);
    for(int i = 0; i < expectFirst.length; i++)
      expected.add(i, expectFirst[i]);
    expect(expected.toArray(), flags);
  }
  public void expect(Object[] expectFirst, String fname) {
    expect(expectFirst, fname, EXACT);
  }
  public void expect(String fname) {
    expect(new Object[] {}, fname, EXACT);
  }
} ///:~




Há várias versões sobrecarregadas de expect( ) fornecidas por questões de conveniência (de modo que o programador possa, por exemplo, fornecer o nome do arquivo em vez de um array de linhas contendo a saída esperada). Todos estes métodos sobrecarregados chamam o método expect( ) principal, que recebe como argumento um array de Objects contendo as linhas de saída esperada e um int contendo vários flags. Os flags foram implementados utilizando deslocamento de bits, onde cada bit corresponde a um flag específico conforme definido no início de Test.java. Comentários (em inglês)



Em primeiro lugar, o método expect( ) checa o argumento flags para verificar se ele deve atrasar o processamento de modo a permitir que um programa lento execute. Ele então chama um método static fileToList( ), que converte o conteúdo do arquivo de saída criado por um programa em um List. O método fileToList( ) também encapsula cada objeto String em um objeto OutputLine; posteriormente, isto se tornará mais claro. Finalmente, o método expect( ) chama o método verify( ) apropriado baseado no argumento flags. Comentários (em inglês)



Há três verificadores: verify( ), verifyIgnoreOrder( ), e o verifyAtLeast( ), correspondendo aos modos EXACT, IGNORE_ORDER e AT_LEAST respectivamente:



//: com:bruceeckel:simpletest:OutputVerifier.java
package com.bruceeckel.simpletest;
import java.util.*;
import java.io.PrintStream;

public class OutputVerifier {
  private static void verifyLength(
    int output, int expected, int compare) {
    if((compare == Test.EXACT && expected != output)
      || (compare == Test.AT_LEAST && output < expected))
      throw new NumOfLinesException(expected, output);
  }
  public static void verify(List output, List expected) {
    verifyLength(output.size(),expected.size(),Test.EXACT);
    if(!expected.equals(output)) {
      //find the line of mismatch
      ListIterator it1 = expected.listIterator();
      ListIterator it2 = output.listIterator();
      while(it1.hasNext()
        && it2.hasNext()
        && it1.next().equals(it2.next()));
      throw new LineMismatchException(
        it1.nextIndex(), it1.previous().toString(),
        it2.previous().toString());
    }
  }
  public static void
  verifyIgnoreOrder(List output, Object[] expected) {
    verifyLength(expected.length,output.size(),Test.EXACT);
    if(!(expected instanceof String[]))
      throw new RuntimeException(
        "IGNORE_ORDER only works with String objects");
    String[] out = new String[output.size()];
    Iterator it = output.iterator();
    for(int i = 0; i < out.length; i++)
      out[i] = it.next().toString();
    Arrays.sort(out);
    Arrays.sort(expected);
    int i =0;
    if(!Arrays.equals(expected, out)) {
      while(expected[i].equals(out[i])) {i++;}
      throw new SimpleTestException(
        ((String) out[i]).compareTo(expected[i]) < 0
          ? "output: <" + out[i] + ">"
          : "expected: <" + expected[i] + ">");
    }
  }
  public static void
  verifyAtLeast(List output, List expected) {
    verifyLength(output.size(), expected.size(),
      Test.AT_LEAST);
    if(!output.containsAll(expected)) {
      ListIterator it = expected.listIterator();
      while(output.contains(it.next())) {}
      throw new SimpleTestException(
        "expected: <" + it.previous().toString() + ">");
    }
  }
} ///:~




Os métodos “verify” testam se a saída produzida por um programa corresponde à saída esperada conforme o especificado pelo modo específico. Se este não é o caso, os métodos “verify” causam uma exceção que aborta o processo de build. Comentários (em inglês)



Cada um dos métodos “verify” utilizam o verifyLength( ) para testar a quantidade de linhas de saída. O modo EXACT requer que tanto a saída quanto os arrays de saída sejam do mesmo tamanho e que cada linha de saída seja igual à linha corespondente na array de saída esperada. O IGNORE_ORDER também requer que ambos os arrays sejam do mesmo tamanho, mas a ordem real em que as linhas aparecem é desconsiderada (os dois arrays de saída devem ser permutações entre si). O modo IGNORE_ORDER é utilizado para testar os exemplos de threading onde, devido à programação não-determinística de threads pelo JVM, é possível que a seqüência de linhas de saída produzidas por um programa não possa ser prevista. O modo AT_LEAST não requer que os dois arrays sejam do mesmo tamanho, mas cada linha de saída esperada deve existir na saída real produzida por um programa, não importando a ordem. Este recurso é útil principalmente para testar exemplos de programa que contenham linhas de saída que podem ou não ser impressas, como é o caso da maioria dos exemplos que utilizam garbage collection. Observe que os três modos são canônicos; ou seja, se um teste passa no modo IGNORE_ORDER então ele passará no modo AT_LEAST se ele passa no modo EXACT ele passará nos outros dois modos. Comentários (em inglês)



Note como é simples a implementação do método “verify”. verify( ), por exemplo, simplesmente chama o método equals( ) fornecido pela classe List, e verifyAtLeast( ) chama List.containsAll( ). Lembre que as duas Listas de saída podem ambas conter objetos OutputLine ou RegularExpression. A razão para envelopar um simples objeto String em OutputLines deve agora se tornar claro; esta abordagem nos permite sobrescrever o método equals( ) , o qual é necessário para tomar vantagem da API Java Collections. Comentários (em inglês)



Objetos no array expect( ) podem ser tanto Strings ou TestExpressions, os quais podem ser encapsulados em expressões regulares (descritas no capítulo 12), que são úteis para exemplos de teste que produzem saídas randômicas. A classe TestExpression encapsula uma String representando uma expressão regular particular. Comentários (em inglês)



//: com:bruceeckel:simpletest:TestExpression.java
// Regular expression for testing program output lines
package com.bruceeckel.simpletest;
import java.util.regex.*;

public class TestExpression implements Comparable {
  private Pattern p;
  private String expression;
  private boolean isRegEx;
  // Default to only one instance of this expression:
  private int duplicates = 1;
  public TestExpression(String s) {
    this.expression = s;
    if(expression.startsWith("%% ")) {
      this.isRegEx = true;
      expression = expression.substring(3);
      this.p = Pattern.compile(expression);
    }
  }
  // For duplicate instances:
  public TestExpression(String s, int duplicates) {
    this(s);
    this.duplicates = duplicates;
  }
  public String toString() {
    if(isRegEx) return p.pattern();
    return expression;
  }
  public boolean equals(Object obj) {
    if(this == obj) return true;
    if(isRegEx) return (compareTo(obj) == 0);
    return expression.equals(obj.toString());
  }
  public int compareTo(Object obj) {
    if((isRegEx) && (p.matcher(obj.toString()).matches()))
      return 0;
    return
      expression.compareTo(obj.toString());
  }
  public int getNumber() {  return duplicates; }
  public String getExpression() { return expression;}
  public boolean isRegEx() { return isRegEx; }
} ///:~




TestExpression pode distinguir expressões regulares padrão de Strings literais. O segundo construtor permite que múltiplas linhas de expressão idênticas sejam envelopadas em um objeto único por conveniência. Comentários (em inglês)



Este sistema de teste foi razoavelmente útil, e o exercício de criá-lo e colocá-lo em uso é de um valor incomensurável. Contudo, no fim eu não fiquei satisfeiro com ele e tenho idéias que provavelmente serão implementadas na próxima edição deste livro (ou o mais breve possível). Comentários (em inglês)

JUnit



Apesar do framework de teste que acabamos de descrever permitir que a você a verificação simples e facil das saídas de programa, em alguns casos você pode querer executar funcionalidades de teste mais extensas em um programa. JUnit, disponível em www.junit.org, é um padrão emergente rápido para escrever testes repetitivos para programas Java, e fornece tanto testes simples como complexos. Comentários (em inglês)



A JUnit original foi presumivelmente baseada no JDK 1.0 e então não poderia fazer uso das facilidades de reflexão do Java. Como resultado, escrever testes de unidade com o velho JUnit era uma atividade lenta e trabalhosa, e eu achei o design desagradavel. Por causa disso, Eu escrevi o meu próprio framework de teste para Java,[90] indo ao extremo e “fazendo a coisa mais simples que pudesse possivelmente funcionar.”[91] Dese então, JUnit foi modificado e usa reflexão para maior simplicação do processo de escrita do código de teste da unidade. No entanto você ainda tem a opção de escrever o código da maneira “antiga” com conjuntos de teste e todos os outros detalhes complexos, Eu acredito que na grande maioria dos casos você pode seguir a abordagem simples mostrada aqui (e tornar sua vida muito prazerosa). Comentários (em inglês)



Na apordagem do mais simples usando JUnit, você coloca todos os seus testes em uma subclasse de TestCase. Cada teste deve ser public, não receber argumentos, retornar void, e ter um nome de método começando com a palavra “test.” As reflection de Junit identificarão estes métodos como testes individuais e os configurarão e executarão um de cada vez, tomando medidas para evitar efeitos colaterais entre os testes. Comentários (em inglês)



Tradicionalmente, o método setUp( ) cria e inicializa um conjunto comum de objetos que serão usados em todos os testes; contudo, você também pode pôr apenas toda a inicialização no construtor para a classe de teste . JUnit cria um objeto para cada teste para assegurar que não haverão efetiso colaterais entre os testes executados. Contudo, todos os objetos para todos os testes são criados no momento (ao invés da criação do objeto só antes do teste), então a única diferença entre usar setUp( ) e o construtor é que setUp( ) é chamado diretamente antes do teste. Em muitas situações isto não será problema, e você pode usar a abordagem do construtor por uma questão de simplicidade. Comentários (em inglês)



Se você precisa executar qualquer limpeza depois de cada teste (se você modificou quaisquer statics e precisa que sejam re-armazenados, abrir arquivos que precisaram ser fechados, abrir conexões de rede, etc.), você escreve um método tearDown( ). Isto também é opcional. Comentários (em inglês)



The following example uses this simple approach to create JUnit tests that exercise the standard Java ArrayList class. To trace how JUnit creates and cleans up its test objects, CountedList is inherited from ArrayList and tracking information is added: Comentários (em inglês)



//: c15:JUnitDemo.java
// Simple use of JUnit to test ArrayList
// {Depends: junit.jar}
import java.util.*;
import junit.framework.*;

// So we can see the list objects being created,
// and keep track of when they are cleaned up:
class CountedList extends ArrayList {
  private static int counter = 0;
  private int id = counter++;
  public CountedList() {
    System.out.println("CountedList #" + id);
  }
  public int getId() { return id; }
}

public class JUnitDemo extends TestCase {
  private static com.bruceeckel.simpletest.Test monitor =
    new com.bruceeckel.simpletest.Test();
  private CountedList list = new CountedList();
  // You can use the constructor instead of setUp():
  public JUnitDemo(String name) {
    super(name);
    for(int i = 0; i < 3; i++)
      list.add("" + i);
  }
  // Thus, setUp() is optional, but is run right
  // before the test:
  protected void setUp() {
    System.out.println("Set up for " + list.getId());
  }
  // tearDown() is also optional, and is called after
  // each test. setUp() and tearDown() can be either
  // protected or public:
  public void tearDown() {
    System.out.println("Tearing down " + list.getId());
  }
  // All tests have method names beginning with "test":
  public void testInsert() {
    System.out.println("Running testInsert()");
    assertEquals(list.size(), 3);
    list.add(1, "Insert");
    assertEquals(list.size(), 4);
    assertEquals(list.get(1), "Insert");
  }
  public void testReplace() {
    System.out.println("Running testReplace()");
    assertEquals(list.size(), 3);
    list.set(1, "Replace");
    assertEquals(list.size(), 3);
    assertEquals(list.get(1), "Replace");
  }
  // A "helper" method to reduce code duplication. As long
  // as the name doesn't start with "test," it will not
  // be automatically executed by JUnit.
  private void compare(ArrayList lst, String[] strs) {
    Object[] array = lst.toArray();
    assertTrue("Arrays not the same length",
      array.length == strs.length);
    for(int i = 0; i < array.length; i++)
      assertEquals(strs[i], (String)array[i]);
  }
  public void testOrder() {
    System.out.println("Running testOrder()");
    compare(list, new String[] { "0", "1", "2" });
  }
  public void testRemove() {
    System.out.println("Running testRemove()");
    assertEquals(list.size(), 3);
    list.remove(1);
    assertEquals(list.size(), 2);
    compare(list, new String[] { "0", "2" });
  }
  public void testAddAll() {
    System.out.println("Running testAddAll()");
    list.addAll(Arrays.asList(new Object[] {
      "An", "African", "Swallow"}));
    assertEquals(list.size(), 6);
    compare(list, new String[] { "0", "1", "2",
       "An", "African", "Swallow" });
  }
  public static void main(String[] args) {
    // Invoke JUnit on the class:
    junit.textui.TestRunner.run(JUnitDemo.class);
    monitor.expect(new String[] {
      "CountedList #0",
      "CountedList #1",
      "CountedList #2",
      "CountedList #3",
      "CountedList #4",
      // '.' indicates the beginning of each test:
      ".Set up for 0",
      "Running testInsert()",
      "Tearing down 0",
      ".Set up for 1",
      "Running testReplace()",
      "Tearing down 1",
      ".Set up for 2",
      "Running testOrder()",
      "Tearing down 2",
      ".Set up for 3",
      "Running testRemove()",
      "Tearing down 3",
      ".Set up for 4",
      "Running testAddAll()",
      "Tearing down 4",
      "",
      "%% Time: .*",
      "",
      "OK (5 tests)",
      "",
    });
  }
} ///:~




To set up unit testing, you must only import junit.framework.* and extend TestCase, as JUnitDemo does. In addition, you must create a constructor that takes a String argument and passes it to its super constructor. Comentários (em inglês)



For each test, a new JUnitDemo object will be created, and thus all the non-static members will also be created. This means a new CountedList object (list) will be created and initialized for each test, since it is a field of JUnitDemo. In addition, the constructor will be called for each test, so list will be initialized with the strings “0”, “1”, and “2” before each test is run. Comentários (em inglês)



To observe the behavior of setUp( ) and tearDown( ), these methods are created to display information about the test that’s being initialized or cleaned up. Note that the base-class methods are protected, so the overridden methods may be either protected or public. Comentários (em inglês)



testInsert( ) and testReplace( ) demonstrate typical test methods, since they follow the required signature and naming convention. JUnit discovers these methods using reflection and runs each one as a test. Inside the methods, you perform any desired operations and use JUnit assertion methods (which all start with the name “assert”) to verify the correctness of your tests (the full range of “assert” statements can be found in the JUnit javadocs for junit.framework.Assert). If the assertion fails, the expression and values that caused the failure will be displayed. This is usually enough, but you can also use the overloaded version of each JUnit assertion statement and include a String that will be printed if the assertion fails. Comentários (em inglês)



The assertion statements are not required; you can also just run the test without assertions and consider it a success if no exceptions are thrown. Comentários (em inglês)



The compare( ) method is an example of a “helper” method that is not executed by JUnit but instead is used by other tests in the class. As long as the method name doesn’t begin with “test,” JUnit doesn’t run it or expect it to have a particular signature. Here, compare( ) is private to emphasize that it is only used within the test class, but it could also be public. The remaining test methods eliminate duplicate code by refactoring it into the compare( ) method. Comentários (em inglês)



To execute the JUnit tests, the static method TestRunner.run( ) is invoked in main( ). This method is handed the class that contains the collection of tests, and it automatically sets up and runs all the tests. From the expect( ) output, you can see that all the objects needed to run all the tests are created first, in a batch—this is where the construction happens.[92] Before each test, the setUp( ) method is called. Then the test is run, followed by the tearDown( ) method. JUnit demarcates each test with a ‘.’. Comentários (em inglês)



Although you can probably survive easily by only using the simplest approach to JUnit as shown in the preceding example, JUnit was originally designed with a plethora of complicated structures. If you are curious, you can easily learn more about them, because the JUnit download from www.JUnit.org comes with documentation and tutorials. Comentários (em inglês)

Improving reliability with assertions



As Asserções já apareceram em exemplos anteriores neste livro. Elas foram incluidas em Java (na versão JDK 1.4) para apoiar os programadores na melhoria da confiabilidade de seus programas. Bem utilizadas, as asserções podem contribuir para a robustez ao verificar que certas condições são satisfeitas durante a execução do seu programa. Por exemplo, suponha que você tem um campo numérico em um objeto, para representar um mês do calendário Juliano. Você sabe que esse valor deve estar sempre na faixa 1-12, e pode utilizar uma asserção para conferir isso e relatar um erro se o valor cair fora dessa faixa. Se você está dentro de um método, você pode conferir a validade do argumento com uma asserção. Esse tipo de teste é importante para confirmar que seu programa está correto, mas ele não pode ser realizado pelo sistema em tempo de compilação, e ele não cai dentro dos propósitos dos testes de unidade. Nessa secção, iremos ver como funciona o mecanismo de asserção, e como utilizar asserções para implementar parcialmente o conceito de projeto por contrato Comentários (em inglês)

Sintaxe das Asserções



Uma vez que se pode simular o efeito de asserções usando outros construtos de programação, pode ser argumentado que a única vantagem de se incluir asserções em Java é que elas são fáceis de se escrever. As instruções de asserção podem ter duas formas:Comentários (em inglês)



assert expressão-booleana;
assert expressão-booleana: expressão-informativa;




Qualquer uma dessas instruções diz: "Eu posso assegurar que a expressão-booleana irá produzir um valor verdadeiro (true)". Se isso não acontecer, a asserção irá produzir uma exceção do tipo AssertionError. Ela é uma subclasse de Throwable, e como tal não requer especificação da exceção.Comentários (em inglês)



Infelizmente, a primeira forma de asserção não produz nenhuma informação contendo a expressão-booleana na asserção produzida por uma asserção que falhou (ao contrário dos mecanismos de asserção da maioria das outras linguagens). Aqui está um exemplo mostrando o uso da primeira forma: Comentários (em inglês)



//: c15:Assert1.java
// Estilo de asserção não-informativa
// Compilar com: javac -source 1.4 Assert1.java
// {JVMArgs: -ea} // Precisa rodar com -ea
// {ThrowsException}

public class Assert1 {
  public static void main(String[] args) {
    assert false;
  }
} ///:~




As asserções estão desabilitadas no JKD 1.4, por default (é chato, mas os projetistas conseguiram convencer a si mesmos que isso era uma boa idéia). Para prevenir erros em tempo de compilação, você precisa compilar com a diretiva:Comentários (em inglês)



-source 1.4




Se você não utilizar essa diretiva, irá receber uma mensagem dizendo que assert é uma palavra-chave em JDK 1.4 e não pode mais ser utilizada como um identificador.Comentários (em inglês)



Se você simplesmente rodar o programa como sempre fez, sem nenhuma diretiva especial para asserções, nada irá acontecer. Você precisa habilitar asserções quando roda seu programa. A maneira mais fácil de fazer isso é usando a diretiva -ea que você pode enunciar também como: -enableassertions. Isso irá rodar o programa e desta vez executar qualquer instrução de asserção. Nosso exemplo irá então produzir: Comentários (em inglês)



Exception in thread "main" java.lang.AssertionError
        at Assert1.main(Assert1.java:8)




Você pode notar que a saída não contem muita informação útil. Se no entanto você usar a expressão-informativa, irá produzir uma mensagem elucidativa quando a asserção falhar.Comentários (em inglês)



Para usar a segunda forma, você fornece uma expressão-informativa que será mostrada como parte da pilha de execução da exceção. Essa expressão-informativa pode produzir dados de qualquer tipo. Contudo, as expressões -informativas mais úteis tipicamente irão usar instâncias de String contendo texto esclarecedor para uso do programador. Aqui está um exemplo:Comentários (em inglês)



//: c15:Assert2.java
// Asserção com mensagem informativa
// {JVMArgs: -ea}
// {ThrowsException}

public class Assert2 {
  public static void main(String[] args) {
    assert false: "Aqui está uma mensagem dizendo o que aconteceu";;
  }
} ///:~




Agora a saída é:



Exception in thread "main" java.lang.AssertionError: Aqui está uma mensagem dizendo o que aconteceu
        at Assert2.main(Assert2.java:6)




Embora o que você veja aqui seja apenas um objeto do tipo String, a expressão-informativa pode produzir qualquer tipo de objeto, e então você tipicamente irá construir um texto mais completo, contendo por exemplo os valores dos objetos envolvidos com a asserção que falhou.Comentários (em inglês)



Como as expressões-informativas são a única maneira de obter informação útil de uma asserção que falhou, essa será a forma em uso neste livro, e a primeira forma será considerada uma escolha pobre.Comentários (em inglês)



Você pode também habilitar e desabilitar asserções baseado em nome de classe ou de pacote (isto é, você pode habilitar ou desabilitar asserções em um pacote por inteiro). Você pode encontrar os detalhes na documentação sobre asserções do JDK 1.4. Isso pode ser útil se você tem um grande projeto instrumentado com asserções e você deseja ligar ou desligar algumas delas. Contudo, o registro de andamento do programa ("logging") ou a depuração ("debugging"), técnicas que serão vistas mais adiante neste capítulo, são provavelmente melhores ferramentas para capturar esse tipo de informação. Esse livro irá sempre habilitar todas as asserções quando necessário, e assim iremos ignorar esse controle de asserções mais detalhado.Comentários (em inglês)



There’s one other way you can control assertions: programmatically, by hooking into the ClassLoader object. JDK 1.4 added several new methods to ClassLoader that allow the dynamic enabling and disabling of assertions, including setDefaultAssertionStatus( ), which sets the assertion status for all the classes loaded afterward. So you might think you could almost silently turn on all assertions like this: Comentários (em inglês)



//: c15:LoaderAssertions.java
// Using the class loader to enable assertions
// Compile with: javac -source 1.4 LoaderAssertions.java
// {ThrowsException}

public class LoaderAssertions {
  public static void main(String[] args) {
    ClassLoader.getSystemClassLoader()
      .setDefaultAssertionStatus(true);
    new Loaded().go();
  }
}

class Loaded {
  public void go() {
    assert false: "Loaded.go()";
  }
} ///:~




Although this does eliminate the need to use the -ea flag on the command line when the Java program is run, it’s not a complete solution because you must still compile everything with the -source 1.4 flag. It may be just as straightforward to enable assertions using command-line arguments; when delivering a standalone product, you probably have to set up an execution script for the user to start the program anyway, in order to configure other startup parameters. Comentários (em inglês)



It does make sense, however, to decide that you want to require assertions to be enabled when the program is run. You can accomplish this with the following static clause, placed in the main class of your system: Comentários (em inglês)



static {
  boolean assertionsEnabled = false;
  // Note intentional side effect of assignment:
  assert assertionsEnabled = true;
  if (!assertionsEnabled)
    throw new RuntimeException("Assertions disabled");
}




If assertions are enabled, then the assert statement will be executed and assertionsEnabled will be set to true. The assertion will never fail, because the return value of the assignment is the assigned value. If assertions are not enabled, the assert statement will not be executed and assertionsEnabled will remain false, resulting in the exception. Comentários (em inglês)

Using Assertions for Design by Contract



Design by ContractDesign by Contract (DBC), ou Projeto por Contrato, é um conceito desenvolvido por Bertrand Meyer, criador da linguagem de programação Eiffel, para ajudar na criação de programas robustos ao garantir que os objetos sigam certas regras que não podem ser verificadas pela verificação de tipos que ocorre em tempo de compilação. [93] Essas regras são determinadas pela natureza do problema que está sendo resolvido, o que está fora do escopo do que o compilador pode saber e testar.Comentários (em inglês)



Embora asserções não implementem diretamente DBC (como faz a linguagem Eiffel), elas podem ser usadas para criar um estilo informal de projeto por contrato.Comentários (em inglês)



A idéia fundamental de DBC é a de que existe um contrato claramente especificado entre o fornecedor de um serviço e o consumidor ou cliente desse serviço. Em programação orientada a objetos, os serviços são usualmente fornecidos pelos objetos, e a fronteira do objeto -a linha divisória entre fornecedor e consumidor- é a interface da classe do objeto. Quando os clientes invocam um método público particular, eles estão esperando um certo comportamento a partir dessa chamada: uma mudança de estado no objeto, e um valor de retorno previsível. A tese de Meyer é que: Comentários (em inglês)

  1. Esse comportamento seja claramente especificado, como se fosse um contrato.
  2. Esse comportamento possa ser garantido pela implementação de certas verificações em tempo de execução, as quais ele chama de précondições, pós-condições e invariantes.


Você pode concordar ou não que o ponto 1 seja sempre uma verdade, mas ele parece ser verdadeiro em situações suficientes para fazer de DBC uma abordagem interessante. (Eu acredito que, como qualquer solução, existem limites para sua utilidade. Mas se você conhece esses limites, você sabe quando tentar aplicá-la). Em particular, uma parte muito valiosa do processo de projeto é a expressão das restrições DBC para uma classe particular; se você não estiver apto a especificar essas restrições, você provavelmente não conhece o bastante acerca daquilo que está tentando construir.Comentários (em inglês)

Check instructions



Antes de se aprofundar nos meandros de DBC, considere a mais simples utilização das asserções, no que Meyer chama de instrução de verificação. Uma instrução de verificação expressa sua convicção de que uma propriedade particular será satisfeita nesse ponto do seu código. A idéia da instrução de verificação é expressar conclusões não-óbvias em código, não somente para ratificar o teste, mas também como documentação para futuros leitores do código.Comentários (em inglês)



Por exemplo, num processo químico, você pode estar titulando um líquido claro em outro, e quando você atinge um certo ponto, a mistura se torna azul. Isto não é óbvio a partir da cor dos dois líquidos; é parte de uma reação complexa. Uma instrução de verificação útil ao finalizar o processo de titulação iria asseverar que o líquido resultante é azul.Comentários (em inglês)



Outro exemplo é o método Thread.holdsLock( ) introduzido no JDK 1.4. Ele é usado para situações complexas de uso de threads (tais como percorrer uma coleção de maneira segura - thread-safe) onde você precisa confiar que o programador cliente ou outra classe de seu sistema está usando a biblioteca de forma adequada, ao invés de confiar tão-somente na palavra reservada synchronized. Para assegurar que o código está seguindo adequadamente os ditames de seu projeto de biblioteca, você pode usar assert para asseverar que a thread corrente tem realmente a posse da condição de bloqueio(lock).Comentários (em inglês)



assert Thread.holdsLock(this); // lock-status assertion




Instruções de verificação são um valioso aporte ao seu código. Como as asserções podem ser desabilitadas, as instruções de verificação devem ser usadas sempre que você tiver conhecimento não-óbvio acerca do estado de seu objeto ou programa. Comentários (em inglês)

Pré-condições



Uma pré-condição é um teste para ter certeza de que o cliente (o código que está invocando este método) cumpriu a sua parte do contrato. Isso quase sempre significa conferir os argumentos passados, logo no início da chamada do método (antes que você faça qualquer outra coisa nesse método) para confirmar que esses argumentos são apropriados para uso no método. Como você nunca sabe o que seu cliente vai passar pra você, conferir uma pré-condição é sempre uma boa idéia. Comentários (em inglês)

Pós-condições



Um teste de pós-condição verifica os resultados do que você fez no método. Esse código é colocado no fim da chamada de método, antes do comando return, se houver um. Para métodos longos, complexos, aonde o resultado dos cálculos deve ser verificado antes de ser enviado (isto é, em situações aonde por algum motivo você nem sempre pode confiar no resultado), os testes de pós-condição são essenciais. Mas toda vez que você puder descrever restrições sobre os resultados de um método, é prudente expressar essas restrições em código como uma pós-condição. Em Java elas são codificadas como asserções, mas os comandos assert irão variar de um método para outro.Comentários (em inglês)

Invariantes



Uma invariante fornece garantias sobre o estado do objeto que serão mantidas entre duas chamadas ao método. Contudo, ela não impede o método de discordar temporariamente dessas garantias durante a execução do método. Ela apenas diz que a informação de estado do objeto irá obedecer suas regras: Comentários (em inglês)

  1. Na entrada do método.
  2. Antes de deixar o método.


Além disso, a invariante é uma garantia acerca do estado do objeto depois de sua construção.Comentários (em inglês)



De acordo com essa descrição, uma invariante efetiva seria definida como um método, provavelmente chamado invariante( ), que seria invocado depois da construção, e no começo e no fim de cada método. O método poderia ser invocado como:



assert invariante();




Dessa maneira, se você decidir desabilitar as asserções por razões de performance, não haveria nenhum problema.Comentários (em inglês)

Relaxando DBC



Apesar de enfatizar a importância de se estar apto a expressar pré-condições, pós-condições e invariantes, e do valor de usá-las durante o desenvolvimento, Meyer admite que nem sempre é prático incluir todo código DBC no produto final a ser lançado. Você pode relaxar a verificação DBC baseado no quanto de confiança você pode atribuir ao código em um ponto particular. Aqui está a ordem de relaxamento, do mais seguro para o menos seguro: Comentários (em inglês)

  1. A verificação de invariante no começo de cada método pode ser desabilitada em primeiro lugar, já que a verificação de invariante ao fim de cada método irá garantir que o estado do objeto seja válido no início de toda chamada de método. Ou seja, você pode geralmente confiar que o estado do objeto não irá mudar entre as chamadas de método. Essa é uma suposição tão segura que você poderia optar por escrever código para verificar invariantes somente ao final.Feedback
  2. A verificação de pós-condição pode ser desabilitada a seguir, se você tiver testes de unidade cabíveis, que confirmem que seus métodos estão retornando valores apropriados. Já que a verificação de invariantes está cuidando do estado do objeto, a verificação de pós-condições está validando os resultados de cálculos durante o método, e podem então ser descartada em favor de testes de unidade. Os testes de unidade não serão tão seguros quanto verificar uma pós-condição em tempo de execução, mas eles podem ser suficientes, especialmente se você tiver bastante confiança no código.Feedback
  3. A verificação de invariante no final de uma chamada de método pode ser desabilitada se voce tiver suficiente certeza de que o corpo do método não coloca o objeto em um estado inválido. Pode ser possível verificar isso com testes de unidade tipo "caixa-branca" (isto é: testes de unidade que têm acesso aos campos privados, de tal forma que possam validar o estado do objeto). Assim, mesmo que isso não seja tão robusto quanto as chamadas da função invariante( ) é possível "migrar" a verificação da invariante, de testes em tempo de execução para testes em tempo de construção (via testes de unidade), da mesma forma que com as pós-condições.Feedback
  4. Finalmente, como um último recurso, você pode desabilitar a verificação de pré-condições. Esta é a coisa menos segura e menos aconselhável a fazer, porque mesmo que você conheça e tenha controle sobre seu próprio código, você não tem controle sobre os argumentos que o cliente pode passar a um método. Contudo, numa situação onde (a) a performance é desesperadamente necessária e a análise do perfil de execução de seu código apontou as verificações de invariante como um gargalo e (b) você tem algum tipo de garantia razoável de que o cliente não irá violar as pré-condições (como no caso em que você mesmo escreveu o código cliente) então pode ser aceitável desabilitar as verificações de pré-condições.Feedback


Quando você desabilitar as verificações, você não deve remover o código que executa as verificações descritas aqui. Se um bug for descoberto, você irá querer "religar" facilmente as verificações, para que possa descobrir rapidamente o problema.Comentários (em inglês)

Exemplo: DBC + teste de unidade "caixa-branca"



The following example demonstrates the potency of combining concepts from Design by Contract with unit testing. It shows a small first-in, first-out (FIFO) queue class that is implemented as a “circular” array—that is, an array used in a circular fashion. When the end of the array is reached, the class wraps back around to the beginning. Comentários (em inglês)



We can make a number of contractual definitions for this queue:

  1. Precondition (for a put( )): Null elements are not allowed to be added to the queue.
  2. Precondition (for a put( )): It is illegal to put elements into a full queue.
  3. Precondition (for a get( )): It is illegal to try to get elements from an empty queue.
  4. Postcondition (for a get( )): Null elements cannot be produced from the array.
  5. Invariant: The region in the array that contains objects cannot contain any null elements.
  6. Invariant: The region in the array that doesn’t contain objects must have only null values.


Here is one way you could implement these rules, using explicit method calls for each type of DBC element: Comentários (em inglês)



//: c15:Queue.java
// Demonstration of Design by Contract (DBC) combined
// with white-box unit testing.
// {Depends: junit.jar}
import junit.framework.*;
import java.util.*;

public class Queue {
  private Object[] data;
  private int
    in = 0, // Next available storage space
    out = 0; // Next gettable object
  // Has it wrapped around the circular queue?
  private boolean wrapped = false;
  public static class
  QueueException extends RuntimeException {
    public QueueException(String why) { super(why); }
  }
  public Queue(int size) {
    data = new Object[size];
    assert invariant(); // Must be true after construction
  }
  public boolean empty() {
    return !wrapped && in == out;
  }
  public boolean full() {
    return wrapped && in == out;
  }
  public void put(Object item) {
    precondition(item != null, "put() null item");
    precondition(!full(), "put() into full Queue");
    assert invariant();
    data[in++] = item;
    if(in >= data.length) {
      in = 0;
      wrapped = true;
    }
    assert invariant();
  }
  public Object get() {
    precondition(!empty(), "get() from empty Queue");
    assert invariant();
    Object returnVal = data[out];
    data[out] = null;
    out++;
    if(out >= data.length) {
      out = 0;
      wrapped = false;
    }
    assert postcondition(
      returnVal != null, "Null item in Queue");
    assert invariant();
    return returnVal;
  }
  // Design-by-contract support methods:
  private static void
  precondition(boolean cond, String msg) {
    if(!cond) throw new QueueException(msg);
  }
  private static boolean
  postcondition(boolean cond, String msg) {
    if(!cond) throw new QueueException(msg);
    return true;
  }
  private boolean invariant() {
    // Guarantee that no null values are in the
    // region of 'data' that holds objects:
    for(int i = out; i != in; i = (i + 1) % data.length)
      if(data[i] == null)
        throw new QueueException("null in queue");
    // Guarantee that only null values are outside the
    // region of 'data' that holds objects:
    if(full()) return true;
    for(int i = in; i != out; i = (i + 1) % data.length)
      if(data[i] != null)
        throw new QueueException(
          "non-null outside of queue range: " + dump());
    return true;
  }
  private String dump() {
    return "in = " + in +
      ", out = " + out +
      ", full() = " + full() +
      ", empty() = " + empty() +
      ", queue = " + Arrays.asList(data);
  }
  // JUnit testing.
  // As an inner class, this has access to privates:
  public static class WhiteBoxTest extends TestCase {
    private Queue queue = new Queue(10);
    private int i = 0;
    public WhiteBoxTest(String name) {
      super(name);
      while(i < 5) // Preload with some data
        queue.put("" + i++);
    }
    // Support methods:
    private void showFullness() {
      assertTrue(queue.full());
      assertFalse(queue.empty());
      // Dump is private, white-box testing allows access:
      System.out.println(queue.dump());
    }
    private void showEmptiness() {
      assertFalse(queue.full());
      assertTrue(queue.empty());
      System.out.println(queue.dump());
    }
    public void testFull() {
      System.out.println("testFull");
      System.out.println(queue.dump());
      System.out.println(queue.get());
      System.out.println(queue.get());
      while(!queue.full())
        queue.put("" + i++);
      String msg = "";
      try {
        queue.put("");
      } catch(QueueException e) {
        msg = e.getMessage();
        System.out.println(msg);
      }
      assertEquals(msg, "put() into full Queue");
      showFullness();
    }
    public void testEmpty() {
      System.out.println("testEmpty");
      while(!queue.empty())
        System.out.println(queue.get());
      String msg = "";
      try {
        queue.get();
      } catch(QueueException e) {
        msg = e.getMessage();
        System.out.println(msg);
      }
      assertEquals(msg, "get() from empty Queue");
      showEmptiness();
    }
    public void testNullPut() {
      System.out.println("testNullPut");
      String msg = "";
      try {
        queue.put(null);
      } catch(QueueException e) {
        msg = e.getMessage();
        System.out.println(msg);
      }
      assertEquals(msg, "put() null item");
    }
    public void testCircularity() {
      System.out.println("testCircularity");
      while(!queue.full())
        queue.put("" + i++);
      showFullness();
      // White-box testing accesses private field:
      assertTrue(queue.wrapped);
      while(!queue.empty())
        System.out.println(queue.get());
      showEmptiness();
      while(!queue.full())
        queue.put("" + i++);
      showFullness();
      while(!queue.empty())
        System.out.println(queue.get());
      showEmptiness();
    }
  }
  public static void main(String[] args) {
    junit.textui.TestRunner.run(Queue.WhiteBoxTest.class);
  }
} ///:~




The in counter indicates the place in the array where the next object will go into, and the out counter indicates where the next object will come from. The wrapped flag shows that in has gone “around the circle” and is now coming up from behind out. When in and out coincide, the queue is empty (if wrapped is false) or full (if wrapped is true). Comentários (em inglês)



You can see that the put( ) and get( ) methods call the methods precondition( ), postcondition( ), and invariant( ), which are private methods defined further down in the class. precondition( ) and postcondition( ) are helper methods designed to clarify the code. Note that precondition( ) returns void, because it is not used with assert. As previously noted, you’ll generally want to keep preconditions in your code; however, by wrapping them in a precondition( ) method call, you have better options if you are reduced to the dire move of turning them off. Comentários (em inglês)



postcondition( ) and invariant( ) return a Boolean value so that they can be used in assert statements. Then, if assertions are disabled for performance reasons, there will be no method calls at all. Comentários (em inglês)



invariant( ) performs internal validity checks on the object. You can see that this is an expensive operation to do at both the beginning and ending of every method call, as Meyer suggests. However, it’s very valuable to have this clearly represented in code, and it helped me get the implementation to be correct. In addition, if you make any changes to the implementation, the invariant( ) will ensure that you haven’t broken the code. But you can see that it would be fairly trivial to move the invariant tests from the method calls into the unit test code. If your unit tests are reasonably thorough, you can have a reasonable level of confidence that the invariants will be respected. Comentários (em inglês)



Notice that the dump( ) helper method returns a string containing all the data rather than printing the data directly. This approach allows many more options as to how the information can be used. Comentários (em inglês)



The TestCase subclass WhiteBoxTest is created as an inner class so that it has access to the private elements of Queue and is thus able to validate the underlying implementation, not just the behavior of the class as in a white-box test. The constructor adds some data so that the Queue is partially full for each test. The support methods showFullness( ) and showEmptiness( ) are meant to be called to verify that the Queue is full or empty, respectively. Each of the four test methods ensures that a different aspect of the Queue operation functions correctly. Comentários (em inglês)



Note that by combining DBC with unit testing, you not only get the best of both worlds, but you also have a migration path—you can move DBC tests to unit tests rather than simply disabling them, so you still have some level of testing. Comentários (em inglês)

Building with Ant



I began my career writing assembly-language programs that controlled real-time devices. These programs usually fit into a single file, so when I was introduced to the make utility, I wasn’t too excited, because the most complex thing I had ever needed to do was run an assembler or a C compiler on a few files of code. Back then, building a project wasn’t the difficult part of my task, and it wasn’t too cumbersome to run everything by hand. Comentários (em inglês)



Time passed, and two events occurred. First, I started to create more complex projects comprising many more files. Keeping track of which files needed compilation became more than I was able (or wanted) to think about. Second, because of this complexity I began to realize that no matter how simple the build process might be, if you do something more than a couple of times, you begin to get sloppy, and parts of the process start to fall through the cracks. Comentários (em inglês)

Automate everything



I came to realize that for a system to be built in a robust and reliable fashion, I needed to automate everything that goes into the build process. This requires some concentration up front, just like writing a program requires concentration, but the payoff is that you solve the problems once, and you rely on your build configuration to take care of the details from then on. It’s a variation of the fundamental programming principle of abstraction: We raise ourselves up from the grinding details by hiding those details inside a process and giving that process a name. For many years, the name of that process was make. Comentários (em inglês)



The make utility appeared along with C as a tool to create the Unix operating system. make’s primary function is to compare the date of two files and to perform some operation that will bring those two files up-to-date with each other. The relationships between all the files in your projects and the rules necessary to bring them up-to-date (the rule is usually running the C/C++ compiler on a source file) are contained in a makefile. The programmer creates a makefile containing the description of how to build the system. When you want to bring the system up-to-date, you simply type make at the command line. To this day, installing Unix/Linux programs consists of unpacking them and typing make commands. Comentários (em inglês)

Problems with make



The concept of make is clearly a good idea, and this idea proliferated to produce many versions of make. C and C++ compiler vendors typically included their own variation of make along with their compiler—these variations often took liberties with what people considered to be the standard makefile rules, so the resulting makefiles wouldn’t run with each other. The problem was finally solved (as has often been the case) by a make that was, and still is, superior to all the other makes, and is also free, so there’s no resistance to using it: GNU make.[94] This tool has a significantly better feature set than the other versions of make and is available on all platforms. Comentários (em inglês)



In the previous two editions of Thinking in Java, I used makefiles to build all the code in the book’s source-code tree. I automatically generated these makefiles—one in each directory, and a master makefile in the root directory that would call the rest—using a tool that I originally wrote in C++ (in about 2 weeks) for Thinking in C++, and later rewrote in Python (in about half a day) called MakeBuilder.py.[95] It worked for both Windows and Linux/Unix, but I had to write extra code to make this happen, and I never tried it on the Macintosh. Therein lies the first problem with make: You can get it to work on multiple platforms, but it’s not inherently cross-platform. So for a language that’s supposed to be “write once, run anywhere” (that is, Java), you can spend a lot of effort getting the same behavior in the build system if you use make. Comentários (em inglês)



The rest of the problems with make can probably be summarized by saying that it is like a lot of tools developed for Unix; the person creating the tool couldn’t resist the temptation to create their own language syntax, and as a result, Unix is filled with tools that are all remarkably different, and equally incomprehensible. That is to say, the make syntax is quite difficult to understand in its entirety—I’ve been learning it for years—and has lots of annoying things like its insistence on tabs instead of spaces.[96] Comentários (em inglês)



All that said, note that I still find GNU make indispensable for many of the projects I create. Comentários (em inglês)

Ant: the defacto standard



All of these issues with make irritated a Java programmer named James Duncan Davidson enough to cause him to create Ant as an open-source tool that migrated to the Apache project at http://jakarta.apache.org/ant. This site contains the full download including the Ant executable and documentation. Ant has grown and improved until it is now generally accepted as the defacto standard build tool for Java projects. Comentários (em inglês)



To make Ant cross-platform, the format for the project description files is XML (covered in Thinking in Enterprise Java). Instead of a makefile, you create a buildfile, which is named by default build.xml (this allows you to just say ‘ant’ on the command line. If you name your buildfile something else, you have to specify that name with a command-line flag). Comentários (em inglês)



The only rigid requirement for your buildfile is that it be a valid XML file. Ant compensates for platform-specific issues like end-of-line characters and directory path separators. You can use tabs or spaces in the buildfile as you prefer. In addition, the syntax and tag names used in buildfiles result in readable, understandable (and thus, maintainable) code. Comentários (em inglês)



On top of all this, Ant is designed to be extensible, with a standard interface that allows you to write your own tasks if the ones that come with Ant aren’t enough (however, they usually are, and the arsenal is regularly expanding). Comentários (em inglês)



Unlike make, the learning curve for Ant is reasonably gentle. You don’t need to know much in order to create a buildfile that compiles Java code in a directory. Here’s a very basic build.xml file, for example, from Chapter 2 of this book: Comentários (em inglês)



<?xml version="1.0"?>

<project name="Thinking in Java (c02)" 
  default="c02.run" basedir=".">
  <!-- build all classes in this directory -->
  <target name="c02.build">
    <javac
      srcdir="${basedir}"
      classpath="${basedir}/.."
      source="1.4"
    />
  </target>

  <!-- run all classes in this directory -->
  <target name="c02.run" depends="c02.build">
    <antcall target="HelloDate.run"/>
  </target>

  <target name="HelloDate.run">
    <java
      taskname="HelloDate"
      classname="HelloDate"
      classpath="${basedir};${basedir}/.."
      fork="true"
      failonerror="true"
    />
  </target>

  <!-- delete all class files -->
  <target name="clean">
    <delete>
      <fileset dir="${basedir}" includes="**/*.class"/>
      <fileset dir="${basedir}" includes="**/*Output.txt"/>
    </delete>
    <echo message="clean successful"/>
  </target>

</project>




The first line states that this file conforms to version 1.0 of XML. XML looks a lot like HTML (notice the comment syntax is identical), except that you can make up your own tag names and the format must strictly conform to XML rules. For example, an opening tag like <project must either end within the tag at its closing angle brace with a slash (/>) or have a matching closing tag like you see at the end of the file (</project>). Within a tag you can have attributes, but the attribute values must be surrounded in quotes. XML allows free formatting, but indentation like you see here is typical. Comentários (em inglês)



Each buildfile can manage a single project described by its <project> tag. The project has an optional name attribute that is used when displaying information about the build. The default attribute is required and refers to the target that is built when you just type ant at the command line without giving a specific target name. The directory reference basedir can be used in other places in the buildfile. Comentários (em inglês)



A target has dependencies and tasks. The dependencies say “which other targets must be built before this target can be built?” You’ll notice that the default target to build is c02.run, and the c02.run target says that it in turn depends on c02.build. Thus, the c02.build target must be executed before c02.run can be executed. Partitioning the buildfile this way not only makes it easier to understand, but it also allows you to choose what you want to do via the Ant command line; if you say ‘ant c02.build,’ then it will only compile the code, but if you say ‘ant co2.run’ (or, because of the default target, just ‘ant’), then it will first make sure things have been built, and then run the examples. Comentários (em inglês)



So, for the project to be successful, targets c02.build and c02.run must first succeed, in that order. The c02.build target contains a single task, which is a command that actually does the work of bringing things up-to-date. This task runs the javac compiler on all the Java files in this current base directory; notice the ${} syntax used to produce the value of a previously-defined variable, and that the orientation of slashes in directory paths is not important, since Ant compensates depending on the operating system you run it on. The classpath attribute gives a directory list to add to Ant’s classpath, and source specifies the compiler to use (this is actually only noticed by JDK 1.4 and beyond). Note that the Java compiler is responsible for sorting out the dependencies between the classes themselves, so you don’t have to explicitly state inter-file dependencies like you must with make and C/C++ (this saves a lot of effort). Comentários (em inglês)



To run the programs in the directory (which, in this case, is only the single program HelloDate), this buildfile uses a task named antcall. This task does a recursive invocation of Ant on another target, which in this case just uses java to execute the program. Note that the java task has a taskname attribute; this attribute is actually available for all tasks, and is used when Ant outputs logging information. Comentários (em inglês)



As you might expect, the java tag also has options to establish the class name to be executed, and the classpath. In addition, the



fork="true"
failonerror="true"




attributes tell Ant to fork off a new process to run this program, and to fail the Ant build if the program fails. You can look up all the different tasks and their attributes in the documentation that comes with the Ant download. Comentários (em inglês)



The last target is one that’s typically found in every buildfile; it allows you to say ant clean and delete all the files that have been created in order to perform this build. Whenever you create a buildfile, you should be careful to include a clean target, because you’re the person who typically knows the most about what can be deleted and what should be preserved. Comentários (em inglês)



The clean target introduces some new syntax. You can delete single items with the one-line version of this task, like this:



<delete file="${basedir}/HelloDate.class"/>




The multiline version of the task allows you to specify a fileset, which is a more complex description of a set of files and may specify files to include and exclude by using wildcards. In this example, the filesets to delete include all files in this directory and all subdirectories that have a .class extension, and all files in the current subdirectory that end with Output.txt. Comentários (em inglês)



The buildfile shown here is fairly simple; within this book’s source code tree (which is downloadable from www.BruceEckel.com) you’ll find more complex buildfiles. Also, Ant is capable of doing much more that what we use for this book. For the full details of its capabilities, see the documentation that comes with the Ant installation. Comentários (em inglês)

Ant extensions



Ant comes with an extension API so that you can create your own tasks by writing them in Java. You can find full details in the official Ant documentation and in the published books on Ant. Comentários (em inglês)



As an alternative, you can simply write a Java program and call it from Ant; this way, you don’t have to learn the extension API. For example, to compile the code in this book, we need to verify that the version of Java that the user is running is JDK 1.4 or greater, so we created the following program: Comentários (em inglês)



//: com:bruceeckel:tools:CheckVersion.java
// {RunByHand}
package com.bruceeckel.tools;

public class CheckVersion {
  public static void main(String[] args) {
    String version = System.getProperty("java.version");
    char minor = version.charAt(2);
    char point = version.charAt(4);
    if(minor < '4' || point < '1')
      throw new RuntimeException("JDK 1.4.1 or higher " +
        "is required to run the examples in this book.");
    System.out.println("JDK version "+ version + " found");
  }
} ///:~




This simply uses System.getProperty( ) to discover the Java version, and throws an exception if it isn’t at least 1.4. When Ant sees the exception, it will halt. Now you can include the following in any buildfile where you want to check the version number: Comentários (em inglês)



    <java
      taskname="CheckVersion"
      classname="com.bruceeckel.tools.CheckVersion"
      classpath="${basedir}"
      fork="true"
      failonerror="true"
    />




If you use this approach to adding tools, you can write them and test them quickly, and if it’s justified, you can invest the extra effort and write an Ant extension. Comentários (em inglês)

Version control with CVS



A revision control system is a class of tool that has been developed over many years to help manage large team programming projects. It has also turned out to be fundamental to the success of virtually all open-source projects, because open-source teams are almost always distributed globally via the Internet. So even if there are only two people working on a project, they benefit from using a revision control system. Comentários (em inglês)



The defacto standard revision control system for open-source projects is called Concurrent Versions System (CVS), available at www.cvshome.org. Because it is open-source and so many people know how to use it, CVS is also a common choice for closed projects. Some projects even use CVS as a way to distribute the system. CVS has the usual benefits of a popular open-source project: the code has been thoroughly reviewed, it’s available for your review and modification, and flaws are rapidly corrected. Comentários (em inglês)



CVS keeps your code in a repository on a server. This server may be on a local area network, but it is typically available on the Internet so that people on the team can get updates without being at a particular location. To connect to CVS, you must have an assigned user name and password, so there’s a reasonable level of security; for more security, you can use the ssh protocol (although these are Linux tools, they are readily available in Windows using Cygwin—see www.cygwin.com). Some graphical development environments (like the free Eclipse editor; see www.eclipse.org) provide excellent integration with CVS. Comentários (em inglês)



Once the repository is initialized by your system administrator, team members may get a copy of the code tree by checking it out. For example, once your machine is logged into the appropriate CVS server (details of which are omitted here), you can perform the initial checkout with a command like this: Comentários (em inglês)



cvs –z5 co PEJ3




This will connect with the CVS server and negotiate the checkout (‘co’) of the code repository called PEJ3. The ‘-z5’ argument tells the CVS programs at both ends to communicate using a gzip compression level of 5 in order to speed up the transfer over the network. Comentários (em inglês)



Once this command is completed, you’ll have a copy of the code repository on your local machine. In addition, you’ll see that each directory in the repository has an additional subdirectory named CVS. This is where all the CVS information about the files in that directory are stored. Comentários (em inglês)



Now that you have your own copy of the CVS repository, you can make changes to the files in order to develop the project. Typically, these changes include corrections and feature additions along with test code and modified buildfiles necessary to compile and run the tests. You’ll find that it’s very unpopular to check in code that doesn’t successfully run all its tests, because then everyone else on the team will get the broken code (and thus fail their builds). Comentários (em inglês)



When you’ve made your improvements and you’re ready to check them in, you must go through a two-step process that is the crux of CVS code synchronization. First, you update your local repository to synchronize it with the main CVS repository by moving into the root of your local code repository and running this command: Comentários (em inglês)



cvs update –dP




At this point, you aren’t required to log in because the CVS subdirectory keeps the login information for the remote repository, and the remote repository keeps signature information about your machine as a double check to verify your identity. Comentários (em inglês)



The ‘-dP’ flag is optional; ‘-d’ tells CVS to create any new directories on your local machine that might have been added to the main repository, and ‘-P’ tells CVS to prune off any directories on your local machine that have been emptied on the main repository. Neither of these things happens by default. Comentários (em inglês)



The main activity of update, however, is quite interesting. You should actually run update on a regular basis, not just before you do a checkin, because it synchronizes your local repository with the main repository. If it finds any files in the main repository that are newer than files on your local repository, it brings the changes onto your local machine. However, it doesn’t just copy the files, but instead does a line-by-line comparison of the files and patches the changes from the main repository into your local version. If you’ve made some changes to a file and someone else has made changes to the same file, CVS will patch the changes together as long as the changes don’t happen to the same lines of code (CVS matches the contents of the lines, and not just the line numbers, so even if line numbers change, it will be able to synchronize properly). Thus, you can be working on the same file as someone else, and when you do an update, any changes the other person has committed to the main repository will be merged with your changes. Comentários (em inglês)



Of course, it’s possible that two people might make changes to the same lines of the same file. This is an accident due to lack of communication; normally you’ll tell each other what you’re working on so as not to tread on each other’s code (also, if files are so big that it makes sense for two different people to work on different parts of the same file, you might consider breaking up the big files into smaller files for easier project management). If this happens, CVS simply notes the collision and forces you to resolve it by fixing the lines of code that collide. Comentários (em inglês)



Note that no files from your machine are moved into the main repository during an update. The update brings only changed files from the main repository onto your machine and patches in any modifications you’ve made. So how do your modifications get into the main repository? This is the second step: the commit. Comentários (em inglês)



When you type



cvs commit




CVS will start up your default editor and ask you to write a description of your modification. This description will be entered into the repository so that others will know what’s been changed. After that, your modified files will be placed into the main repository so they are available to everyone else the next time they do an update. Comentários (em inglês)



CVS has other capabilities, but checking out, updating, and committing are what you’ll be doing most of the time. For detailed information about CVS, books are available, and the main CVS Web site has full documentation: www.cvshome.org. In addition, you can search on the Internet using Google or other search engines; there are some very nice condensed introductions to CVS that can get you started without bogging you down with too many details (the “Gentoo Linux CVS Tutorial” by Daniel Robbins (www.gentoo.org/doc/cvs-tutorial.html) is particularly straightforward). Comentários (em inglês)

Daily builds



By incorporating compilation and testing into your buildfiles, you can follow the practice of performing daily builds, advocated by the Extreme Programming folks and others. Regardless of the number of features that you currently have implemented, you always keep your system in a state in which it can be successfully built, so that if someone performs a checkout and runs Ant, the buildfile will perform all the compilations and run all the tests without failing. Comentários (em inglês)



This is a powerful technique. It means that you always have, as a baseline, a system that compiles and passes all its tests. At any time, you can always see what the true state of the development process is by examining the features that are actually implemented in the running system. One of the timesavers of this approach is that no one has to waste time coming up with a report explaining what is going on with the system; everybody can see for themselves by checking out a current build and running the program. Comentários (em inglês)



Running builds daily, or more often, also ensures that if someone (accidentally, we presume) checks in changes that cause tests to fail, you’ll know about it in short order, before those bugs have a chance to propagate further problems in the system. Ant even has a task that will send email, because many teams set up their buildfile as a cron[97] job to automatically run daily, or even several times a day, and send email if it fails. There is also an open-source tool that automatically performs builds and provides a Web page to show the project status; see http://cruisecontrol.sourceforge.net. Comentários (em inglês)

Logging



Logging is the process of reporting information about a running program. In a debugged program, this information can be ordinary status data that describes the progress of the program (for example, if you have an installation program, you may log the steps taken during installation, the directories where you stored files, startup values for the program, etc.). Comentários (em inglês)



Logging is also very useful during debugging. Without logging, you might try to decipher the behavior of a program by inserting println( ) statements. Many examples in this book use that very technique, and in the absence of a debugger (a topic that will be introduced shortly), it’s about all you have. However, once you decide the program is working properly, you’ll probably take the println( ) statements out. Then if you run into more bugs, you may need to put them back in. It’s much nicer if you can put in some kind of output statements, which will only be used when necessary. Comentários (em inglês)



Prior to the availability of the logging API in JDK 1.4, programmers would often use a technique that relies on the fact that the Java compiler will optimize away code that will never be called. If debug is a static final boolean and you say: Comentários (em inglês)



if(debug) {
  System.out.println("Debug info");
}




then when debug is false, the compiler will completely remove the code within the braces (thus the code doesn’t cause any run-time overhead at all when it isn’t used). Using this technique, you can place trace code throughout your program and easily turn it on and off. One drawback to the technique, however, is that you must recompile your code in order to turn your trace statements on and off, whereas it’s generally more convenient to be able to turn on the trace without recompiling the program by using a configuration file that you can change to modify the logging properties. Comentários (em inglês)



The logging API in JDK 1.4 provides a more sophisticated facility to report information about your program with almost the same efficiency of the technique in the preceding example. For very simple informational logging, you can do something like this: Comentários (em inglês)



//: c15:InfoLogging.java
import com.bruceeckel.simpletest.*;
import java.util.logging.*;
import java.io.*;

public class InfoLogging {
  private static Test monitor = new Test();
  private static Logger logger =
    Logger.getLogger("InfoLogging");
  public static void main(String[] args) {
    logger.info("Logging an INFO-level message");
    monitor.expect(new String[] {
      "%% .* InfoLogging main",
      "INFO: Logging an INFO-level message"
    });
  }
} ///:~




The output during one run is:



Jul 7, 2002 6:59:46 PM InfoLogging main
INFO: Logging an INFO-level message




Notice that the logging system has detected the class name and method name from which the log message originated. It’s not guaranteed that these names will be correct, so you shouldn’t rely on their accuracy. If you want to ensure that the proper class name and method are printed, you can use a more complex method to log the message, like this: Comentários (em inglês)



//: c15:InfoLogging2.java
// Guaranteeing proper class and method names
import com.bruceeckel.simpletest.*;
import java.util.logging.*;
import java.io.*;

public class InfoLogging2 {
  private static Test monitor = new Test();
  private static Logger logger =
    Logger.getLogger("InfoLogging2");
  public static void main(String[] args) {
    logger.logp(Level.INFO, "InfoLogging2", "main",
      "Logging an INFO-level message");
    monitor.expect(new String[] {
      "%% .* InfoLogging2 main",
      "INFO: Logging an INFO-level message"
    });
  }
} ///:~




The logp( ) method takes arguments of the logging level (you’ll learn about this next), the class name and method name, and the logging string. You can see that it’s much simpler to just rely on the automatic approach if the class and method names reported during logging are not critical. Comentários (em inglês)

Logging Levels



The logging API provides multiple levels of reporting and the ability to change to a different level during program execution. Thus, you can dynamically set the logging level to any of the following states: Comentários (em inglês)

Level

Effect

Numeric Value

OFF

No logging messages are reported.

Integer.MAX_VALUE

SEVERE

Only logging messages with the level SEVERE are reported.

1000

WARNING

Logging messages with levels of WARNING and SEVERE are reported.

900

INFO

Logging messages with levels of INFO and above are reported.

800

CONFIG

Logging messages with levels of CONFIG and above are reported.

700

FINE

Logging messages with levels of FINE and above are reported.

500

FINER

Logging messages with levels of FINER and above are reported.

400

FINEST

Logging messages with levels of FINEST and above are reported.

300

ALL

All logging messages are reported.

Integer.MIN_VALUE



You can even inherit from java.util.Logging.Level (which has protected constructors) and define your own level. This could, for example, have a value of less than 300, so the level is less than FINEST. Then logging messages at your new level would not appear when the level is FINEST. Comentários (em inglês)



You can see the effect of trying out the different levels of logging in the following example: Comentários (em inglês)



//: c15:LoggingLevels.java
import com.bruceeckel.simpletest.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.Handler;
import java.util.logging.LogManager;

public class LoggingLevels {
  private static Test monitor = new Test();
  private static Logger
    lgr = Logger.getLogger("com"),
    lgr2 = Logger.getLogger("com.bruceeckel"),
    util = Logger.getLogger("com.bruceeckel.util"),
    test = Logger.getLogger("com.bruceeckel.test"),
    rand = Logger.getLogger("random");
  private static void logMessages() {
    lgr.info("com : info");
    lgr2.info("com.bruceeckel : info");
    util.info("util : info");
    test.severe("test : severe");
    rand.info("random : info");
  }
  public static void main(String[] args) {
    lgr.setLevel(Level.SEVERE);
    System.out.println("com level: SEVERE");
    logMessages();
    util.setLevel(Level.FINEST);
    test.setLevel(Level.FINEST);
    rand.setLevel(Level.FINEST);
    System.out.println("individual loggers set to FINEST");
    logMessages();
    lgr.setLevel(Level.SEVERE);
    System.out.println("com level: SEVERE");
    logMessages();
    monitor.expect("LoggingLevels.out");
  }
} ///:~




The first few lines of main( ) are necessary because the default level of logging messages that will be reported is INFO and greater (more severe). If you do not change this, then the messages of level CONFIG and below will not be reported (try taking out the lines to see this happen).Comentários (em inglês)



You can have multiple logger objects in your program, and these loggers are organized into a hierarchical tree, which can be programmatically associated with the package namespace. Child loggers keep track of their immediate parent and by default pass the logging records up to the parent. Comentários (em inglês)



The “root” logger object is always created by default, and is the base of the tree of logger objects. You get a reference to the root logger by calling the static method Logger.getLogger(""). Notice that it takes an empty string rather than no arguments. Comentários (em inglês)



Each Logger object can have one or more Handler objects associated with it. Each Handler object provides a strategy[98] for publishing the logging information, which is contained in LogRecord objects. To create a new type of Handler, you simply inherit from the Handler class and override the publish( ) method (along with flush( ) and close( ), to deal with any streams you may use in the Handler).Comentários (em inglês)



The root logger always has one associated handler by default, which sends output to the console. In order to access the handlers, you call getHandlers( ) on the Logger object. In the preceding example, we know that there’s only one handler so we don’t technically need to iterate through the list, but it’s safer to do so in general because someone else may have added other handlers to the root logger. The default level of each handler is INFO, so in order to see all the messages, we set the level to ALL (which is the same as FINEST).Comentários (em inglês)



The levels array allows easy testing of all the Level values. The logger is set to each value and all the different logging levels are attempted. In the output you can see that only messages at the currently selected logging level, and those messages that are more severe, are reported. Comentários (em inglês)

LogRecords



A LogRecord is an example of a Messenger object,[99] whose job is simply to carry information from one place to another. All the methods in the LogRecord are getters and setters. Here’s an example that dumps all the information stored in a LogRecord using the getter methods: Comentários (em inglês)



//: c15:PrintableLogRecord.java
// Override LogRecord toString()
import com.bruceeckel.simpletest.*;
import java.util.ResourceBundle;
import java.util.logging.*;

public class PrintableLogRecord extends LogRecord {
  private static Test monitor = new Test();
  public PrintableLogRecord(Level level, String str) {
    super(level, str);
  }
  public String toString() {
    String result = "Level<" + getLevel() + ">\n"
      + "LoggerName<" + getLoggerName() + ">\n"
      + "Message<" + getMessage() + ">\n"
      + "CurrentMillis<" + getMillis() + ">\n"
      + "Params";
    Object[] objParams = getParameters();
    if(objParams == null)
      result += "<null>\n";
    else
      for(int i = 0; i < objParams.length; i++)
        result += "  Param # <" + i + " value " +
          objParams[i].toString() + ">\n";
    result += "ResourceBundle<" + getResourceBundle()
      + ">\nResourceBundleName<" + getResourceBundleName()
      + ">\nSequenceNumber<" + getSequenceNumber()
      + ">\nSourceClassName<" + getSourceClassName()
      + ">\nSourceMethodName<" + getSourceMethodName()
      + ">\nThread Id<" + getThreadID()
      + ">\nThrown<" + getThrown() + ">";
    return result;
  }
  public static void main(String[] args) {
    PrintableLogRecord logRecord = new PrintableLogRecord(
      Level.FINEST, "Simple Log Record");
    System.out.println(logRecord);
    monitor.expect(new String[] {
      "Level<FINEST>",
      "LoggerName<null>",
      "Message<Simple Log Record>",
      "%% CurrentMillis<.+>",
      "Params<null>",
      "ResourceBundle<null>",
      "ResourceBundleName<null>",
      "SequenceNumber<0>",
      "SourceClassName<null>",
      "SourceMethodName<null>",
      "Thread Id<10>",
      "Thrown<null>"
    });
  }
} ///:~




PrintableLogRecord is a simple extension of LogRecord that overrides toString( ) to call all the getter methods available in LogRecord. Comentários (em inglês)

Handlers



As noted previously, you can easily create your own handler by inheriting from Handler and defining publish( ) to perform your desired operations. However, there are predefined handlers that will probably satisfy your needs without doing any extra work: Comentários (em inglês)

StreamHandler

Writes formatted records to an OutputStream

ConsoleHandler

Writes formatted records to System.err

FileHandler

Writes formatted log records either to a single file, or to a set of rotating log files

SocketHandler

Writes formatted log records to remote TCP ports

MemoryHandler

Buffers log records in memory



For example, you often want to store logging output to a file. The FileHandler makes this easy: Comentários (em inglês)



//: c15:LogToFile.java
// {Clean: LogToFile.xml,LogToFile.xml.lck}
import com.bruceeckel.simpletest.*;
import java.util.logging.*;

public class LogToFile {
  private static Test monitor = new Test();
  private static Logger logger =
    Logger.getLogger("LogToFile");
  public static void main(String[] args) throws Exception {
    logger.addHandler(new FileHandler("LogToFile.xml"));
    logger.info("A message logged to the file");
    monitor.expect(new String[] {
      "%% .* LogToFile main",
      "INFO: A message logged to the file"
    });
  }
} ///:~




When you run this program, you’ll notice two things. First, even though we’re sending output to a file, you’ll still see console output. That’s because each message is converted to a LogRecord, which is first used by the local logger object, which passes it to its own handlers. At this point the LogRecord is passed to the parent object, which has its own handlers. This process continues until the root logger is reached. The root logger comes with a default ConsoleHandler, so the message appears on the screen as well as appearing in the log file (you can turn off this behavior by calling setUseParentHandlers(false)).Comentários (em inglês)



The second thing you’ll notice is that the contents of the log file is in XML format, which will look something like this: Comentários (em inglês)



<?xml version="1.0" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
  <date>2002-07-08T12:18:17</date>
  <millis>1026152297750</millis>
  <sequence>0</sequence>
  <logger>LogToFile</logger>
  <level>INFO</level>
  <class>LogToFile</class>
  <method>main</method>
  <thread>10</thread>
  <message>A message logged to the file</message>
</record>
</log>




The default output format for a FileHandler is XML. If you want to change the format, you must attach a different Formatter object to the handler. Here, a SimpleFormatter is used for the file in order to output as plain text format: Comentários (em inglês)



//: c15:LogToFile2.java
// {Clean: LogToFile2.txt,LogToFile2.txt.lck}
import com.bruceeckel.simpletest.*;
import java.util.logging.*;

public class LogToFile2 {
  private static Test monitor = new Test();
  private static Logger logger =
    Logger.getLogger("LogToFile2");
  public static void main(String[] args) throws Exception {
    FileHandler logFile= new FileHandler("LogToFile2.txt");
    logFile.setFormatter(new SimpleFormatter());
    logger.addHandler(logFile);
    logger.info("A message logged to the file");
    monitor.expect(new String[] {
      "%% .* LogToFile2 main",
      "INFO: A message logged to the file"
    });
  }
} ///:~




The LogToFile2.txt file will look like this: Comentários (em inglês)



Jul 8, 2002 12:35:17 PM LogToFile2 main
INFO: A message logged to the file



Multiple Handlers



You can register multiple handlers with each Logger object. When a logging request comes to the Logger, it notifies all the handlers that have been registered with it,[100] as long as the logging level for the Logger is greater than or equal to that of the logging request. Each handler, in turn, has its own logging level; if the level of the LogRecord is greater than or equal to the level of the handler, then that handler publishes the record. Comentários (em inglês)



Here’s an example that adds a FileHandler and a ConsoleHandler to the Logger object: Comentários (em inglês)



//: c15:MultipleHandlers.java
// {Clean: MultipleHandlers.xml,MultipleHandlers.xml.lck}
import com.bruceeckel.simpletest.*;
import java.util.logging.*;

public class MultipleHandlers {
  private static Test monitor = new Test();
  private static Logger logger =
    Logger.getLogger("MultipleHandlers");
  public static void main(String[] args) throws Exception {
    FileHandler logFile =
      new FileHandler("MultipleHandlers.xml");
    logger.addHandler(logFile);
    logger.addHandler(new ConsoleHandler());
    logger.warning("Output to multiple handlers");
    monitor.expect(new String[] {
      "%% .* MultipleHandlers main",
      "WARNING: Output to multiple handlers",
      "%% .* MultipleHandlers main",
      "WARNING: Output to multiple handlers"
    });
  }
} ///:~




When you run the program, you’ll notice that the console output occurs twice; that’s because the root logger’s default behavior is still enabled. If you want to turn this off, make a call to setUseParentHandlers(false):



//: c15:MultipleHandlers2.java
// {Clean: MultipleHandlers2.xml,MultipleHandlers2.xml.lck}
import com.bruceeckel.simpletest.*;
import java.util.logging.*;

public class MultipleHandlers2 {
  private static Test monitor = new Test();
  private static Logger logger =
    Logger.getLogger("MultipleHandlers2");
  public static void main(String[] args) throws Exception {
    FileHandler logFile =
      new FileHandler("MultipleHandlers2.xml");
    logger.addHandler(logFile);
    logger.addHandler(new ConsoleHandler());
    logger.setUseParentHandlers(false);
    logger.warning("Output to multiple handlers");
    monitor.expect(new String[] {
      "%% .* MultipleHandlers2 main",
      "WARNING: Output to multiple handlers"
    });
  }
} ///:~




Now you’ll see only one console message. Comentários (em inglês)

Writing your own Handlers



You can easily write custom handlers by inheriting from the Handler class. To do this, you must not only implement the publish( ) method (which performs the actual reporting), but also flush( ) and close( ), which ensure that the stream used for reporting is properly cleaned up. Here’s an example that stores information from the LogRecord into another object (a List of String). At the end of the program, the object is printed to the console: Comentários (em inglês)



//: c15:CustomHandler.java
// How to write custom handler
import com.bruceeckel.simpletest.*;
import java.util.logging.*;
import java.util.*;

public class CustomHandler {
  private static Test monitor = new Test();
  private static Logger logger =
    Logger.getLogger("CustomHandler");
  private static List strHolder = new ArrayList();
  public static void main(String[] args) {
    logger.addHandler(new Handler() {
      public void publish(LogRecord logRecord) {
        strHolder.add(logRecord.getLevel() + ":");
        strHolder.add(logRecord.getSourceClassName()+":");
        strHolder.add(logRecord.getSourceMethodName()+":");
        strHolder.add("<" + logRecord.getMessage() + ">");
        strHolder.add("\n");
      }
      public void flush() {}
      public void close() {}
    });
    logger.warning("Logging Warning");
    logger.info("Logging Info");
    System.out.print(strHolder);
    monitor.expect(new String[] {
      "%% .* CustomHandler main",
      "WARNING: Logging Warning",
      "%% .* CustomHandler main",
      "INFO: Logging Info",
      "[WARNING:, CustomHandler:, main:, " +
      "<Logging Warning>, ",
      ", INFO:, CustomHandler:, main:, <Logging Info>, ",
      "]"
    });
  }
} ///:~




The console output comes from the root logger. When the ArrayList is printed, you can see that only selected information has been captured into the object. Comentários (em inglês)

Filters



When you write the code to send a logging message to a Logger object, you often decide, at the time you’re writing the code, what level the logging message should be (the logging API certainly allows you to devise more complex systems wherein the level of the message can be determined dynamically, but this is less common in practice). The Logger object has a level that can be set so that it can decide what level of message to accept; all others will be ignored. This can be thought of as a basic filtering functionality, and it’s often all you need. Comentários (em inglês)



Sometimes, however, you need more sophisticated filtering so that you can decide whether to accept or reject a message based on something more than just the current level. To accomplish this you can write custom Filter objects. Filter is an interface that has a single method, boolean isLoggable(LogRecord record), which decides whether or not this particular LogRecord is interesting enough to report. Comentários (em inglês)



Once you create a Filter, you register it with either a Logger or a Handler by using the setFilter( ) method. For example, suppose you’d like to only log reports about Ducks: Comentários (em inglês)



//: c15:SimpleFilter.java
import com.bruceeckel.simpletest.*;
import java.util.logging.*;

public class SimpleFilter {
  private static Test monitor = new Test();
  private static Logger logger =
    Logger.getLogger("SimpleFilter");
  static class Duck {};
  static class Wombat {};
  static void sendLogMessages() {
    logger.log(Level.WARNING,
      "A duck in the house!", new Duck());
    logger.log(Level.WARNING,
      "A Wombat at large!", new Wombat());
  }
  public static void main(String[] args) {
    sendLogMessages();
    logger.setFilter(new Filter() {
      public boolean isLoggable(LogRecord record) {
        Object[] params = record.getParameters();
        if(params == null)
          return true; // No parameters
        if(record.getParameters()[0] instanceof Duck)
          return true;  // Only log Ducks
        return false;
      }
    });
    logger.info("After setting filter..");
    sendLogMessages();
    monitor.expect(new String[] {
      "%% .* SimpleFilter sendLogMessages",
      "WARNING: A duck in the house!",
      "%% .* SimpleFilter sendLogMessages",
      "WARNING: A Wombat at large!",
      "%% .* SimpleFilter main",
      "INFO: After setting filter..",
      "%% .* SimpleFilter sendLogMessages",
      "WARNING: A duck in the house!"
    });
  }
} ///:~




Before setting the Filter, messages about Ducks and Wombats are reported. The Filter is created as an anonymous inner class that looks at the LogRecord parameter to see if a Duck was passed as an extra argument to the log( ) method. If so, it returns true to indicate that the message should be processed. Comentários (em inglês)



Notice that the signature of getParameters( ) says that it will return an Object[]. However, if no additional arguments have been passed to the log( ) method, getParameters( ) will return null (in violation of its signature—this is a bad programming practice). So instead of assuming that an array is returned (as promised) and checking to see if it is of zero length, we must check for null. If you don’t do this correctly, then the call to logger.info( ) will cause an exception to be thrown. Comentários (em inglês)

Formatters



A Formatter is a way to insert a formatting operation into a Handler’s processing steps. If you register a Formatter object with a Handler, then before the LogRecord is published by the Handler, it is first sent to the Formatter. After formatting, the LogRecord is returned to the Handler, which then publishes it. Comentários (em inglês)



To write a custom Formatter, extend the Formatter class and override format(LogRecord record). Then, register the Formatter with the Handler by using the setFormatter( ) call, as seen here: Comentários (em inglês)



//: c15:SimpleFormatterExample.java
import com.bruceeckel.simpletest.*;
import java.util.logging.*;
import java.util.*;

public class SimpleFormatterExample {
  private static Test monitor = new Test();
  private static Logger logger =
    Logger.getLogger("SimpleFormatterExample");
  private static void logMessages() {
    logger.info("Line One");
    logger.info("Line Two");
  }
  public static void main(String[] args) {
    logger.setUseParentHandlers(false);
    Handler conHdlr = new ConsoleHandler();
    conHdlr.setFormatter(new Formatter() {
      public String format(LogRecord record) {
        return record.getLevel()  + "  :  "
          + record.getSourceClassName()  + " -:- "
          + record.getSourceMethodName()  + " -:- "
          + record.getMessage() + "\n";
      }
    });
    logger.addHandler(conHdlr);
    logMessages();
    monitor.expect(new String[] {
      "INFO  :  SimpleFormatterExample -:- logMessages "
        + "-:- Line One",
      "INFO  :  SimpleFormatterExample -:- logMessages "
        + "-:- Line Two"
    });
  }
} ///:~




Remember that a logger like myLogger has a default handler that it gets from the parent logger (the root logger, in this case). Here, we are turning off the default handler by calling setUseParentHandlers(false), and then adding in a console handler to use instead. The new Formatter is created as an anonymous inner class in the setFormatter( ) statement. The overridden format( ) statement simply extracts some of the information from the LogRecord and formats it into a string. Comentários (em inglês)

Example: Sending email to report log messages



You can actually have one of your logging handlers send you an email so that you can be automatically notified of important problems. The following example uses the JavaMail API to develop a mail user agent to send an email. Comentários (em inglês)



The JavaMail API is a set of classes that interface to the underlying mailing protocol (IMAP, POP, SMTP). You can devise a notification mechanism on some exceptional condition in the running code by registering an additional Handler to send an email. Comentários (em inglês)



//: c15:EmailLogger.java
// {RunByHand} Must be connected to the Internet
// {Depends: mail.jar,activation.jar}
import java.util.logging.*;
import java.io.*;
import java.util.Properties;
import javax.mail.*;
import javax.mail.internet.*;

public class EmailLogger {
  private static Logger logger =
    Logger.getLogger("EmailLogger");
  public static void main(String[] args) throws Exception {
    logger.setUseParentHandlers(false);
    Handler conHdlr = new ConsoleHandler();
    conHdlr.setFormatter(new Formatter() {
      public String format(LogRecord record) {
        return record.getLevel() + "  :  "
          + record.getSourceClassName() + ":"
          + record.getSourceMethodName() + ":"
          + record.getMessage() + "\n";
      }
    });
    logger.addHandler(conHdlr);
    logger.addHandler(
      new FileHandler("EmailLoggerOutput.xml"));
    logger.addHandler(new MailingHandler());
    logger.log(Level.INFO,
      "Testing Multiple Handlers", "SendMailTrue");
  }
}

// A handler that sends mail messages
class MailingHandler extends Handler {
  public void publish(LogRecord record) {
    Object[] params = record.getParameters();
    if(params == null) return;
    // Send mail only if the parameter is true
    if(params[0].equals("SendMailTrue")) {
      new MailInfo("bruce@theunixman.com",
        new String[] { "bruce@theunixman.com" },
        "smtp.theunixman.com", "Test Subject",
        "Test Content").sendMail();
    }
  }
  public void close() {}
  public void flush() {}
}

class MailInfo {
  private String fromAddr;
  private String[] toAddr;
  private String serverAddr;
  private String subject;
  private String message;
  public MailInfo(String from, String[] to,
    String server, String subject, String message) {
    fromAddr = from;
    toAddr = to;
    serverAddr = server;
    this.subject = subject;
    this.message = message;
  }
  public void sendMail() {
    try {
      Properties prop = new Properties();
      prop.put("mail.smtp.host", serverAddr);
      Session session =
        Session.getDefaultInstance(prop, null);
      session.setDebug(true);
      // Create a message
      Message mimeMsg = new MimeMessage(session);
      // Set the from and to address
      Address addressFrom = new InternetAddress(fromAddr);
      mimeMsg.setFrom(addressFrom);
      Address[] to = new InternetAddress[toAddr.length];
      for(int i = 0; i < toAddr.length; i++)
        to[i] = new InternetAddress(toAddr[i]);
      mimeMsg.setRecipients(Message.RecipientType.TO,to);
      mimeMsg.setSubject(subject);
      mimeMsg.setText(message);
      Transport.send(mimeMsg);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
} ///:~




MailingHandler is one of the Handlers registered with the logger. To send an email, the MailingHandler uses the MailInfo object. When a logging message is sent with an additional parameter of “SendMailTrue,” the MailingHandler sends an email. Comentários (em inglês)



The MailInfo object contains the necessary state information, such as the to address, from address, and the subject information required to send an email. This state information is provided to the MailInfo object through the constructor when it is instantiated. Comentários (em inglês)



To send an email you must first establish a Session with the Simple Mail Transfer Protocol (SMTP) server. This is done by passing the address of the server inside a Properties object, in a property named mail.smtp.host. You establish a session by calling Session.getDefaultInstance( ), passing it the Properties object as the first argument. The second argument is an instance of Authenticator that may be used for authenticating the user. Passing a null value for the Authenticator argument specifies no authentication. If the debugging flag in the Properties object is set, information regarding the communication between the SMTP server and the program will be printed. Comentários (em inglês)



MimeMessage is an abstraction of an Internet email message that extends the class Message. It constructs a message that complies with the MIME (Multipurpose Internet Mail Extensions) format. A MimeMessage is constructed by passing it an instance of Session. You may set the from and to addresses by creating an instance of InternetAddress class (a subclass of Address). You send the message using the static call Transport.send( ) from the abstract class Transport. An implementation of Transport uses a specific protocol (generally SMTP) to communicate with the server to send the message. Comentários (em inglês)

Controlling Logging Levels through Namespaces



Although not mandatory, it’s advisable to give a logger the name of the class in which it is used. This allows you to manipulate the logging level of groups of loggers that reside in the same package hierarchy, at the granularity of the directory package structure. For example, you can modify all the logging levels of all the packages in com, or just the ones in com.bruceeckel, or just the ones in com.bruceeckel.util, as shown in the following example: Comentários (em inglês)



//: c15:LoggingLevelManipulation.java
import com.bruceeckel.simpletest.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.Handler;
import java.util.logging.LogManager;

public class LoggingLevelManipulation {
  private static Test monitor = new Test();
  private static Logger
    lgr = Logger.getLogger("com"),
    lgr2 = Logger.getLogger("com.bruceeckel"),
    util = Logger.getLogger("com.bruceeckel.util"),
    test = Logger.getLogger("com.bruceeckel.test"),
    rand = Logger.getLogger("random");
  static void printLogMessages(Logger logger) {
    logger.finest(logger.getName() + " Finest");
    logger.finer(logger.getName() + " Finer");
    logger.fine(logger.getName() + " Fine");
    logger.config(logger.getName() + " Config");
    logger.info(logger.getName() + " Info");
    logger.warning(logger.getName() + " Warning");
    logger.severe(logger.getName() + " Severe");
  }
  static void logMessages() {
    printLogMessages(lgr);
    printLogMessages(lgr2);
    printLogMessages(util);
    printLogMessages(test);
    printLogMessages(rand);
  }
  static void printLevels() {
    System.out.println(" -- printing levels -- "
      + lgr.getName() + " : " + lgr.getLevel()
      + " " + lgr2.getName() + " : " + lgr2.getLevel()
      + " " + util.getName() + " : " + util.getLevel()
      + " " + test.getName() + " : " + test.getLevel()
      + " " + rand.getName() + " : " + rand.getLevel());
  }
  public static void main(String[] args) {
    printLevels();
    lgr.setLevel(Level.SEVERE);
    printLevels();
    System.out.println("com level: SEVERE");
    logMessages();
    util.setLevel(Level.FINEST);
    test.setLevel(Level.FINEST);
    rand.setLevel(Level.FINEST);
    printLevels();
    System.out.println(
      "individual loggers set to FINEST");
    logMessages();
    lgr.setLevel(Level.FINEST);
    printLevels();
    System.out.println("com level: FINEST");
    logMessages();
    monitor.expect("LoggingLevelManipulation.out");
  }
} ///:~




As you can see in this code, if you pass getLogger( ) a string representing a namespace, the resulting Logger will control the severity levels of that namespace; that is, all the packages within that namespace will be affected by changes to the severity level of the logger. Comentários (em inglês)



Each Logger keeps a track of its existing ancestor Logger. If a child logger already has a logging level set, then that level is used instead of the parent's logging level. Changing the logging level of the parent does not affect the logging level of the child once the child has its own logging level. Comentários (em inglês)



Although the level of individual loggers is set to FINEST, only messages with a logging level equal to or more severe than INFO are printed because we are using the ConsoleHandler of the root logger, which is at INFO. Comentários (em inglês)



Because it isn’t in the same namespace, the logging level of random remains unaffected when the logging level of the logger com or com.bruceeckel is changed. Comentários (em inglês)

Logging Practices for Large Projects



At first glance, the Java logging API can seem rather over-engineered for most programming problems. The extra features and abilities don’t come in handy until you start building larger projects. In this section we’ll look at these features and recommended ways to use them. If you’re only using logging on smaller projects, you probably won’t need to use these features. Comentários (em inglês)

Configuration files



The following file shows how you can configure loggers in a project by using a properties file:



//:! c15:log.prop
#### Configuration File ####
# Global Params
# Handlers installed for the root logger
handlers= java.util.logging.ConsoleHandler java.util.logging.FileHandler
# Level for root logger—is used by any logger 
# that does not have its level set
.level= FINEST
# Initialization class—the public default constructor 
# of this class is called by the Logging framework
config = ConfigureLogging

# Configure FileHandler
# Logging file name - %u specifies unique
java.util.logging.FileHandler.pattern = java%g.log
# Write 100000 bytes before rotating this file
java.util.logging.FileHandler.limit = 100000
# Number of rotating files to be used
java.util.logging.FileHandler.count = 3
# Formatter to be used with this FileHandler
java.util.logging.FileHandler.formatter =   java.util.logging.SimpleFormatter

# Configure ConsoleHandler
java.util.logging.ConsoleHandler.level = FINEST
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

# Set Logger Levels #
com.level=SEVERE
com.bruceeckel.level = FINEST
com.bruceeckel.util.level = INFO
com.bruceeckel.test.level = FINER
random.level= SEVERE
///:~




The configuration file allows you to associate handlers with the root logger. The property handlers specify the comma-separated list of handlers you wish to register with the root logger. Here, we register the FileHandler and the ConsoleHandler with the root logger. The .level property species the default level for the logger. This level is used by all the loggers that are children of the root logger and do not have their own level specified. Note that, without a properties file, the default logging level of the root logger is INFO. This is because, in absence of a custom configuration file, the virtual machine uses the configuration from the JAVA_HOME\jre\lib\logging.properties file. Comentários (em inglês)

Rotating log files



The preceding configuration file generates rotating log files, which are used to prevent any log file from becoming too large. By setting the FileHandler.limit value, you give the maximum number of bytes allowed in one log file before the next one begins to fill. FileHandler.count determines the number of rotating log files to use; the configuration file shown here specifies three files. If all three files are filled to their maximum, then the first file begins to fill again, overwriting the old contents. Comentários (em inglês)



Alternatively, all the output can be put in a single file by giving a FileHandler.count value of one. (FileHandler parameters are explained in detail in the JDK documentation). Comentários (em inglês)



In order for the following program to use the preceding configuration file, you must specify the parameter java.util.logging.config.file on the command line: Comentários (em inglês)



java -Djava.util.logging.config.file=log.prop ConfigureLogging




The configuration file can only modify the root logger. If you want to add filters and handlers for other loggers, you must write the code to do it inside a Java file, as noted in the constructor: Comentários (em inglês)



//: c15:ConfigureLogging.java
// {JVMArgs: -Djava.util.logging.config.file=log.prop}
// {Clean: java0.log,java0.log.lck}
import com.bruceeckel.simpletest.*;
import java.util.logging.*;

public class ConfigureLogging {
  private static Test monitor = new Test();
  static Logger lgr = Logger.getLogger("com"),
    lgr2 = Logger.getLogger("com.bruceeckel"),
    util = Logger.getLogger("com.bruceeckel.util"),
    test = Logger.getLogger("com.bruceeckel.test"),
    rand = Logger.getLogger("random");
  public ConfigureLogging() {
    /* Set Additional formatters, Filters and Handlers for
       the loggers here. You cannot specify the Handlers
       for loggers except the root logger from the
       configuration file. */
  }
  public static void main(String[] args) {
    sendLogMessages(lgr);
    sendLogMessages(lgr2);
    sendLogMessages(util);
    sendLogMessages(test);
    sendLogMessages(rand);
    monitor.expect("ConfigureLogging.out");
  }
  private static void sendLogMessages(Logger logger) {
    System.out.println(" Logger Name : "
      + logger.getName() + " Level: " + logger.getLevel());
    logger.finest("Finest");
    logger.finer("Finer");
    logger.fine("Fine");
    logger.config("Config");
    logger.info("Info");
    logger.warning("Warning");
    logger.severe("Severe");
  }
} ///:~




The configuration will result in the output being sent to the files named java0.log, java1.log, and java2.log in the directory from which this program is executed. Comentários (em inglês)

Suggested practices



Although it’s not mandatory, you should generally consider using a logger for each class, following the standard of setting the logger name to be the same as the fully qualified name of the class. As shown earlier, this allows for finer-grained control of logging because of the ability to turn logging on and off based on namespaces. Comentários (em inglês)



If you don’t set the logging level for individual classes in that package, then the individual classes default to the logging level set for the package (assuming that you name the loggers according to their package and class). Comentários (em inglês)



If you control the logging level in a configuration file instead of changing it dynamically in your code, then you can modify logging levels without recompiling your code. Recompilation is not always an option when the system is deployed; often, only the class files are shipped to the destination environment. Comentários (em inglês)



Sometimes there is a requirement to execute some code to perform initialization activities such as adding Handlers, Filters, and Formatters to loggers. This can be achieved by setting the config property in the properties file. You can have multiple classes whose initialization can be done using the config property. These classes should be specified using space-delimited values like this: Comentários (em inglês)



config = ConfigureLogging1 ConfigureLogging2 Bar Baz




Classes specified in this fashion will have their default constructors invoked. Comentários (em inglês)

Summary



Although this has been a fairly thorough introduction to the logging API, it doesn’t include everything. For instance, we haven’t talked about the LogManager or details of the various built-in handlers, such as MemoryHandler, FileHandler, ConsoleHandler, etc. You should go to the JDK documentation for further details. Comentários (em inglês)

Debugging



Although judicious use of System.out statements or logging information can produce valuable insight into the behavior of a program,[101] for difficult problems this approach becomes cumbersome and time-consuming. In addition, you may need to peek more deeply into the program than print statements will allow. For this, you need a debugger. Comentários (em inglês)



In addition to more quickly and easily displaying information that you could produce with print statements, a debugger will also set breakpoints and then stop the program when it reaches those breakpoints. A debugger can also display the state of the program at any instant, view the values of variables that you’re interested in, step through the program line-by-line, connect to a remotely running program, and more. Especially when you start building larger systems (where bugs can easily become buried), it pays to become familiar with debuggers. Comentários (em inglês)

Debugging with JDB



The Java Debugger (JDB) is a command-line debugger that ships with the JDK. JDB is at least conceptually a descendant of the Gnu Debugger (GDB, which was inspired by the original Unix DB), in terms of the instructions for debugging and its command-line interface. JDB is useful for learning about debugging and performing simpler debugging tasks, and it’s helpful to know that it’s always available wherever the JDK is installed. However, for larger projects you’ll probably want to use a graphical debugger, described later. Comentários (em inglês)



Suppose you’ve written the following program:



//: c15:SimpleDebugging.java
// {ThrowsException}
public class SimpleDebugging {
  private static void foo1() {
    System.out.println("In foo1");
    foo2();
  }
  private static void foo2() {
    System.out.println("In foo2");
    foo3();
  }
  private static void foo3() {
    System.out.println("In foo3");
    int j = 1;
    j--;
    int i = 5 / j;
  }
  public static void main(String[] args) {
    foo1();
  }
} ///:~




If you look at foo3( ), the problem is obvious; you’re dividing by zero. But suppose this code is buried in a large program (as is implied here by the sequence of calls) and you don’t know where to start looking for the problem. As it turns out, the exception that will be thrown will give enough information for you to locate the problem (this is just one of the great things about exceptions). But let’s just suppose that the problem is more difficult than that, and that you need to drill into it more deeply and get more information than what an exception provides. Comentários (em inglês)



To run JDB, you must tell the compiler to generate debugging information by compiling SimpleDebugging.java with the –g flag. Then you start debugging the program with the command line:



jdb SimpleDebugging




This brings up JDB and gives you a command prompt. You can view the list of available JDB commands by typing ‘?’ at the prompt. Comentários (em inglês)



Here’s an interactive debugging trace that shows how to chase down a problem:



Initializing jdb ...
> catch Exception




The > indicates that JDB is waiting for a command, and the commands typed in by the user are shown in bold. The command catch Exception causes a breakpoint to be set at any point where an exception is thrown (however, the debugger will stop anyway, even if you don’t explicitly give this comment—exceptions appear to be default breakpoints in JDB). Comentários (em inglês)



Deferring exception catch Exception.
It will be set after the class is loaded.
> run




Now the program will run until the next breakpoint, which in this case is where the exception occurs. Here’s the result of the run command:



run SimpleDebugging
>
VM Started: In foo1
In foo2
In foo3
Exception occurred: java.lang.ArithmeticException (uncaught)"thread=main", SimpleDebugging.foo3(), line=18 bci=15
18        int i = 5 / j;




The program runs until line 18, where the exception generated, but JDB does not exit when it hits the exception. The debugger also displays the line of code that caused the exception. You can list the point where the execution stopped in the program source by the list command as shown here: Comentários (em inglês)



main[1] list
14      private static void foo3() {
15        System.out.println("In foo3");
16        int j = 1;
17        j--;
18 =>     int i = 5 / j;
19      }
20
21      public static void main(String[] args) {
22        foo1();
23      }




The pointer (“=>”) in this listing shows the current point from where the execution will resume. You could resume the execution by the cont (continue) command. But doing that will make JDB exit at the exception, printing the stack trace. Comentários (em inglês)



The locals command dumps the value of all the local variables:



main[1] locals
Method arguments:
Local variables:
j = 0




You can see that the value of j=0 is what caused the exception. Comentários (em inglês)



The wherei command prints the stack frames pushed in the method stack of the current thread:



main[1] wherei
[1] SimpleDebugging.foo3 (SimpleDebugging.java:18), pc = 15
[2] SimpleDebugging.foo2 (SimpleDebugging.java:11), pc = 8
[3] SimpleDebugging.foo1 (SimpleDebugging.java:6), pc = 8
[4] SimpleDebugging.main (SimpleDebugging.java:22), pc = 0




Each line after the wherei command represents a method call and the point where the call will return (which is shown by the value of the program counter pc). Here the calling sequence was main( ), foo1( ), foo2( ), and foo3( ). You can pop the stack frame pushed when the call was made to foo3( ) with the pop command: Comentários (em inglês)



main[1] pop
main[1] wherei
[1] SimpleDebugging.foo2 (SimpleDebugging.java:11), pc = 8
[2] SimpleDebugging.foo1 (SimpleDebugging.java:6), pc = 8
[3] SimpleDebugging.main (SimpleDebugging.java:22), pc = 0




You can make the JDB step through the call to foo3( ) again with the reenter command:



main[1] reenter
>
Step completed: "thread=main", SimpleDebugging.foo3(), line=15 bci=0
System.out.println("In foo3");




The list command shows us that the execution begins at the start of foo3( ):



main[1] list
11        foo3();
12      }
13
14      private static void foo3() {
15 =>     System.out.println("In foo3");
16        int j = 1;
17        j--;
18        int i = 5 / j;
19      }
20




JDB also allows you to modify the value of the local variables. The divide by zero that was caused by executing this piece of code the last time can be avoided by changing the value of j. You can do this directly in the debugger, so you can continue debugging the program without going back and changing the source file. Before you set the value of j, you will have to execute through line 25 since that is where j is declared. Comentários (em inglês)



main[1] step
> In foo3

Step completed: "thread=main", SimpleDebugging.foo3(), line=16 bci=8
16        int j = 1;

main[1] step
>
Step completed: "thread=main", SimpleDebugging.foo3(), line=17 bci=10
17        j--;

main[1] list
13
14      private static void foo3() {
15        System.out.println("In foo3");
16        int j = 1;
17 =>     j--;
18        int i = 5 / j;
19      }
20
21      public static void main(String[] args) {
22        foo1();




At this point, j is defined and you can set its value so that the exception can be avoided.



main[1] set j=6
 j=6 = 6
main[1] next
>
Step completed: "thread=main", SimpleDebugging.foo3(), line=18 bci=13
18        int i = 5 / j;
main[1] next
>
Step completed: "thread=main", SimpleDebugging.foo3(), line=19 bci=17
19      }
main[1] next
>
Step completed: "thread=main", SimpleDebugging.foo2(), line=12 bci=11
12      }
main[1] list
8
9      private static void foo2() {
10        System.out.println("In foo2");
11        foo3();
12 =>   }
13
14      private static void foo3() {
15        System.out.println("In foo3");
16        int j = 1;
17        j--; 
main[1] next
>
Step completed: "thread=main", SimpleDebugging.foo1(), line=7 bci=11
7      }
main[1] list
3    public class SimpleDebugging {
4      private static void foo1() {
5        System.out.println("In foo1");
6        foo2();
7 =>   }
8
9      private static void foo2() {
10        System.out.println("In foo2");
11        foo3();
12      } 
main[1] next
>
Step completed: "thread=main", SimpleDebugging.main(), line=23 bci=3
23      }

main[1] list
19      }
20
21      public static void main(String[] args) {
22        foo1();
23 =>   }
24    } ///:~ 
main[1] next
>
The application exited




next executes a line at a time. You can see that the exception is avoided and we can continue stepping through the program. list is used to show the position in the program from where execution will proceed. Comentários (em inglês)

Graphical debuggers



Using a command-line debugger like JDB can be inconvenient. You must use explicit commands to do things like looking at the state of the variables (locals, dump), listing the point of execution in the source code (list), finding out the threads in the system(threads), setting breakpoints (stop in, stop at), etc. A graphical debugger allows you to do all these things with a few clicks and also view the latest details of program being debugged without using explicit commands. Comentários (em inglês)



Thus, although you may want to get started by experimenting with JDB, you’ll probably find it much more productive to learn to use a graphical debugger in order to quickly track down your bugs. During the development of this edition of this book, we began using IBM’s Eclipse editor and development environment, which contains a very good graphical debugger for Java. Eclipse is well designed and implemented, and you can download it for free from www.Eclipse.org (this is a free tool, not a demo or shareware—thanks to IBM for investing the money, time, and effort to make this available to everyone). Comentários (em inglês)



Other free development tools have graphical debuggers as well, such as Sun’s Netbeans and the free version of Borland’s JBuilder. Comentários (em inglês)

Profiling and optimizing



“We should forget about small efficiencies, say about 97% of the time: Premature optimization is the root of all evil.”—Donald Knuth



Although you should always keep this quote in mind, especially when you discover yourself on the slippery slope of premature optimization, sometimes you do need to determine where your program is spending all its time, to see if you can improve the performance of those sections. Comentários (em inglês)



A profiler gathers information that allows you to see which parts of the program consume memory and which methods consume maximum time. Some profilers even allow you to disable the garbage collector to help determine patterns of memory allocation. Comentários (em inglês)



A profiler can also be a useful tool in detecting threading deadlock in your program. Comentários (em inglês)

Tracking memory consumption



Here is the kind of data a profiler can show for memory usage:

  • Number of object allocations for a specific type.
  • Places where the object allocations are taking place.
  • Methods involved in allocation of instances of this class.
  • Loitering objects: objects that are allocated, not used, and not garbage collected. These keep increasing the size of the JVM heap and represent memory leaks, which can cause an out-of-memory error or excessive overhead on the garbage collector.
  • Excessive allocation of temporary objects that increase the work of the garbage collector and thus reduce the performance of the application.
  • Failure to release instances added to a collection and not removed (this is a special case of loitering objects). Feedback

Tracking CPU usage



Profilers also keep track of how much time the CPU spends in various parts of your code. They can tell you:

  • The number of times a method was invoked.
  • The percentage of CPU time utilized by each method. If this method calls other methods, the profiler can tell you the amount of time spent in these other methods.
  • Total absolute time spent by each method, including the time it waits for I/O, locks, etc. This time depends on the available resources of the system.


This way you can decide which sections of your code need optimizing. Comentários (em inglês)

Coverage testing



Coverage testing shows the lines of code that were not executed during the test. This can draw your attention to code that is not used and might therefore be a candidate for removal or refactoring. Comentários (em inglês)



To get coverage testing information for SimpleDebugging.java, you use the command:



java –Xrunjcov:type=M SimpleDebugging




As an experiment, try putting lines of code that will not be executed into SimpleDebugging.java (you’ll have to be somewhat clever about this since the compiler can detect unreachable lines of code). Comentários (em inglês)

JVM Profiling Interface



The profiler agent communicates the events it is interested in to the JVM. The JVM profiling interface supports the following events:

  • Enter and exit a method
  • Allocate, move, and free an object
  • Create and delete a heap arena
  • Begin and end a garbage collection cycle
  • Allocate and free a JNI global reference
  • Allocate and free a JNI weak global reference
  • Load and unload a compiled method
  • Start and end a thread
  • Class file data ready for instrumentation
  • Load and unload a class
  • For a Java monitor under contention: Wait To Enter , entered, and exit
  • For a raw monitor under contention: Wait To Enter, entered, and exit
  • For an uncontended Java monitor: Wait and waited
  • Monitor Dump
  • Heap Dump
  • Object Dump
  • Request to dump or reset profiling data
  • JVM initialization and shutdown


While profiling, the JVM sends these events to the profiler agent, which then transfers the desired information to the profiler front end, which can be a process running on another machine, if desired. Comentários (em inglês)

Using HPROF



The example in this section shows how you can run the profiler that ships with the JDK. Although the information from this profiler is in the somewhat crude form of text files rather than the graphical representation that most commercial profilers produce, it still provides valuable help in determining the characteristics of your program. Comentários (em inglês)



You run the profiler by passing an extra argument to the JVM when you invoke the program. This argument must be a single string, without any spaces after the commas, like this (although it should be on a single line, it has wrapped in the book): Comentários (em inglês)



java –Xrunhprof:heap=sites,cpu=samples,depth=10,monitor=y,thread=y,doe=y ListPerformance



  • The heap=sites tells the profiler to write information about memory utilization on the heap, indicating where it was allocated.
  • cpu=samples tells the profiler to do statistical sampling to determine CPU use.
  • depth=10 indicates the depth of the trace for threads.
  • thread=y tells the profiler to identify the threads in the stack traces.
  • doe=y tells the profiler to produce dump of profiling data on exit.


The following listing contains only a portion of the file produced by HPROF. The output file is created in the current directory and is named java.hprof.txt. Comentários (em inglês)



The beginning of java.hprof.txt describes the details of the remaining sections in the file. The data produced by the profiler is in different sections; for example, TRACE represents a trace section in the file. You will see many TRACE sections, each numbered so that they can be referenced later. Comentários (em inglês)



The SITES section shows memory allocation sites. The section has several rows, sorted by the number of bytes that are allocated and are being referenced—the live bytes. The memory is listed in bytes. The column self represents the percentage of memory taken up by this site, the next column, accum, represents the cumulative memory percentage. The live bytes and live objects columns represent the number of live bytes at this site and the number of objects that were created that consumes these bytes. The allocated bytes and objects represent the total number of objects and bytes that are instantiated, including the ones being used and the ones not being used. The difference in the number of bytes listed in allocated and live represent the bytes that can be garbage collected. The trace column actually references a TRACE in the file. The first row references trace 668 as shown below. The name represents the class whose instance was created. Comentários (em inglês)



SITES BEGIN (ordered by live bytes) Thu Jul 18 11:23:06 2002
          percent         live       alloc'ed  stack class
 rank   self  accum    bytes objs   bytes objs trace name
    1 59.10% 59.10%   573488    3  573488    3   668 java.lang.Object
    2  7.41% 66.50%    71880  543   72624  559     1 [C
    3  7.39% 73.89%    71728    3   82000   10   649 java.lang.Object
    4  5.14% 79.03%    49896  232   49896  232     1 [B
    5  2.53% 81.57%    24592  310   24592  310     1 [S 

TRACE 668: (thread=1)
	java.util.Vector.ensureCapacityHelper(Vector.java:222)
	java.util.Vector.insertElementAt(Vector.java:564)
	java.util.Vector.add(Vector.java:779)
	java.util.AbstractList$ListItr.add(AbstractList.java:495)
	ListPerformance$3.test(ListPerformance.java:40)
	ListPerformance.test(ListPerformance.java:63)
	ListPerformance.main(ListPerformance.java:93)




This trace shows the method call sequence that allocates the memory. If you go through the trace as indicated by the line numbers, you will find that an object allocation takes place on line number 222 of Vector.java:



elementData = new Object[newCapacity];




This helps you discover parts of the program that use up significant amounts of memory (59.10 %, in this case). Comentários (em inglês)



Note the [C in SITE 1 represents the primitive type char. This is the internal representation of the JVM for the primitive types. Comentários (em inglês)

Thread performance



The CPU SAMPLES section shows the CPU utilization. Here is part of a trace from this section. Comentários (em inglês)



SITES END
CPU SAMPLES BEGIN (total = 514) Thu Jul 18 11:23:06 2002
rank   self  accum   count trace method
   1 28.21% 28.21%     145   662 java.util.AbstractList.iterator
   2 12.06% 40.27%      62   589 java.util.AbstractList.iterator
   3 10.12% 50.39%      52   632 java.util.LinkedList.listIterator
   4  7.00% 57.39%      36   231 java.io.FileInputStream.open
   5  5.64% 63.04%      29   605 ListPerformance$4.test
   6  3.70% 66.73%      19   636 java.util.LinkedList.addBefore




The organization of this listing is similar to the organization of the SITES listings. The rows are sorted by CPU utilization. The row on the top has the maximum CPU utilization, as indicated in the self column. The accum column lists the cumulative CPU utilization. The count field specifies the number of times this trace was active. The next two columns specify the trace number and the method that took this time. Comentários (em inglês)



Consider the first row of the CPU SAMPLES section. 28.12% of total CPU time was utilized in the method java.util.AbstractList.iterator( ), and it was called 145 times. The details of this call can be seen by looking at trace number 662: Comentários (em inglês)



TRACE 662: (thread=1)
	java.util.AbstractList.iterator(AbstractList.java:332)
	ListPerformance$2.test(ListPerformance.java:28)
	ListPerformance.test(ListPerformance.java:63)
	ListPerformance.main(ListPerformance.java:93)




You can infer that iterating through a list takes a significant amount of time. Comentários (em inglês)



For large projects it is often more helpful to have the information represented in graphical form. A number of profilers produce graphical displays, but coverage of these is beyond the scope of this book. Comentários (em inglês)

Optimization guidelines

  • Avoid sacrificing code readability for performance. Feedback
  • Performance should not be considered in isolation. Weigh the amount of effort required versus the advantage gained. Feedback
  • Performance can be a concern in big projects but is often not an issue for small projects. Feedback
  • Getting a program to work should have a higher priority than delving into the performance of the program. Once you have a working program you can use the profiler to make it more efficient. Performance should be considered during the initial design/development process only if it is determined to be a critical factor. Feedback
  • Do not make assumptions about where the bottlenecks are. Run a profiler to get the data. Feedback
  • Whenever possible try to explicitly discard an instance by setting it to null. This can sometimes be a useful hint to the garbage collector. Feedback
  • The size of the program matters. Performance optimization is generally valuable only when the size of the project is large, it runs for a long time and speed is an issue. Feedback
  • static final variables can be optimized by the JVM to improve program speed. Program constants should thus be declared as static and final. Feedback

Doclets



Although it might be a bit surprising to think of a tool that was developed for documentation support as something that helps you track down problems in your programs, doclets can be surprisingly useful. Because a doclet hooks into the javadoc parser, it has information available to that parser. With this, you can programmatically examine the class names, field names, and method signatures in your code and flag potential problems. Comentários (em inglês)



The process of producing the JDK documentation from the Java source files involves the parsing of the source file and the formatting of this parsed file by using the standard doclet. You can write a custom doclet to customize the formatting of your javadoc comments. However, doclets allow you to do far more than just formatting the comment because a doclet has available much of the information about the source file that’s being parsed. Comentários (em inglês)



You can extract information about all the members of the class: fields, constructors, methods, and the comments associated with each of the members (alas, the method code body is not available). Details about the members are encapsulated inside special objects, which contain information about the properties of the member (private, static, final etc.). This information can be helpful in detecting poorly written code, such as member variables that should be private but are public, method parameters without comments, and identifiers that do not follow naming conventions. Comentários (em inglês)



Javadoc may not catch all compilation errors. It will spot syntax errors, such as an unmatched brace, but it may not catch semantic errors. The safest approach is to run the Java compiler on your code before attempting to use a doclet-based tool. Comentários (em inglês)



The parsing mechanism provided by javadoc parses the entire source file and stores it in memory in an object of class RootDoc. The entry point for the doclet submitted to javadoc is start(RootDoc doc). It is comparable to a normal Java program’s main(String[] args). You may traverse through the RootDoc object and extract the necessary information. The following example shows how to write a simple doclet; it just prints out all the members of each class that was parsed: Comentários (em inglês)



//: c15:PrintMembersDoclet.java
// Doclet that prints out all members of the class.
import com.sun.javadoc.*;

public class PrintMembersDoclet {
  public static boolean start(RootDoc root) {
    ClassDoc[] classes = root.classes();
    processClasses(classes);
    return true;
  }
  private static void processClasses(ClassDoc[] classes) {
    for(int i = 0; i < classes.length; i++) {
      processOneClass(classes[i]);
    }
  }
  private static void processOneClass(ClassDoc cls) {
    FieldDoc[] fd = cls.fields();
    for(int i = 0; i < fd.length; i++)
      processDocElement(fd[i]);
    ConstructorDoc[] cons = cls.constructors();
    for(int i = 0; i < cons.length; i++)
      processDocElement(cons[i]);
    MethodDoc[] md = cls.methods();
    for(int i = 0; i < md.length; i++)
      processDocElement(md[i]);
  }
  private static void processDocElement(Doc dc) {
    MemberDoc md = (MemberDoc)dc;
    System.out.print(md.modifiers());
    System.out.print(" " + md.name());
    if(md.isMethod())
      System.out.println("()");
    else if(md.isConstructor())
      System.out.println();
  }
} ///:~




You can use the doclet to print the members like this:



javadoc -doclet PrintMembersDoclet -private PrintMembersDoclet.java




This invokes javadoc on the last argument in the command, which means it will parse the PrintMembersDoclet.java file. The -doclet option tells javadoc to use the custom doclet PrintMembersDoclet. The -private tag instructs javadoc to also print private members (the default is to print only protected and public members). Comentários (em inglês)



RootDoc contains a collection of ClassDoc that holds all the information about the class. Classes such as MethodDoc, FieldDoc, and ConstructorDoc contain information regarding methods, fields, and constructors, respectively. The method processOneClass( ) extracts the list of these members and prints them. Comentários (em inglês)



You can also create taglets, which allow you to implement custom javadoc tags. The JDK documentation presents an example that implements a @todo tag, which displays its text in yellow in the resulting Javadoc output. Search for “taglet” in the JDK documentation for more details. Comentários (em inglês)

Summary



This chapter introduced what I’ve come to realize may be the most essential issue in programming, superceding language syntax and design issues: How do you make sure your code is correct, and keep it that way?



Recent experience has shown that the most useful and practical tool to date is unit testing, which, as shown in this chapter, may be combined very effectively with Design by Contract. There are other types of tests as well, such as conformance testing to verify that your use cases/user stories have all been implemented. But for some reason, we have in the past relegated testing to be done later by someone else. Extreme Programming insists that the unit tests be written before the code; you create the test framework for the class, and then the class itself (on one or two occasions I’ve successfully done this, but I’m generally pleased if testing appears somewhere during the initial coding process). There remains resistance to testing, usually by those who haven’t tried it and believe they can write good code without testing. But the more experience I have, the more I repeat to myself:



If it’s not tested, it’s broken.



This a worthwhile mantra, especially when you’re thinking about cutting corners. The more of your own bugs you discover, the more attached you grow to the security of built-in tests.



Build systems (in particular, Ant) and revision control (CVS) were also introduced in this chapter because they provide structure for your project and its tests. To me, the primary goal of Extreme Programming is velocity—the ability to rapidly move your project forward (but in a reliable fashion), and to quickly refactor it when you realize that it can be improved. Velocity requires a support structure to give you confidence that things won’t fall through the cracks when you start making big changes to your project. This includes a reliable repository, which allows you to roll back to any previous version, and an automatic build system that, once configured, guarantees that the project can be compiled and tested in a single step.



Once you have reason to believe that your program is healthy, logging provides a way to monitor its pulse, and even (as shown in this chapter) to automatically email you if something starts to go wrong. When it does, debugging and profiling help you track down bugs and performance issues.



Perhaps it’s the nature of computer programming to want a single, clear, concrete answer. After all, we work with ones and zeros, which do not have fuzzy boundaries (they actually do, but the electronic engineers have gone to great lengths to give us the model we want). When it comes to solutions, it’s great to believe that there’s one answer. But I’ve found that there are boundaries to any technique, and understanding where those boundaries are is far more powerful than any single approach can be, because it allows you to use a method where its greatest strength lies, and to combine it with other approaches where it isn’t so strong. For example, in this chapter Design by Contract was presented in combination with white-box unit testing, and as I was creating the example, I discovered that the two working in concert were much more useful than either one alone.



I have found this idea to be true in more than just the issue of discovering problems, but also in building systems in the first place. For example, using a single programming language or tool to solve your problem is attractive from the standpoint of consistency, but I’ve often found that I can solve certain problems much more quickly and effectively by using the Python programming language instead of Java, to the general benefit of the project. You may also discover that Ant works in some places, and in others, make is more useful. Or, if your clients are on Windows platforms, it may be sensible to make the radical decision of using Delphi or Visual BASIC to develop client-side programs more rapidly than you could in Java. The important thing is to keep an open mind and remember that you are trying to achieve results, not necessarily use a certain tool or technique. This can be difficult, but if you remember that the project failure rate is quite high and your chances of success are proportionally low, you could be a little more open to solutions that might be more productive. One of my favorite phrases from Extreme Programming (and one I find that I violate often for usually silly reasons) is “do the simplest thing that could possibly work.” Most of the time, the simplest and most expedient approach, if you can discover it, is the best one.

Exercises

  1. Create a class containing a static clause that throws an exception if assertions are not enabled. Demonstrate that this test works correctly.
  2. Modify the preceding exercise to use the approach in LoaderAssertions.java to turn on assertions instead of throwing an exception. Demonstrate that this works correctly.
  3. In LoggingLevels.java, comment out the code that sets the severity level of the root logger handlers and verify that messages of level CONFIG and below are not reported. Feedback
  4. Inherit from java.util.Logging.Level and define your own level with a value less than FINEST. Modify LoggingLevels.java to use your new level and show that messages at your level will not appear when the logging level is FINEST.
  5. Associate a FileHandler with the root logger. Feedback
  6. Modify the FileHandler so that it formats output to a simple text file. Feedback
  7. Modify MultipleHandlers.java so that it generates output in plain text format instead of XML. Feedback
  8. Modify LoggingLevels.java to set different logging levels for the handlers associated with the root logger. Feedback
  9. Write a simple program that sets the root logger logging level based on a command-line argument. Feedback
  10. Write an example using Formatters and Handlers to output a log file as HTML. Feedback
  11. Write an example using Handlers and Filters to log messages with any severity level over INFO in one file and any severity level including and below INFO in other file. The files should be written in simple text. Feedback
  12. Modify log.prop to add an additional initialization class that initializes a custom Formatter for the logger com. Feedback
  13. Run JDB on SimpleDebugging.java, but do not give the command catch Exception. Show that it still catches the exception.
  14. Add an uninitialized reference to SimpleDebugging.java (you’ll have to do it in a way that the compiler doesn’t catch the error!) and use JDB to track down the problem.
  15. Perform the experiment described in the “Coverage Testing” section.
  16. Create a doclet that displays identifiers that might not follow the Java naming convention by checking how capital letters are used for those identifiers.



[86] It is primarily oriented to static checking, however. There is an alternative system, called latent typing or dynamic typing or weak typing, in which the type of an object is still enforced, but it is enforced at run time, when the type is used, rather than at compile time. Writing code in such a language—Python (http://www.python.org) is an excellent example—gives the programmer much more flexibility and requires far less verbiage to satisfy the compiler, and yet still guarantees that objects are used properly. However, to a programmer convinced that strong, static type checking is the only sensible solution, latent typing is anathema and serious flame wars have resulted from comparisons between the two approaches. As someone who is always in pursuit of greater productivity, I have found the value of latent typing to be very compelling. In addition, the ability to think about the issues of latent typing help you, I believe, to solve problems that are difficult to think about in strong, statically typed languages.



[87] Although the electronic version of this book is freely available, it is not open source.



[88] The first try, anyway. I find that the process of building something for the first time eventually produces insights and new ideas.



[89] Inspired by Python’s doctest module.



[90] Originally placed in Thinking in Patterns (with Java) at www.BruceEckel.com. However, with the addition of the reflection approach in JUnit, my framework doesn’t make much sense anymore and will probably be removed.



[91] A key phrase from Extreme Programming (XP). Ironically, one of the JUnit authors (Kent Beck) is also the author of Extreme Programming Explained (Addison-Wesley 2000) and a main proponent of XP.



[92] Bill Venners and I have discussed this at some length, and we haven’t been able to figure out why it is done this way rather than creating each object right before the test is run. It is likely that it is simply an artifact of the way JUnit was originally implemented.



[93] Design by contract is described in detail in Chapter 11 of Object-Oriented Software Construction, 2nd Edition, by Bertrand Meyer, Prentice Hall 1997.



[94] Except by the occasional company which, for reasons beyond comprehension, is still convinced that closed-source tools are somehow better or have superior tech support. The only situations where I’ve seen this to be true are when tools have a very small user base, but even then it would be safer to hire consultants to modify open-source tools, and thus leverage prior work and guarantee that the work you pay for won’t become unavailable to you (and also make it more likely that you’ll find other consultants already up to speed on the program).



[95] This is not available on the web site because it’s too customized to be generally useful.



[96] Other tools are under development, that attempt to repair the problems with make without making Ant’s compromises. See, for example, www.a-a-p.org or search the Web for “bjam.”



[97] Cron is a program that was developed under Unix to run programs at specified times. However, it is also available in free versions under Windows, and as a Windows NT/2000 service: http://www.kalab.com/freeware/cron/cron.htm.



[98] A pluggable algorithm. Strategies allow you to easily change one part of a solution while leaving the rest unchanged. They are often used (as in this case) as ways to allow the client programmer to provide a portion of the code needed to solve a particular problem. For more details, see Thinking in Patterns (with Java) at www.BruceEckel.com.



[99] A term coined by Bill Venners. This may or may not be a design pattern.



[100] This is the Observer design pattern (ibid).



[101] I learned C++ primarily by printing information, since at the time I was learning there were no debuggers available.


Anterior Próximo Página Inicial Índice Conteúdo