[Pesquisar este blog]

quinta-feira, 22 de fevereiro de 2018

POO::Plena-14-Objetos que usam objetos

POO-F-13-Tratamento de exceções POO-P-15-Associações
A Programação Orientada a Objetos (POO) é uma técnica de construção de sistemas onde um conjunto de objetos se inter-relacionam para produzir os resultados desejados. Todo objeto é uma ocorrência de uma classe, combinando dados e operações específicas, de maneira a definir um conjunto particular de responsabilidades.

A construção de sistemas orientados a objetos (OO) é bastante interessante, pois como os objetos têm suas responsabilidades delimitadas, é possível que as classes que os originam sejam utilizados em situações diferentes, ou seja, a OO promove a reusabilidade de código.

Outro aspecto positivo é que a manutenção do código se torna mais simples, pois, dependendo da qualidade do projeto, podem ser feitas modificações apenas nas classes relacionadas a objetos específicos, sem que isso implique em alterações em outras classes, ou seja, nas demais partes do sistema [7].

Divisão de responsabilidades

Existe um importante princípio no projeto de software que é conhecido como coesão (cohesion) ou divisão de responsabilidades (responsability division). Segundo este princípio, um módulo, uma classe ou um componente de software deve ter uma, e apenas uma, responsabilidade.

Tal responsabilidade deve ser claramente definida, de maneira que, num projeto OO, cada classe deva ser escrita, modificada e mantida para apenas um propósito, representando um tipo único de entidade ou de ator.

Quando uma classe possui muitas responsabilidades é usualmente difícil compreender seu papel, o que torna sua manutenção é mais complicada; além disso, será pouco provável seu reuso, devido a combinação peculiar de responsabilidade.

Por outro lado, quando uma classe é bem coesa, isto é, quanto é centrada numa responsabilidade única, somente uma razão específica leva à sua modificação, o que  simplifica e flexibiliza a realização de mudanças no futuro, pois provavelmente tais alterações não criarão impactos em outras entidades do sistema.

Embora possa existir alguma dificuldade em dividir as responsabilidades que surgem num problema, tal trabalho de separar é compensado pelas facilidades em evoluir.

Problema-Exemplo

Uma empresa especializada na instalação de pisos e carpetes precisa, com grande frequência, calcular a área e o perímetro dos espaços onde tais acabamentos serão instalados, pois tais informações possibilitam determinar a lista de materiais necessários, da qual deriva o orçamento e, depois, o pedido e entrega dos itens envolvidos para realização do serviço. Tais espaços, tipicamente salas e quartos, são tipicamente retangulares.

Assim, um programa simples que determina a área de tais espaços é bastante útil para agilizar o cálculo da área do piso a ser instalado e também o perímetro de rodapés. Existem outros detalhes neste serviço, que serão omitidos por serem menos importantes, de maneira que o problema seja reduzido a "como determinar a área e perímetro de uma sala retangular".

O retângulo é uma entidade do problema, o que sugere a existência de uma classe Retangulo. Um programa simples pode ser construído para obter os dados necessários para criar um objeto retângulo e, com este, determinar as informações requeridas de área e perímetro.

Então é necessário modelar uma classe Retangulo, para o cálculo de área e perímetro; e outra para o programa em si, que interage com o usuário. Isto separa as responsabilidades identificadas.

Modelagem da classe Retangulo

Uma sala ou área retangular é um retângulo, formado por quatro lados perpendiculares, onde os lados opostos têm a mesma medida: a medida maior (de dois dos lados opostos) será denominada comprimento e a medida menor (dos outro lados) de largura. Da geometria sabemos que:
  • área = comprimento * largura
  • perímetro = 2 * (comprimento + largura)
A representação mais simples de um retângulo é uma dupla de medidas (comprimento e largura), pois os seus quatro ângulos são sempre 90°. Isto indica que comprimento e largura são atributos (ou campos) desta entidade (da classe Retangulo), cujos valores são números reais (ou de tipo double) e positivos, ou seja, comprimento>0.0 e largura>0.0.

Como tais atributos tem uma restrição (são apenas positivos), mas o tipo double aceita valores positivos e negativos, devem ser privados, evitando seu acesso direto e indiscriminado. Métodos de acesso ou getters podem ser providos para que o valor de comprimento e largura de um retângulo sejam obtidos e conhecidos; assim como métodos de alteração ou setters devem ser inclusos para que seja possível modificar as medidas do retângulo, respeitando as regras dadas.

Como não é possível definir (e portanto existir) um retângulo sem estas duas medidas, o construtor da classe deve exigir dois argumentos reais para suprí-las. Não deve ser incluso um construtor default, pois não parece existir um retângulo de medida padrão.

Métodos adicionais podem calcular a área e o perímetro, sem demandar outros dados, pois apenas as medidas de comprimento e largura são necessárias. Tais métodos podem ser denominados area ou getArea, perimetro ou getPerimetro.

Esta análise resulta numa classe Java que pode ser como segue.
public class Retangulo {
// atributos
   private double comprimento, largura;

   // construtor parametrizado exige dimensões
   public Retangulo (double comprimento, double largura)
         throws RuntimeException {
      setComprimento(comprimento);
      setLargura(largura);
   }

// métodos de ajuste/mutação
   public void setComprimento (double comprimento)
 
         throws RuntimeException {
      // verifica dimensão
      if (comprimento<=0) {
         throw new RuntimeException("Comprimento invalido: "
            + comprimento);
      }
      // dimensão ok
      this.comprimento = comprimento;
   }
   public void setLargura (double largura)
         throws RuntimeException {
      // verifica dimensão
      if (largura<=0) {
         throw new RuntimeException("Largura invalida: " + largura);
      }
      // dimensão ok
      this.largura = largura;
   }

// métodos de acesso/observação
   public double getComprimento () { return comprimento; }
   public double getLargura () { return largura; }

   public double area() { return comprimento*largura; }
   public double perimetro() { return 2*(comprimento+largura); }

// método de acesso "padronizado"
   public String toString() {
      return String.format("Retangulo[C:%.2d x L:%.2d]",
            comprimento, largura);
   }
}

Um objeto do tipo Retangulo pode ser instanciado com:
Retangulo ret1 = new Retangulo(4.20, 2.70);

As medidas podem ser modificadas com os métodos setter:
ret1.setComprimento(4.30);
ret1.setLargura(2.75);

As medidas podem ser obtidas individualmente com os métodos getter:
double c = ret1.getComprimento();
double l = ret1.getLargura();

Uma String que descreve o objeto pode ser obtida com toString():
// uso direto - explícito
String desc = ret1.toString();
// uso indireto - implícito
System.out.println(ret1);

A área e o comprimento podem ser obtidas com:
double area = ret1.area();
double perim = ret1.perimetro();

Caso medidas inválidas sejam fornecidas, na instanciação ou na alteração de uma das medidas do retângulo, será lançada uma exceção do tipo RuntimeException:
// na instanciação
Retangulo ret2 = new Retangulo(-2, 3.5);
|  java.lang.RuntimeException thrown: Comprimento invalido: -2.0
|        at Retangulo.setComprimento (#1:15)
|        at Retangulo.<init> (#1:7)
|        at (#2:1)

// na alteração
ret1.setComprimento(0);
|  java.lang.RuntimeException thrown: Comprimento invalido: 0.0
|        at Retangulo.setComprimento (#1:15)
|        at (#4:1)

Com isso dispomos de uma classe Retangulo, que modela adequadamente as entidades deste tipo e cujas instâncias podem representar convenientemente salas retangulares, provendo os serviços de cálculo da área e do perímetros destas salas.

Modelagem do programa

O programa mais simples que permite resolver o problema de determinar a área e o perímetro de uma sala retangular precisa:
  1. obter as medidas da sala (comprimento e largura), solicitando-as para o usuário;
  2. criar um objeto de tipo Retangulo com as medidas dadas;
  3. obter a área e o perímetro da sala por meio do uso do objeto Retangulo;
  4. exibir os resultados (área e o perímetro da sala).
Uma classe denominada APS (Área e Perímetro da Sala) é um programa Java válido se contiver um método main(String[]), como o que segue, que realiza as ações listadas acima.
import java.util.Scanner;

public class APS {
   public static void main(String[] args) {
      // obtém medidas da sala
      Scanner teclado = new Scanner(System.in);
      System.out.print("Sala--comprimento: ");
      double comprimento = teclado.nextDouble();
      System.out.print("Sala--largura: ");
      double largura = teclado.nextDouble();

      // cria objeto Retangulo
      Retangulo sala = new Retangulo(comprimento, largura);

      // obtém área e perímetro da sala
      double area = sala.area();
      double perimetro = sala.perimetro();

      // exibe resultados
      System.out.println("Sala--area = " + area);     
      System.out.println("Sala--perimetro = " + perimetro);     

      // finalização do programa
      teclado.close();
   }
}

Quando executado, este programa produz resultados como:
>java APS
Sala--comprimento: 4,3
Sala--largura: 2,75
Sala--area = 11.825
Sala--perimetro = 14.1

Considerações finais

Embora simples, esta solução permite o cálculo da área e do perímetros de uma sala retangular. Além disso, devemos observar que:
  • A classe APS realiza a interação com o usuário, ou seja, solicita valores e exibe resultados.
  • A classe APS não tem conhecimento de como os resultados são obtidos (como são calculados), mas utiliza um objeto de outra classe (Retangulo) que é capaz de realizar tais cálculos.
  • A classe APS é como um cliente da classe Retangulo.
  • A classe Retangulo não realiza qualquer interação com usuário (não existe leitura de valores ou exibição de mensagens), possibilitando seu uso por qualquer tipo de programa (de console, GUI ou web).
  • Cada classe tem suas próprias responsabilidades: mudanças na interação com usuário não afetam a classe Retangulo; mudanças na classe Retangulo - desde que não modifiquem sua interface - não afetam seus clientes.
Esta divisão de responsabilidades acaba por criar uma organização dos elementos da aplicação, isto é, das classes componentes deste sistema que é conhecida como organização em camadas, uma arquitetura bastante conhecida e consagrada.

A figura que segue ilustra o modelo de camadas tradicional:


Considerando apenas as camadas não opcionais, temos:
  • Presentation Layer, ou Camada de Apresentação, que é responsável pela interação da aplicação com seus usuários. Esta camada contém a interface da aplicação.
  • Logic Layer, ou Camada de Lógica ou de Regras de Negócio, contém os elementos necessários para a realização das operações e transações requeridas pela aplicação.
  • Persistence Layer, ou Camada de Persistência, tem como responsabilidade prover os serviços de armazenamento e recuperação dos dados da aplicação, envolvendo bancos de dados, sistemas de arquivos e outros elementos presentes em sistemas locais, em rede, centralizados ou em nuvem.
Nosso exemplo é aderente a esta arquitetura, pois:
  • Classe APS constitui a camada de apresentação da aplicação, interagindo com o usuário na obtenção de dados e exibição de resultados.
  • Classe Retangulo faz parte da camada de lógica, pois é responsável pelas operações da aplicação (cálculo da área e do perímetro de salas retangulares).
  • Não existem elementos da camada de persistência, pois nossa aplicação não armazena dados, o que revela uma fragilidade justificável por sua simplicidade.
Um outro aspecto importante deste exemplo é que a utilização de um objeto por parte de um segundo objeto pode indicar uma associação entre tais objetos, ou seja um vínculo ou um relacionamento. Este é o assunto do próximo post!

POO-F-13-Tratamento de exceções POO-P-15-Associações

Referências Bibliográficas

[1] JAMSA, K.; KLANDER, L.. Programando em C/C++: a bíblia. São Paulo: Makron Books, 1999.
[2] PAGE_JONES, M.. Fundamentos do Desenho Orientado a Objeto com UML. São Paulo: Makron Books, 2001.
[3] SOMMERVILLE, I.. Software Engineering. 6th. Ed. Harlow: Pearson, 2001.
[4] DEITEL, H.M.; DEITEL, P.J.. Java: como programar. 6a. Ed. São Paulo: Pearson Prentice-Hall, 2005.
[5] SAVITCH, W.. C++ Absoluto. São Paulo: Pearson Addison-Wesley, 2004.
[6] JANDL JR., P. Introdução ao C++. São Paulo: Futura, 2003.
[7] JANDL JR., P.. Java - guia do programador. 3a. ed. São Paulo: Novatec, 2015.
[8] RUMBAUGH, J.; BLAHA, M.; PREMERLANI, W.; EDDY, F.; LORENSEN, W.. Object-oriented modeling and design. Englewoods Cliffs: Prentice-Hall, 1991.
[9] STROUSTRUP, B.. The C++ Programming Language. 3rd Ed. Reading: Addison-Wesley, 1997.
[10] LANGSAM, Y.; AUGENSTEIN, M. J.; TENENBAUM, A. M.. Data structures using C and C++. 2nd Ed. Upper Saddle River: Prentice-Hall, 1996.
[11] WATSON, K.; NAGEL, C.; PEDERSEN, J.H.; REID, J.D.; SKINNER, M.; WHITE, E.. Beginning Microsoft Visual C# 2008. Indianapolis: Wiley Publishing, 2008.

segunda-feira, 19 de fevereiro de 2018

POO::Fundamentos-13-Tratamento de exceções

POO-F-12-Exceções e seu tratamento POO-P-14-Objetos que usam objetos
As linguagens OO modernas utilizam as exceções para realizar o tratamento de qualquer situação anormal ou erro no código dos programas. As exceções são objetos especiais que sinalizam a ocorrência de problemas que devem ser tratados pelo programador, possibilitando a criação de código mais robusto e também seguro.

Como vimos, no Java, as exceções são objetos da família da classe java.lang.Exception, que tanto sinalizam a ocorrência de uma anormalidade, como também carregam informações adicionais sobre o problema encontrado. Embora a ocorrência das exceções, como o próprio nome indica, não seja algo normal ou desejado, são previsíveis no projeto como o recebimento de argumentos inválidos, entradas de dados inconsistentes, recursos (arquivos ou urls) não encontrados ou indisponíveis, assim como o time-out das operações executadas.

O tratamento de exceções é, portanto, uma necessidade da programação OO.

Fluxo do tratamento de exceções

O uso das exceções envolve um ciclo de tratamento simples: identificação do problema, criação, lançamento e tratamento.

Identificação do problema

Vários tipos de problemas podem ocorrer durante a execução de um problema, como um dado que não é validado ou um recurso não localizado ou indisponível. Se o problema pode ser resolvido localmente, não precisamos lançar mão do mecanismo de exceções.

Criação de exceção

Mas quando o problema detectado não pode ser resolvido no mesmo local (no mesmo método ou contexto onde foi identificado), deve ser criada uma exceção, do tipo adequado, eventualmente com informações adicionais do problema. A criação de uma exceção é uma operação de instanciação, que envolve o operador new e a classe de exceção selecionada [4][7]. As exceções criadas podem ser do tipo monitorado (checked exception) ou não monitorado (unchecked exception).


Lançamento de exceção

De posse de um objeto de exceção de qualquer tipo, é possível lançar tal exceção (to throw an exception), com uso da diretiva throw. Isto provoca a entrega do objeto de exceção lançado por throw no trecho de código do contexto superior (método que invocou a execução do método onde o problema foi encontrado) [4][7].

Tratamento de exceção

Nos locais do código onde podem ser lançadas exceções não monitoradas (unchecked exceptions), o programador pode optar pelo seu tratamento local (no contexto de sua ocorrência), pela indicação explícita de seu lançamento (para o contexto superior), ou mesmo não fazer nada (que é prática ruim).

Aqui vale lembrar que caso uma exceção, de qualquer tipo, alcance o nível da JVM, que corresponde ao contexto mais alto, pois ela acionou o programa, a execução é interrompida imediatamente e sinalizada para o usuário [7].

Quando são lançadas exceções monitoradas (checked exceptions), o programador deve optar ou pelo seu tratamento local, ou pela indicação explícita de seu lançamento para o contexto superior. Assim as exceções monitoradas exigem melhores práticas de programação!

Se a opção for o lançamento explícito, o método onde a exceção ocorre deverá empregar uma cláusula throw para indicar o lançamento da exceção, como visto no post anterior Exceções e seu lançamento.

Se a opção for o tratamento local, será necessário apanhar a exceção (to catch an exception) com uso de diretivas try associadas com cláusulas catch ou finally, como segue.


Tratamento de Exceções

Para tratarmos as exceções usamos a diretiva try, que em sua forma mais simples, emprega cláusulas catch, com a sintaxe abaixo:
try {
   // bloco de código monitorado
:
} catch (Exception e) {
   // bloco de código de tratamento
:
}

Cada par de chaves { e } delimita um bloco de código.

Aquele que segue a diretiva try é considerado o bloco de código monitorado, no qual se localizam as instruções do programa (acionamento de métodos ou outras operações) onde pode ocorrer uma ou mais exceções.

O outro bloco, que segue a cláusula catch, é o bloco de tratamento de exceções, onde as exceções de um tipo específico são apanhadas e tratadas com o código ali presente.

As exceções lançadas no bloco de código não monitorado que não apanhadas pelo bloco de tratamento de exceções seguem o fluxo usual para o contexto superior.

Se não ocorre qualquer exceção no bloco de código monitorado (bloco try), o bloco catch não é executado, ou seja, a execução sem exceções do programa desvia do bloco catch.

Mas quando ocorre uma exceção no bloco try, a execução é imediatamente desviada para o bloco catch que trata o tipo específico da exceção.

Em ambos os casos a execução continua após a diretiva try/catch, exceto quando são executadas operações de retorno, ou ocorre o encerramento do programa, ou ainda novas exceções são lançadas.

Observe o programa Java que segue, onde o método main converte o primeiro argumento recebido, de String para inteiro, exibindo uma contagem simples.
public class Contagem {
   public static void main (String args[]) {
      int max = Integer.parseInt(args[0]);
      for (int i=0; i<max; i++) {
         System.out.print(i + " ");
      }
      System.out.println();
   }
}

Neste programa, se o usuário não fornece um argumento ou fornece um argumento inválido (que não corresponde a um valor inteiro), o programa é interrompido, com a exibição de várias mensagens que podem confundir o usuário.

Por exemplo, execução do programa Contagem sem argumentos:
>java Contagem
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
        at Contagem.main(Contagem.java:3)

Ou, outro exemplo, execução do programa Contagem com um argumento inválido:
>java Contagem Jandl
Exception in thread "main" java.lang.NumberFormatException: For input string: "Jandl"
        at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
        at java.base/java.lang.Integer.parseInt(Integer.java:652)
        at java.base/java.lang.Integer.parseInt(Integer.java:770)
        at Contagem.main(Contagem.java:3)

Neste exemplo, se o usuário não fornece um argumento válido na linha de comando, podem ocorrer dois tipos de exceção na linha:
int max = Integer.parseInt(args[0]);

Se não existe argumento (nada é digitado além do nome da classe), ocorre a exceção: ArrayIndexOutOfBoundsException, que mostra que tentamos usar uma posição do array cujo índice não é válido (no caso o índice 0 que corresponde ao primeiro argumento.

Se o argumento existe mas é inválido (não corresponde a um valor inteiro), ocorre a exceção: NumberFormatException, que indica que a conversão realizada não pode ser concluída devido ao argumento incorreto fornecido (no caso o valor que o usuário digitou).

Para resolver o problema, a linha onde ocorre a exceção deve ser substituída por um trecho que inclua o tratamento de erro. Ou seja, deveríamos substituir:
int max = Integer.parseInt(args[0]);

Pelo trecho dado a seguir:
int max = 0;
try {
   max = Integer.parseInt(args[0]);
} catch (Exception e) {
   // tratamento da exceção
   :
}

Com isso, uma opção melhor para obtermos um programa mais robusto e adequado para o usuário seria como segue:
public class Contagem {
   public static void main (String args[]) {
      int max = 0; // use zero ou outro valor default
   // código que ESTÁ sujeito a exceções
      try {
         max = Integer.parseInt(args[0]);
      } catch (Exception e) {
   // tratamento da exceção
         System.out.println("É necessário um argumento inteiro válido!");
         System.exit(-1); // finalização do programa devido ao erro
      }
   // código que NÃO ESTÁ está sujeito a exceções
      for (int i=0; i<max; i++) {
        System.out.print(i + " ");
      }
      System.out.println();
   }
}

Com este programa, caso não seja fornecido um argumento ou caso seja inválido, será exibida uma mensagem melhor para o usuário, como nos exemplos que seguem:
>java Contagem
É necessário um argumento inteiro válido!

>java Contagem Jandl
É necessário um argumento inteiro válido!

>java Contagem 7
0 1 2 3 4 5 6

Nesta situação, foi escolhido o tratamento de Exception, o tipo mais genérico de exceção, que inclui todas as outras, ou seja, inclui as exceções específicas deste programa que são ArrayIndexOutOfBoundsException e NumberFormatException.

Note que o uso de exceções evita poluir o código que se deseja executar com testes e validações diversas, feitas com diretivas if e switch, além de funções de validação. O bloco de código monitorado contém a sequência de instruções necessárias ao programa, construída para quando tudo está correto. Se ocorrem problemas, a execução é desviada para o bloco de tratamento de erros, que apenas lida com o problema encontrado.

Tratamento seletivo de exceções

Outro aspecto muito positivo da diretiva try é que são permitidas várias cláusulas catch, para exceções diferentes. Assim é possível atrelar dois ou mais blocos de tratamento diferentes para o tratamento de cada exceção possível.

A única regra para isso é primeiro apanhar as exceções mais específicas e depois as mais genéricas, assim tratar a exceção Exception será sempre a última opção, quando necessário.

O programa Contagem agora pode ter um tratamento de exceções bem sofisticado.
public class Contagem {
   public static void main (String args[]) {
      int max = 0; // use zero ou outro valor default
   // código que ESTÁ sujeito a exceções
      try {
         max = Integer.parseInt(args[0]);
   // tratamento de um tipo exceção
      } catch (ArrayIndexOutOfBoundsException e) {
System.out.println(“Faltou argumento.”);
max = 3; // aqui usamos um valor default,
                 // sem finalizar o programa
   // tratamento de outro tipo exceção
      } catch (NumberFormatException e) {
        System.out.println(“Argumento invalido.”);
System.exit(-1); // aqui finalizamos o programa
      }
   // código que NÃO ESTÁ está sujeito a exceções
      for (int i=0; i<max; i++) {
        System.out.print(i + " ");
      }
      System.out.println();
   }
}

Observe que cada exceção possível de ocorrer tem um trecho de código associado, por meio de cláusula catch, que realiza seu tratamento. Apenas um bloco catch é executado, sendo desviados os demais. Outro aspecto positivo é não não necessários testes (com diretivas if ou switch) para determinar o tipo de erro e selecionar seu tratamento.

Se o trecho de código do tratamento torna-se grande, pode ser criado um método de tratamento específico, que separa ainda mais a ocorrência do erro de seu tratamento.

Objetos de exceção apanhados

Deve ser observado que a variável e declarada nos blocos de tratamento de exceção, pode ter qualquer nome determinado pelo programador, como exc, excecao ou outro.

Esta variável permite o acesso e uso do objeto exceção apanhado, o que permite, por exemplo, obter uma String com a mensagem de erro associada ao tipo da exceção:
String msg = e.getMessage();

Tais mensagens podem conter detalhes que podem tanto ser exibidos para o usuário, quando é conveniente e adequado fornecer informações adicionais do problema; como também armazenados em arquivos de registro (logs), para análise posterior.

Também é possível imprimir a rota da exceção no console, isto é, o caminho percorrido pelo programa até a ocorrência da exceção:
e.printStackTrace();

Tal rota também pode ser obtida e processada de outras maneiras, mas isto deixa de ser básico e foge ao escopo desta lição!

Considerações finais

As exceções permitem um tratamento simples do ponto de vista de programação, mas bastante sofisticado quando consideradas as escolhas possíveis entre seu tratamento local e seu envio para contextos superiores. 

Recomenda-se que a ocorrência de erros sempre seja sinalizada com o lançamento de exceções, o que possibilita a separação clara entre o código normal (aquele executado na ausência de erros) e o código de tratamento de erros. Novamente é necessário que tal separação é uma grande vantagem, pois permite, a partir do ponto onde ocorreu exceção, desviar o fluxo de processamento para um contexto superior ou outro ponto mais adequado do programa; mas sem uso de variáveis globais, funções de erro, códigos de erro genéricos e outras técnicas improvisadas.

O tratamento seletivo de erros também possibilita melhor organização do código, o que facilita seu reuso e, principalmente, sua manutenção.


POO-F-12-Exceções e seu tratamento POO-P-14-Objetos que usam objetos

Referências Bibliográficas

[1] JAMSA, K.; KLANDER, L.. Programando em C/C++: a bíblia. São Paulo: Makron Books, 1999.
[2] PAGE_JONES, M.. Fundamentos do Desenho Orientado a Objeto com UML. São Paulo: Makron Books, 2001.
[3] SOMMERVILLE, I.. Software Engineering. 6th. Ed. Harlow: Pearson, 2001.
[4] DEITEL, H.M.; DEITEL, P.J.. Java: como programar. 6a. Ed. São Paulo: Pearson Prentice-Hall, 2005.
[5] SAVITCH, W.. C++ Absoluto. São Paulo: Pearson Addison-Wesley, 2004.
[6] JANDL JR., P. Introdução ao C++. São Paulo: Futura, 2003.
[7] JANDL JR., P.. Java - guia do programador. 3a. ed. São Paulo: Novatec, 2015.
[8] RUMBAUGH, J.; BLAHA, M.; PREMERLANI, W.; EDDY, F.; LORENSEN, W.. Object-oriented modeling and design. Englewoods Cliffs: Prentice-Hall, 1991.
[9] STROUSTRUP, B.. The C++ Programming Language. 3rd Ed. Reading: Addison-Wesley, 1997.
[10] LANGSAM, Y.; AUGENSTEIN, M. J.; TENENBAUM, A. M.. Data structures using C and C++. 2nd Ed. Upper Saddle River: Prentice-Hall, 1996.
[11] WATSON, K.; NAGEL, C.; PEDERSEN, J.H.; REID, J.D.; SKINNER, M.; WHITE, E.. Beginning Microsoft Visual C# 2008. Indianapolis: Wiley Publishing, 2008.

quinta-feira, 15 de fevereiro de 2018

POO::Fundamentos-12-Exceções e seu lançamento

POO-F-11-Autorreferência this POO-F-13-Tratamento de exceções
Na plataforma de programação Java, assim como em outras linguagens OO modernas, a política de tratamento de problemas ocorridos durante a execução de um programa utiliza a estratégia de criação, lançamento e tratamento de exceções e erros.

Então, é essencial realizar a distinção entre:

  • Exceções, que são problemas de severidade variável, tratáveis pelo programador; e
  • Erros, problemas de severidade alta que não tratáveis pelo programador.

Exceções e Erros

Uma exceção (exception) é um objeto especial, de uma classe específica pertencente a hierarquia da API Java, a classe Exception do pacote java.lang, que é destinada a sinalização de situações que podem ser tratadas pelo programador [4][7].

Isto significa que uma exceção indica a ocorrência de uma anormalidade no código, a qual poderia ser solucionada pelo próprio programa, por meio do uso de um dado alternativo, de uma outra estratégia de cálculo, pela obtenção de um novo dado junto ao usuário do programa, ou eventualmente outra ação considerada apropriada.

Um erro (error) é também um objeto especial, de outra classe específica pertencente a hierarquia Java que é java.lang.Error, mas reservada para indicar situações severas ou complexas, que não deveriam ser tratadas pelo programador [4][7].

Assim, um erro ocorrido durante a execução do código muito provavelmente não pode ser solucionado pelo próprio programa, pois pode ter origem num defeito do hardware, num problema junto ao sistema operacional ou ainda na própria máquina virtual Java (JVM).

Enquanto uma exceção pode estar relacionada a um problema previsível, que pode ser resolvido com algum cuidado extra no projeto do sistema; um erro é uma situação rara, imprevista, severa e crítica, cuja prudência recomenda apenas informar o ocorrido e parar imediatamente o programa.

As exceções (exceptions) geralmente estão relacionadas a situações que, embora anormais, são previsíveis no projeto como o recebimento de argumentos inválidos, entradas de dados inconsistentes, recursos (arquivos ou urls) não encontrados ou indisponíveis, assim como o time-out das operações executadas.

Já os erros (errors) estão sempre associados a situações anormais, atípicas e não previsíveis pelo programador, tais como erros internos da JVM, falhas oriundas do mal funcionamento do hardware (processador e memória), defeitos (bugs) no sistema operacional, assim inconsistências decorrentes de ataques ao sistema.

Por conta disso, o termo tratamento de exceções é visto com frequência, sendo uma necessidade comum da programação. Já o tratamento de erros é raro e, por esta razão, não será tratado neste material.

A classe Throwable

Na hierarquia de classes da API Java existe a classe Throwable, do pacote java.lang, que é a superclasse de todos os erros e exceções da linguagem Java, como mostra a figura que segue. Apenas objetos deste tipo ou de suas subclasses são lançados pela JVM e podem ser lançados pelo uso da diretiva throw.

De maneira análoga, apenas objetos deste tipo, ou de suas subclasses, podem ser apanhados por cláusulas catch, que fazem parte da diretiva try.

A diretiva try e a cláusula catch, ou simplesmente try/catch, compõem uma das formas mais comuns do tratamento de exceções. Já a diretiva throw serve para que um programa sinalize a ocorrência de uma situação anormal por meio de do lançamento de uma exceção [7].

Funcionamento das exceções

As exceções são criadas nos locais onde as situações problemáticas são encontradas no código do programa. Assim, como qualquer objeto, as exceções devem ser criadas e depois lançadas para o contexto superior (trecho de código que invocou a execução do trecho específico onde o problema foi encontrado) [7]. A figura que segue ilustra o funcionamento das exceções.

Isto significa que as exceções podem ser monitoradas e tratadas em locais diferentes do código (em diferentes contextos superiores), ou seja, a ocorrência anormal pode ser tratada em outro local específico do código do programa, melhorando sua organização.

A ideia central é que quando um erro ou problema ocorre, é criada e lançada uma exceção (to throw an exception). A exceção é sempre lançada para um contexto superior, isto é, para quem acionou o trecho de código problemático, separando o problema de seu tratamento. Para tratar uma exceção é necessário apanhá-la (to catch an exception). Exceções apanhadas podem ser relançadas para outro contexto superior. Caso o contexto mais alto alcance o nível da JVM, nível onde o programa foi acionado, a execução é interrompida imediatamente e sinalizada para o usuário [7].

A grande vantagem do uso do lançamento e tratamento de exceções é a possibilidade de colocar o tratamento do problema em contextos (ou escopos) diferentes. Assim, seu emprego evita o uso de códigos de erro genéricos; ou de variáveis globais para sinalização, identificação e tratamento de erros. A experiência já mostrou que o tratamento de exceções é uma estratégia muito flexível para lidar com os problemas do código de qualquer programa [4][7].

Classes de exceção

Existem muitas classes de exceção no Java, mas a classe básica é java.lang.Exception, que indica um problema de natureza geral. Todas as demais classes de exceção são derivadas de Exception, por isso tem sempre o sufixo Exception em seus nomes, como nos exemplos que seguem, onde os nomes dos tipos das exceções também indicam seu propósito específico:

  • ArrayIndexOutOfBoundsException, sinaliza o uso de índices inválidos em arrays;
  • IOException, indica problemas em operações de entrada e saída;
  • NullPointerException, ocorre quando uma referência nula (null) é usada ao invés de um objeto válido;
  • NumberFormatException, aponta problemas de representação (formato) de valores numéricos;
  • RuntimeException, serve para indicar erros gerais durante a execução de um trecho do código do programa.
A exceção java.lang.RuntimeException é bastante importante, pois toda a família derivada desta exceção tem tratamento opcional, ou seja, são exceções consideradas não monitoradas (unchecked exceptions).

Lançamento de exceções com cláusula throw

Para lançar uma exceção basta instanciar um objeto de exceção to tipo desejado e lançá-lo, por exemplo:
throw new Exception();

Usualmente as classes de exceção aceitam mensagens como argumento de seus construtores, como:
throw new NumberFormatException(“Valor inválido”);

Tais mensagens podem incluir detalhes do problema ocorrido, que podem ser usados em na parte do programa destinada ao tratamento da exceção.

Assim, na ocorrência de um problema na execução do código, sugere-se fortemente o lançamento de uma exceção de tipo adequado, que pode ser escolhida entre as muitas existentes na API Java.

Tipos de exceções

Existem dois tipos de exceções: as exceções não monitoradas (unchecked exceptions) e as exceções monitoradas (checked exceptions).

As exceções não monitoradas (unchecked exceptions) são aquelas em que o tratamento com try/catch não é obrigatório. Estas exceções são implicitamente lançadas por diversos métodos presentes na API Java (o compilador não faz menção a sua ocorrência), assim como por métodos que podem ser criados pelos programadores. De alguma forma, sinalizam problemas eventuais, considerados de menor severidade.

Já as exceções monitoradas (checked exceptions) são aquelas em que o tratamento com try/catch é obrigatório, pois sua severidade é maior, requerendo tratamento. Estas exceções são explicitamente lançadas por muitos métodos presentes na API Java, de modo que o compilador indica como erro a ausência de tratamento próprio. Como antes, o programador pode criar métodos que lancem este tipo de exceção.

Independentemente de serem monitoradas ou não monitoradas, todas as exceções do Java, quando ocorrem, são lançadas para o contexto superior e, se alcançam a JVM, provocam a interrupção do programa. Apenas o tratamento é considerado opcional no caso de exceções não monitoradas (unchecked exceptions).

Lançamento de exceções não monitoradas

O código dos programas Java tipicamente se encontra distribuído nos métodos que compõem as classes dos objetos usados. Assim, quando o código de um método produz ou (explicitamente) lança exceções não monitoradas, não é obrigatório o tratamento local nem a indicação explícita de seu lançamento, como no método converteESoma(String, String), que segue, que recebe dois argumentos do tipo String, convertendo-se em números reais e retornando sua soma.
public double converteESoma(String s1, String s2) {
   double d1 = Double.parseDouble(s1);
   double d2 = Double.parseDouble(s2);
   return d1 + d2;
}

Dois tipos de exceções podem ser lançadas por este método:
  • NullPointerException, quando o argumento s1, ou o argumento s2 é null, isto é, não indica um objeto válido;
  • NumberFormatException, quando o argumento s1, ou o argumento s2 é um objeto válido do tipo String, mas que não tem o formato adequado de um número em ponto flutuante ou inteiro.
A primeira exceção ocorre se fizermos:
converteESoma(null, "13");
converteESoma("-7.56", null);
converteESoma(null, null);

Já a segunda exceção ocorre quando algum dos argumentos não é uma String com um número válido:
converteESoma("", "64");
converteESoma("19.85", "dois");
converteESoma("0,23534", "3/4");

Observe que no código deste método, o lançamento destes dois tipos de exceção é implícito, ou seja, sem o uso da diretiva throw. Além disso, o código do método não trata a ocorrência destas exceções, nem faz qualquer indicação de seu lançamento.

Isto é possível porque as exceções lançadas são do tipo não monitorado (unchecked exceptions), que não exigem o tratamento da exceção ou indicação explícita de seu lançamento.

Nestas situações o programador deve avaliar e antever as possibilidades de ocorrências de exceções em métodos como este.

Uso da cláusula throws

A cláusula throws pode ser aplicada em um método para indicar explicitamente as exceções possivelmente lançadas em seu código, desobrigando seu tratamento local, quaisquer sejam seus tipos (checked ou unchecked). O mesmo método converteESoma(String, String) poderia ser modificado como:
public double converteESoma(String s1, String s2)
      throws NumberFormatException, NullPointerException {
   double d1 = Double.parseDouble(s1);
   double d2 = Double.parseDouble(s2);
   return d1 + d2;
}

A cláusula throws, que segue a lista de argumentos do método, lista as exceções, monitoradas ou não monitoradas, que podem ser lançadas pelo método. Isto auxilia o programador a prever os problemas decorrentes do uso do método, além de desonerar o tratamento das exceções listadas no código do próprio método.

No caso de exceções não monitoradas (unchecked exceptions), sua indicação com a cláusula throws não é obrigatória, mas torna explícito seu lançamento possível, auxiliando o programador.

No caso de exceções monitoradas (checked exceptions), sua indicação com a cláusula throws é obrigatória quando não são tratadas no código do método.

Lançamento de exceções monitoradas

No código dos métodos das classes de um programa também podem ocorrer exceções monitoradas (checked exceptions), isto é, situações anormais que devem ser tratadas pelo programador. Exemplos deste tipo de exceção são as operações de entrada e saída envolvendo o sistema de arquivos, ou de escrita e leitura em dispositivos de rede.

Se o código de um método produz exceções monitoradas, ou tais exceções são tratadas localmente, ou são explicitamente indicadas com o uso da cláusula throws.

No exemplo que segue, o método contaLinhasArquivo(String) recebe um argumento do tipo String contendo o nome do arquivo, abrindo-o e percorrendo-o para a contagem de suas linhas, fechando-o e retornando o resultado.
public int contaLinhasArquivo(String arquivo)
      throws IOException {
   int linhas = 0;
   BufferedReader br = new BufferedReader(
      new FileReader(arquivo));
   :
   return linhas;
}

Embora apenas um fragmento deste método seja exibido para simplificação do exemplo, podemos observar que a operação de abertura do arquivo pode lançar uma exceção do tipo IOException. Tal exceção é indicada na lista da cláusula throws do método, explicitando sua ocorrência e adiando seu tratamento para um contexto superior.

Se o argumento arquivo, do tipo String, for null, poderá ser lançada uma exceção NullPointerException, mas que é do tipo não monitorada, não requerendo indicação explícita de lançamento, tão pouco tratamento.

Se for preferível o tratamento local, surge uma diretiva try/catch, envolvendo o trecho onde uma exceção a ser tratada pode ocorrer, como esquematizado abaixo.
public int contaLinhasArquivo(String arquivo) {
   int linhas = 0;
   try {
      BufferedReader br = new BufferedReader(
            new FileReader(arquivo));
      :
   } catch (IOException ioe) {
      // tratamento de erros
   }
   return linhas;
}

Com o tratamento local, não se emprega a cláusula throws para a exceção tratada.

Na próxima lição veremos como efetuar o tratamento de exceções por meio da diretiva try e suas cláusulas catch e finally.


[1] JAMSA, K.; KLANDER, L.. Programando em C/C++: a bíblia. São Paulo: Makron Books, 1999.
[2] PAGE_JONES, M.. Fundamentos do Desenho Orientado a Objeto com UML. São Paulo: Makron Books, 2001.
[3] SOMMERVILLE, I.. Software Engineering. 6th. Ed. Harlow: Pearson, 2001.
[4] DEITEL, H.M.; DEITEL, P.J.. Java: como programar. 6a. Ed. São Paulo: Pearson Prentice-Hall, 2005.
[5] SAVITCH, W.. C++ Absoluto. São Paulo: Pearson Addison-Wesley, 2004.
[6] JANDL JR., P. Introdução ao C++. São Paulo: Futura, 2003.
[7] JANDL JR., P.. Java - guia do programador. 3a. ed. São Paulo: Novatec, 2015.
[8] RUMBAUGH, J.; BLAHA, M.; PREMERLANI, W.; EDDY, F.; LORENSEN, W.. Object-oriented modeling and design. Englewoods Cliffs: Prentice-Hall, 1991.
[9] STROUSTRUP, B.. The C++ Programming Language. 3rd Ed. Reading: Addison-Wesley, 1997.
[10] LANGSAM, Y.; AUGENSTEIN, M. J.; TENENBAUM, A. M.. Data structures using C and C++. 2nd Ed. Upper Saddle River: Prentice-Hall, 1996.
[11] WATSON, K.; NAGEL, C.; PEDERSEN, J.H.; REID, J.D.; SKINNER, M.; WHITE, E.. Beginning Microsoft Visual C# 2008. Indianapolis: Wiley Publishing, 2008.

segunda-feira, 12 de fevereiro de 2018

POO::Fundamentos-11-Autorreferência this

POO-F-10-Pacotes e Namespaces POO-F-12-Tratamento de Exceções
Todo objeto, em Java, C# ou C++, dispõe de uma referência para si próprio que é representada por meio da palavra reservada this, conhecida também como referência this ou autorreferência.

A autorreferência this tem duas aplicações bastante típicas: a primeira é possibilitar a diferenciação de campos da classe de variáveis locais ou parâmetros formais; a segunda é permitir que um método retorne uma referência para o próprio objeto[1][4][5][6][7][9].


Diferenciação de elementos

Quando um método de uma classe utiliza métodos ou campos não estáticos da própria classe, implicitamente está sendo usada a referência this [4][7]. Observe os métodos e os construtores da classe Horario que segue.

public class Horario {
// atributos int de acesso restrito (privado)
   private int hor, min, seg;

// métodos de acesso (leitura) dos campos restritos
   public int getHoras () { return hor; }
   public int getMinutos () { return min; }
   public int getSegundos () { return seg; }

// métodos de alteração (escrita) dos campos restritos
   public void setHoras (int hor) {
      if(hor<0 || hor>23) {
         throw new RuntimeException("Hora invalida: " + hor);
      }
      this.hor = hor;
   }
   public void setMinutos (int min) {
      if(min<0 || min>59) {
         throw new RuntimeException("Minutos invalido: " + min);
      }
      this.min = min;
   }
   public void setSegundos (int seg) {
      if(seg<0 || seg>59) {
         throw new RuntimeException("Segundos invalido: " + seg);
      }
      this.seg = seg;
   }
   public void setHorario (int hor, int min, int seg) {
      setHoras(hor); setMinutos(min); setSegundos(seg);
   }

// construtor parametrizado
   public Horario (int hor, int min, int seg) {
      setHorario(hor, min, seg);
   }
// construtor default
   public Horario () {
      this(0, 0, 0);
   }

// representação textual dos objetos Hora
   public String toString () {
      return String.format("%02d:%02d:%02d",
         hor, min, seg);
   }
}

Observe os métodos setHoras(int), setMinutos(int) e setSegundos(int): nos três o parâmetro formal declarado tem o mesmo nome do respectivo campo da classe. O uso explícito de this permite diferenciar variáveis locais dos campos (variáveis-membro) da classe com mesma denominação.

Quando um método declara uma variável local (ou um parâmetro) com o mesmo nome de um campo da classe, a variável local oculta o campo, ou seja, quando tal nome é usado, é acessada a variável local e não o campo da classe. Analise o fragmento de setHoras(int):
if(hor<0 || hor>23) {
   throw new RuntimeException("Hora invalida: " + hor);
}

A variável hor assim indicada faz referência ao parâmetro formal do método e não ao campo da classe. Ou seja, este código testa o valor recebido pelo método. Se o valor é adequado, é atribuído ao campo da classe, distinguido da variável local por this:
// campo da classe recebe valor da variável local
this.hor = hor;

Quando não existe conflito entre os nomes presentes no escopo de um método, os campos da classe são usados automaticamente [4][7]. Na verdade, nesta situação ocorre o uso implícito da autorreferencia this, como no método toString() da classe Horario.

A figura que segue ilustra o uso de this numa outra classe.

A referência this também permite distinguir construtores sobrecarregados da classe [7], como feito na classe Horario.

O construtor Horario(int, int, int) aciona o método público setHorário(int, int, int), que por sua vez aciona os métodos setHoras(int), setMinutos(int) e setSegundos(int), os quais validam os componentes do horário.

Já o construtor Horario() aciona o construtor Horario(int, int, int) por meio do uso de this, acompanhado dos argumentos desejados:
this(0, 0, 0);

Deve ser destacado que esse uso de this só é possível em construtores e quando feito na primeira linha de código do construtor. Ao mesmo tempo, é evitada a repetição de código em construtores, prática benéfica para facilitar a manutenção da classe.

Retorno da autorreferência

Outra aplicação da autorreferência this é como retorno de métodos da classe [7].

É comum que métodos retornem novos objetos, como por exemplo, o método substring (int ini, int fim) da classe java.lang.String, que retorna um novo objeto do tipo String contendo o fragmento delimitado pelas posições ini (incluída) e fim (não incluída) do texto da String. O método toUpperCase() retorna um novo objeto String apenas com maiúsculas. Por exemplo:
// Posição             6    11
//                     v    v
String texto1 = "Peter Jandl Junior";
// texto2 recebe "Jandl"
String texto2 = texto1.substring(6, 11);
// texto3 recebe JANDL
String texto3 = texto2.toUpperCase();

Quando um método retorna um objeto, é possível, no Java e C#, encadear a chamada de métodos ou concatenar suas chamadas, assim pode ser feito:
String texto1 = "Peter Jandl Junior";
String texto3 = texto1.substring(6, 11).toUpperCase();

Aqui é acionado o método substring(int, int) do objeto texto1 (do tipo String), e o método toUpperCase(), no resultado produzido por substring, que também é do tipo String. Cada chamada de método da classe String produz um resultado String (ou de outro tipo), de modo que o objeto original não é alterado, pois, por definição de projeto, os objetos da class String são imutáveis.

Mas existem situações onde os objetos são mutáveis, isto é, podem ser alterados, como os objetos do tipo Horário, dado anteriormente neste post. Os métodos setHoras(int), setMinutos(int) e setSegundos(int) têm retorno do tipo void, portanto não permitem qualquer espécie de encadeamento.

Considere agora a classe Somador abaixo.

public class Somador {
// campo privado com total do somador
   private double total;

// construtores parametrizado e default
   public Somador(double valorInicial) {
      setTotal(valorInicial);
   }
   public Somador() {
      this(0);
   }

// método de acesso
   public double getTotal() { return total; }

// métodos de alteração
   public Somador setTotal(double valor) {
      total = valor;
      return this;
   }
   public Somador add(double valor) {
      total = total + valor;
      return this;
   }

// método de produção   
   public Somador addToNew(double valor) {
      Somador novo = new Somador(total);
      novo.add(valor);
      return novo;
   }
}

A classe Somador tem como propósito representar um somador (ou um totalizador) de valores reais. Os construtores sobrecarregados permitem tanto criar um somador com um valor inicial predeterminado, como um somador default com valor inicial zero, sem repetição de código.
// somador com valor inicial = 15.4
Somador s1 = new Somador(15.4);
// somador com valor inicial = zero
Somador s2 = new Somador();

Como o campo total é privado e, portanto, inacessível, o método de acesso getTotal() permite consultar o valor total presente do objeto.
System.out.println("Somador s1 = " + s1.getTotal());
double aux = s2.getTotal();

Existem também dois métodos de alteração. O método add(double) permite adicionar o valor do argumento ao total do objeto, mas ao invés de retornar void (típico de métodos setter), retorna a referência do próprio objeto com this (por isso o tipo de retorno de add(double) é a própria classe Somador). Isto permite o encadeamento de métodos:
// com encademento
Somador s3 = new Somador();   // total = 0
s3.add(1.5).add(3.6).add(-1); // total = 4.1

// sem encademento
Somador s4 = new Somador(); // total = 0
s4.add(1.5); // total = 1.5
s4.add(3.6); // total = 5.1
s4.add(-1);  // total = 4.1

É fácil notar que o uso do encadeamento permite obter construções mais simples. Também deve ser notado que o retorno de this permite que um mesmo objeto seja afetado (alterado) por uma série de operações encadeadas.

Finalmente, a classe Somador possui o método addToNew(double) que é considerado de produção porque retorna um novo objeto, sem alterar o objeto inicial. No código deste método, observe a instanciação de um novo somador com o valor total do objeto existente, depois a adição do valor dado como argumento e o retorno da referência deste novo objeto.
Somador s5 = s4.addToNew(10.9);
// s5 contém 15.0
// s4 continua com 4.1

Esta estratégia permite criar operações que tanto retornam novos objetos, como mantém inalterados os objetos iniciais, além de possibilitar o encadeamento de operações.

Considerações finais

O uso da autorreferência this é bastante conveniente, pois além de permitir a diferenciação de campos da classe de variáveis locais, possibilita que a sobrecarga de construtores, evitando a repetição de código.

Outro aspecto do uso de this é permitir o retorno da referência do próprio objeto, possibilitando o encadeamento de operações sobre o mesmo objeto.

POO-F-10-Pacotes e Namespaces POO-F-12-Tratamento de Exceções

Referências Bibliográficas

[1] JAMSA, K.; KLANDER, L.. Programando em C/C++: a bíblia. São Paulo: Makron Books, 1999.
[2] PAGE_JONES, M.. Fundamentos do Desenho Orientado a Objeto com UML. São Paulo: Makron Books, 2001.
[3] SOMMERVILLE, I.. Software Engineering. 6th. Ed. Harlow: Pearson, 2001.
[4] DEITEL, H.M.; DEITEL, P.J.. Java: como programar. 6a. Ed. São Paulo: Pearson Prentice-Hall, 2005.
[5] SAVITCH, W.. C++ Absoluto. São Paulo: Pearson Addison-Wesley, 2004.
[6] JANDL JR., P. Introdução ao C++. São Paulo: Futura, 2003.
[7] JANDL JR., P.. Java - guia do programador. 3a. ed. São Paulo: Novatec, 2015.
[8] RUMBAUGH, J.; BLAHA, M.; PREMERLANI, W.; EDDY, F.; LORENSEN, W.. Object-oriented modeling and design. Englewoods Cliffs: Prentice-Hall, 1991.
[9] STROUSTRUP, B.. The C++ Programming Language. 3rd Ed. Reading: Addison-Wesley, 1997.
[10] LANGSAM, Y.; AUGENSTEIN, M. J.; TENENBAUM, A. M.. Data structures using C and C++. 2nd Ed. Upper Saddle River: Prentice-Hall, 1996.
[11] WATSON, K.; NAGEL, C.; PEDERSEN, J.H.; REID, J.D.; SKINNER, M.; WHITE, E.. Beginning Microsoft Visual C# 2008. Indianapolis: Wiley Publishing, 2008.