Вы находитесь на странице: 1из 60

Práticas de Programação

Orientada a Objetos
GCC178 – Turma 14A
2017-1

Parte 08 – Classes Abstratas, Interfaces e


Depuração

Júlio César Alves professores.dcc.ufla.br/~jcalves


Parte 08
Classes Abstratas
Métodos “vazios” que precisam ser sobrescritos

Voltando ao exemplo que fizemos da Rede Social, a classe Mensagem tinha um método
exibirConteudo que foi criado para que as subclasses o sobrescrevessem.

public class Mensagem {


...
public void exibir() {
Mas havia dois problemas: System.out.println(autor);
exibirConteudo();
1) Ele não precisava ter implementação System.out.println("\t" + exibirTempo() + " - “
nenhuma na superclasse. + nroCurtidas + " pessoas curtiram isso!");
exibirComentarios();
2) Quem implementasse subclasse poderia }
esquecer de sobrescrevê-lo.
protected void exibirConteudo() {
System.out.println(
Esse tipo de situação é muito "Esqueceu de sobrescrever exibirConteudo()!");
comum! }
}
Métodos “vazios” que precisam ser sobrescritos

No caso da rede social era um método protegido. Mas a situação mais comum é quando
queremos usar o polimorfismo para um método que sabemos que todas as subclasses
terão, mas que não faz sentido na superclasse.

FormaGeometrica No nosso exemplo de formas geométricas, a classe FormaGeometrica


______________ tem um método exibir. Mas ela não tem o que fazer, pois, ela não
posX sabe como exibir as formas. Cada subclasse é que precisa
posY sobrescrever o método e se exibir.
cor
estaVisivel
______________
exibir Mesmo assim, é muito interessante ter o
esconder método exibir na superclasse, porque com
estaDesenhado isso a classe Controle pode ter uma única
moverDireita coleção de formas geométricas e chamar o
Quadrado Circulo
moverEsquerda
moverCima
Herda de Herda de método exibir a partir dela, sem fazer casting
FormaGeometrica FormaGeometrica ou algo do tipo.
moverBaixo
______________ ______________
moverHorizontal
tamanho diametro
moverVertical
______________ ______________
mudarCor
mudarTamanho mudarDiametro
Métodos “vazios” que precisam ser sobrescritos  Métodos abstratos

Já que é tão comum e útil algo precisava ser feito... e foi!

Nós podemos declarar um método como abstrato. Com isso,


deixaremos claro que:
• O método não tem implementação na superclasse.
• E precisa ser sobrescrito na subclasse.

Usamos a palavra-chave abstract.

public abstract void algumMetodo();

O método tem apenas assinatura


E qual a utilidade disso? (não tem implementação).
Métodos abstratos  Classes Abstratas

Suponha que na classe Mensagem da nossa Rede O que aconteceria se


Social nós façamos o método exibirConteudo abstrato. criássemos um objeto da
classe Mensagem e
public class Mensagem { tentássemos chamar o
...
protected abstract void exibirConteudo(); método exibirConteudo?
}

Na verdade não é possível criar objetos de Nós dizemos nesse caso que a própria classe também é
classes que tenham métodos abstratos. abstrata.

E precisamos inclusive colocar isso


explicitamente no código (caso contrário, Ainda não vi muita
ocorrerá um erro de compilação). utilidade...
public abstract class Mensagem {
...
protected abstract void exibirConteudo();
}
Métodos e Classes Abstratas
O bacana disso é o seguinte: se a subclasse não sobrescrever o método abstrato da
superclasse, a subclasse também será considerada abstrata (o que gerará um erro de
compilação).

public abstract class Mensagem {


...
protected abstract void exibirConteudo();
}

public class MensagemTexto extends Mensagem {


...
// não sobrescreve exibirConteudo()
} Numa situação como essa, se fosse
possível criar um objeto da classe
MensagemTexto ele teria o método
MensagemTexto.java:1: error: MensagemTexto is not
abstract and does not override abstract method exibirConteudo (por herança), mas
exibirConteudo() in Mensagem ele é abstrato.
public class MensagemTexto extends Mensagem {
^
1 error
Métodos e Classes Abstratas

Portanto, na verdade uma situação como essa também não é possível.


Ou a classe MensagemTexto sobrescreve o método exibirConteudo ou ela também terá
que ser declarada abstrata.

public abstract class Mensagem {


...
protected abstract void exibirConteudo();
}

}
...
X
public class MensagemTexto extends Mensagem {

// não sobrescreve exibirConteudo()

public class MensagemTexto extends Mensagem {


...
@Override
protected void exibirConteudo() {
System.out.println("\t" + texto);
}
} Essa parte nós já tínhamos feito antes.
Métodos e Classes Abstratas

Resumindo, temos as duas possibilidades abaixo:

public class Mensagem {


... public abstract class Mensagem {
protected void exibirConteudo() { ...
System.out.println( protected abstract void exibirConteudo();
"Esqueceu de sobrescrever exibirConteudo()!"); }
}
}

Na primeira opção (que havíamos feito antes), temos


as seguintes desvantagens: Já na segunda opção:
• Não fica explícito que a subclasse precisa sobrescrever o • Se a subclasse não sobrescrever o método ocorre
método. um erro de compilação!
• O erro só é percebido durante a execução do programa • Portanto, não tem como o programador esquecer
com uma mensagem de texto (e se for um programa de sobrescrever o método.
com interface gráfica?).

Portanto, sempre que precisar garantir que suas subclasses tenham determinado método, declare-o como
abstrato na superclasse.
Exemplo de Métodos e Classes Abstratas

No nosso exemplo das FormasGeométricas:


• Em quais classes o método exibir deve ser abstrato? Em quais
deve ser implementado?
FormaGeometrica • Quais classes devem ser abstratas? Quais devem ser concretas?
______________
posX
posY O método exibir deve ser abstrato na classe FormaGeometrica,
cor
estaVisivel
portanto, ela deve ser uma classe abstrata.
______________ Já nas demais classes o método deve ser implementado e as
exibir
esconder classes devem ser concretas.
estaDesenhado
moverDireita
Quadrado Circulo
moverEsquerda
Herda de Herda de
moverCima
FormaGeometrica FormaGeometrica
moverBaixo
______________ ______________
moverHorizontal
tamanho diametro
moverVertical
______________ ______________ Obs: chamamos de Classes Concretas
mudarCor aquelas que não são abstratas.
mudarTamanho mudarDiametro
Classes Abstratas

Classes abstratas são úteis não apenas quando se tem métodos abstratos.
Na verdade, com o uso de herança, muitas vezes definimos classes para as quais não
desejamos que sejam criados objetos.

Nos nossos exemplos não faria sentido criar objetos das classes FormaGeometrica e
Mensagem mesmo que elas não tivessem métodos abstratos.
Nós poderíamos então declará-las como abstratas mesmo assim, apenas para impedir
a criação de objetos delas.

Repare que uma classe pode ser declarada abstrata sem ter
métodos abstratos.
Mas o contrário não é verdadeiro. Se uma classe tiver pelo
menos um método abstrato (mesmo que por herança) ela
necessariamente precisa ser abstrata.
Resumindo Classes e Métodos Abstratos

Em resumo:
• Subclasses de superclasses abstratas só são concretas se sobrescreverem e
implementarem todos os métodos abstratos da superclasse.
• Caso contrário, elas também são abstratas.
• Portanto, métodos abstratos servem para forçar que subclasses concretas os
implementem.

Classes abstratas não podem ser instanciadas mas ainda assim definem tipos válidos.
Ou seja, posso declarar uma variável do tipo de uma classe abstrata normalmente. E
ela pode receber um objeto de uma subclasse da classe abstrata.

No nosso exemplo se declararmos a classe Mensagem como abstrata, ainda assim


podemos ter um ArrayList<Mensagem> na classe FeedNoticias normalmente.

Em resumo: classes abstratas aumentam o poder do polimorfismo!


Questões sobre Classes Abstratas

Classes abstratas podem ter construtores?

Sim!
Apesar de não podermos criar objetos de classes abstratas, podemos chamar seus construtores usando super nas
subclasses.
E isso é não só útil, como necessário, uma vez que a classe abstrata precisa garantir um estado inicial válido (valor
inicial de seus atributos) e, pelo princípio do encapsulamento, seus atributos precisam ser privados.

Qual seria a utilidade de uma classe abstrata que nunca é uma superclasse?

Nenhuma!
Classes abstratas se tornam úteis quando são estendidas por classes concretas.
Diagrama de Classes UML: Classes Abstratas

Supondo que a classe FormaGeometrica seja abstrata e


tenha um método obterArea abstrato:

Classes e métodos abstratos são


representados nos diagramas de classe
UML em itálico.
Parte 08
Interfaces
FormaGeometrica Circulo Triangulo Suponha que nesse
______________ Herda de Herda de
posX FormaGeometrica FormaGeometrica exemplo queiramos agora
posY ______________ ______________ incluir no software o
cor diametro altura desenho de coisas que não
estaVisivel ______________ largura
são formas geométricas
______________ mudarDiametro ______________
exibir exibir mudarTamanho (ex: figuras, símbolos, etc.)
esconder exibir
estaDesenhado
Uma figura não tem relação
Quadrado
moverDireita Herda de Controle com forma geométrica.
moverEsquerda FormaGeometrica ______________ Conseguimos usar Herança
moverCima ______________ formas e Polimorfismo para nos
moverBaixo tamanho ______________
moverHorizontal
ajudar, ou agora nossa
______________ inserirForma
moverVertical mudarTamanho removerForma classe Controle vai precisar
mudarCor exibir manter duas listas de coisas
separadas?

As formas geométricas e as figuras têm coisas em comum, apesar de eu não poder dizer
que uma forma é uma figura ou vice-versa.

Podemos pensar em uma superclasse abstrata mais geral que serve pra ambas (tem o que
é comum). E a classe controle teria uma lista de objetos dessa superclasse.
ObjetoDesenhavel teria apenas o método abstrato exibir.

Objeto
Desenhavel

Forma
Figura Simbolo
Geometrica

Circulo Quadrilatero Triangulo

Retangulo Trapezio

Quadrado

Repare que deixamos nosso sistema mais genérico e ainda mais


facilmente extensível!
Repare que quanto mais fazemos isso, mais abstratas são as superclasses
“mais altas” (anteriores na hierarquia).
Ao ponto de que é muito comum ter superclasses abstratas que possuem
apenas métodos abstratos públicos.

Ou seja, elas têm o único objetivo de garantir que as subclasses tenham


uma determinada interface (ou seja, tenham métodos com as assinaturas
definidas nas superclasses).
Interfaces

Em OO resolve-se isso usando o conceito de Interfaces (como tipo de dados).


Ou seja, posso definir um tipo que é apenas uma interface para uma classe. E
as linguagens OO me permitem herdar desses tipos (além de herdar de uma
superclasse normal).

Em Java essas “classes” especiais são chamadas de interfaces mesmo. E


declaradas usando a palavra-chave interface ao invés de class.

public interface MinhaInteface { ... }

Elas são no fundo classes abstratas, com todos os métodos abstratos e públicos.
Podemos dizer também que elas definem um tipo (com um nome e um
conjunto de métodos) sem nenhuma implementação para os métodos.
Interfaces em Java

Alguns pontos importantes sobre as interfaces:


• Como todos os métodos são abstratos por definição, não é necessário usar a palavra-
chave abstract.
• O mesmo vale para a palavra-chave public. Os métodos são todos públicos por definição.
• Apenas constantes são permitidas como atributos (públicas, estáticas e finais). E não é
necessário escrever public static final.
• Não possuem construtores (já que não têm atributos mutáveis a serem inicializados).

A interface ObjetoDesenhavel poderia ser implementada assim:

public interface ObjetoDesenhavel {


void exibir();
}
Interfaces em Java

Java permite que classes herdem de interfaces. Mas faz isso usando uma palavra-
chave diferente: implements.
Costumamos dizer então que uma classe implementa uma interface.

public abstract class FormaGeometrica implements ObjetoDesenhavel {


...
}
Nesse nosso exemplo, como o método exibir será
sobrescrito nas subclasses (Circulo, Quadrado, etc.),
a classe FormaGeometrica precisa ser abstrata e não
precisa sobrescrever o método exibir.

public class Circulo extends FormaGeometrica {


...
@Override
public void exibir() {
Já a classe Circulo, por exemplo,
...
deve sobrescrever o método exibir,
} para ser uma classe Concreta.
}
Herança de Classes e Implementação de interfaces

Em Java, uma classe pode estender apenas uma superclasse, mas pode implementar
várias interfaces.

Isso permite utilizar parte dos benefícios da herança múltipla, evitando alguns de seus
problemas.

Suponha que queiramos incluir animais no software de desenho e, além disso, queiramos que uma
classe Cachorro , por exemplo, herdasse de uma classe Animal já existente.
Como ObjetoDesenhavel é uma interface, nós podemos fazer o que precisamos, pois a classe
Cachorro pode estender a superclasse Animal e implementar a interface ObjetoDesenhavel.
Já se ObjetoDesenhavel fosse uma classe, isso não seria possível.

Fazemos implementar da seguinte forma:


public class Cachorro extends Animal implements ObjetoDesenhavel { ...

Agora a classe cachorro pode sobrescrever os métodos da


interface ObjetoDesenhavel e também da classe Animal.
Herança de Classes e Implementação de interfaces

Repare que uma classe herda os métodos de uma interface como métodos abstratos. Portanto se
uma classe implementa uma interface mas não sobrescreve seus métodos ela não pode ser
concreta.

Veja também que quando temos uma hierarquia de herança, podemos ter várias classes
abstratas em uma determinada linha de hierarquia. E se nenhuma delas implementar um
método de uma interface mais ancestral, por exemplo, a subclasse concreta precisará
implementar.
Obs: o compilador te ajuda indicando quais métodos precisam ser implementados, caso
isso não seja feito.

Resumindo, interfaces foram criadas em Java para maximizar os


benefícios da utilização de Herança e Polimorfismo!
Interfaces = Especificações ou Contratos

Interfaces podem ser vistas também como especificações ou contratos.


Pois elas separam completamente a definição da funcionalidade da sua
implementação.
E essa é uma das grandes vantagens do uso de interfaces.

Por exemplo:
Sabemos que uma estrutura de dados pilha precisa ter um conjunto de métodos:
• Ex: Empilhar, Desempilhar e getTamanho
Se definirmos uma interface Pilha e utilizarmos variáveis sempre do tipo Pilha. Podemos depois ter várias
implementações de Pilha...
• Ex: PilhaComoVetor, PilhaComoLista, etc.
... e não precisaremos alterar nenhum lugar onde usamos as operações de Pilha (só precisamos mudar a criação do
objeto). E mais, podemos no futuro criar qualquer novo tipo de Pilha sem alterar o código de utilização das mesmas.
Ou seja, a interface Pilha funcionou como uma especificação, um contrato a ser seguido. Quem segue a especificação
pode ser utilizado sem transtornos.
Interfaces = Especificações ou Contratos

Java faz exatamente isso com suas classes que


implementam listas.

Interface
List<E>

Class Class
ArrayList<E> LinkedList<E>
Interfaces = Especificações ou Contratos

Java faz exatamente isso com suas classes que


implementam listas.

A classe ArrayList possui uma implementação mais eficiente


para retornar um objeto da lista, mas é mais lenta para inserir
e remover objetos.

Já a classe LinkedList é mais lenta para retornar um objeto,


Interface mas é mais eficiente para remover e inserir.
List<E>
Portanto, dizer qual classe é melhor usar depende muito do
seu sistema.

Class Class E mais importante: pode ser que você comece utilizando uma
ArrayList<E> LinkedList<E> e depois descubra que a outra seria melhor.
Se você utilizar variáveis do tipo List você pode mudar de uma
pra outra alterando apenas a criação dos objetos.
Interface ou Classe Abstrata

Como devemos decidir entre usar uma classe abstrata ou


uma interface?

Se algum método tem implementação, claro precisa ser uma classe abstrata.

Se nenhum método tem implementação é melhor usar Interface. Por que?

Para não limitar a herança múltipla!


Diagrama de Classes UML: Interfaces

Assim como o Java usa palavras-chave diferentes para herança de


classe e de interface (extends e implements, respectivamente), no
diagrama de classes a representação da herança de interface
também é diferente: usa-se linha tracejada

Na UML a herança de interface também tem um nome diferente: é


chamada de realização.

Repare que para a interface ObjetoDesenhavel


utilizamos um estereótipo (<<NNN>>). Ele serve
para destacar quando um elemento do diagrama
é diferente dos demais.
Obs: Usaremos estereótipos apenas para interfaces na
disciplina.
Interface: diferentes significados em OO

Eu não sei porque as


pessoas gostam tanto da Em OO usamos o termo interface para três situações distintas:
palavra interface. Isso dá
muita confusão na cabeça
das pessoas.
Interface de Interface como Interface Gráfica
uma classe tipo de dado de Usuário
• Como já vimos, a • Vimos também que • Ou em inglês GUI
interface de uma uma interface é o (Graphical User
classe é formada nome dado em Interface) é a
pelas assinaturas de algumas linguagens representação
seus métodos, ou para classes gráfica de um
seja, representa abstratas que programa, como
como é possível possuam apenas estamos
interagir com a métodos abstratos acostumados a
classe. públicos e atributos utilizar no dia-a-dia
públicos e (antes os programas
constantes. eram todos em linha
de comando).
Java 8 – Interfaces e Métodos default (padrão)

Devido ao grande poder que o conceito de Interfaces (como tipo de dados) trazem
para o desenvolvimento de software, ele é amplamente utilizado.
Mas com o passar do tempo surgiu um problema.

Imagine que em nosso exemplo do software de desenho, depois de um tempo


resolvamos criar um novo método na interface ObjetoDesenhavel. Um método que
retorne uma descrição do desenho, por exemplo.
O que aconteceria com todo o nosso código já pronto (implementado e testado)?

Como o método está na interface, todas as classes concretas precisarão implementá-lo.


Ou seja, teremos uma alteração em cascata em várias classes (Círculo, Quadrado, Figura, Símbolo, etc.)

Esse efeito colateral acaba dificultando a evolução de interfaces já desenvolvida. Imagina se o pessoal
responsável Java acrescenta um novo método em uma interface largamente utilizada; milhares de
empresas teriam que gastar ($) só para tratar isso.
Por outro lado, não poder fazer alterações em interfaces prontas impede que o código evolua
(acrescentando métodos que talvez seriam muito úteis).
Java 8 – Interfaces e Métodos default (padrão)

Para resolver esse problema a versão 8 do Java traz o conceito de métodos default
(padrão) para Interfaces.

Ou seja, agora é possível criar métodos com implementação dentro de uma interface.
E as subclasses que implementam uma interface que tenha método default compilará
mesmo que não sobrescreva o método (mas se quiser pode sobrescrevê-lo também).

public interface ObjetoDesenhavel {

void exibir();

É necessário usar a palavra-chave default String getDescricao() {


default para incluir esse tipo de return "Um objeto desenhavel";
método em uma interface. }
}
Java 8 – Interfaces e Métodos default (padrão)

Mas e os problemas da herança múltipla?


E se eu tiver uma classe que implementa duas interfaces que
tenham métodos default com a mesma assinatura?

public interface ObjetoDesenhavel { public interface SerVivo {


void exibir(); String getEspecie();
default String getDescricao() { default String getDescricao() {
return "Um objeto desenhavel"; return "Um ser vivo";
} }
} }
O compilador não consegue decidir
public class Cachorro implements SerVivo, ObjetoDesenhavel { qual implementação default será
public void exibir() { usada para o método getDescricao.
//...
}
Cachorro.java:1: error: class Cachorro inherits unrelated
public String getEspecie() { defaults for getDescricao() from types SerVivo and
return "Cachorro"; ObjetoDesenhavel
} public class Cachorro implements SerVivo, ObjetoDesenhavel
} {
Java 8 – Interfaces e Métodos default (padrão)
Nesse caso você deve sobrescrever o método na subclasse Cachorro a fim de eliminar
o conflito.
Além disso, se você quiser usar a implementação default das interfaces, a linguagem
fornece uma forma de se fazer isso.

public class Cachorro implements SerVivo, ObjetoDesenhavel {


public void exibir() {
//...
}
public String getEspecie() {
return "Cachorro";
}
@Override
public String getDescricao() {
return SerVivo.super.getDescricao() + ": " + getEspecie();
}
Dessa forma estamos chamando o
método default da interface SerVivo.
Parte 8
Outros tópicos do Java

34
Mudando de assunto, o
que acontece se eu
sobrescrever um
método estático?
Métodos Estáticos x Sobrescrita

Métodos estáticos não são chamados a partir de instâncias (objetos) e sim


diretamente pela classe. Portanto, eles não podem ser sobrescritos.

Se a subclasse tiver um método com mesma assinatura da superclasse dizemos


que ele oculta (hidden) o método da superclasse.

Por que dizemos que ele oculta o método da superclasse?

Porque se ele não existisse, ao chamar SubClasse.metodo(), o metodo da


superclasse é que seria chamado!
Java: final para métodos

E o que acontece se eu declarar um método como final?

Nós vimos que ao declarar um atributo como final, nós estamos dizendo
que ele é uma constante. Mas podemos também usar o modificador
final para métodos. O que ele faz?

public final void algumMetodo() { ... }

Ele indica que aquele método não pode ser sobrescrito nas subclasses!
Mas pra que isso serve?

É útil para evitar que um método crítico seja sobrescrito. Exemplo: um método que se for
alterado deixa o estado do objeto inconsistente.
Além disso, métodos chamados dentro de construtores deveriam ser final, caso contrário o
objeto poderia ser criado com estado inconsistente.
Java: final para classes

Hum... legal! Vou então


declarar uma classe como final
aí ninguém pode herdar dela!

Ele está certo! Você pode realmente declarar uma classe como final e dessa
forma impedir que ela se torne uma superclasse.
Exemplo: public final class MinhaClasse { ... }

Mas você acha que isso é útil?

É útil para objetos imutáveis. O que adiantaria projetar uma classe imutável se
alguém pudesse sobrescrever e torná-la mutável?

A classe String por exemplo é final por questões de segurança (para evitar acessos
indevidos a arquivos sem permissão).
Parte 8
Testes e depuração

39
Um ponto muito importante para o
desenvolvimento de software OO é a criação de
objetos bem comportados! ☺

Ou seja, implementar corretamente os objetos sem


erros de sintaxe ou lógica.
Documente seu código! Mas corretamente!

Fazendo o trabalho vocês devem ter percebido que quando


estamos programando, nem sempre as coisas funcionam como
gostaríamos.

Desenvolvedores iniciantes possuem muitas dúvidas na


linguagem, e cometem muitos erros de sintaxe.

Mas esses erros são fáceis de achar pois são apontados


pelo compilador. O problema são os erros lógicos!
Teste e Depuração

Você pode se tornar um desenvolvedor muito experiente que mesmo assim


não conseguirá sempre implementar códigos que funcionam como deveriam
de primeira...

Ou seja, vez ou outra se deparará com Testar se refere a verificar se uma parte
situações nas quais o sistema não do código faz o que deveria ser feito.
está funcionando como deveria. Por
isso são muito importantes as E depurar (ou debugar) se refere a
atividades de Testes e Depuração. descobrir a causa de um erro e corrigí-la.
Teste e Depuração

Existem diversas técnicas de testes e depuração. É muito


importante conhecê-las e saber quando usar cada uma.

Os testes, por exemplo, podem ser de sistema ou


de unidade.

Testes de sistema significam testar o sistema


funcionando, como o usuário final, para verificar
se ele se comporta como esperado.

Já testes de unidade se referem a testar cada parte


do sistema individualmente, para garantir que
aquela parte não tem erros.
Teste e Depuração

Existem diversas técnicas de testes de sistema (ou


integrados), mas elas não são o foco da nossa disciplina.
De qualquer forma, é muito interessante conhecê-las em
disciplinas como Engenharia de Software.

Testes de unidade também são ensinados em Engenharia


de Software. Mas por enquanto vale a pena pelo menos
saber que você pode fazer isso manualmente ou usando
ferramentas automatizadas.
Teste de Unidade

Um teste de unidade manual de uma classe, por exemplo, poderia ser feito da forma
apresentada abaixo.
Imagine que queremos testar uma classe chamada ClasseA.

...
ClasseATester

Imagine que o
sistema possui ClasseA ...
muitas outras
classes e nossa
ClasseA se Vamos criar uma classe auxiliar ClasseATester, que possa gerar um executável (ou
relaciona com seja, possui um método main), e cria objetos da ClasseA e faça chamadas a todos os
algumas delas. seus métodos públicos.

Repare que essa ClasseATester não fará parte do software final. Mas com ela nós
podemos fazer um pequeno programa que testa completamente a ClasseA.
Exemplo de Classe para Teste de Unidade
public class ContaTester {
public static void main(String[] args) { Esse é um trecho da classe que testa uma
Conta conta1 = new Conta(10);
classe Conta.
System.out.println("Testando numero da conta\t");
if (conta1.getNumero() == 10) { Veja que a ideia é criar objetos da classe
System.out.println("[OK]");
}
conta e testar se seu estado se mantém
else { coerente a cada chamada de método
System.out.println("[Erro] " + público.
"Esperado: 10, Retornado: " + conta1.getNumero());
}
Com essa classe pronta, se depois você
System.out.println("Testando saldo inicial da conta\t");
if (conta1.getSaldo() == 0) { precisar fazer qualquer alteração na classe
System.out.println("[OK]"); Conta, basta rodar esse programa
} novamente para ver se não causou
else {
nenhum efeito colateral.
System.out.println("[Erro] " +
"Esperado: 0; Retornado: " + conta1.getSaldo());
}
System.out.println("Testando saldo apos deposito de R$ 100,00\t");
conta1.depositar(100);
if (conta1.getSaldo() == 100) { Veja na página da disciplina a classe
System.out.println("[OK]");
completa no exemplo: Exemplo Agencia-
}
else { Contas v2.
System.out.println("[Erro] " +
"Esperado: 100; Retornado: " + conta1.getSaldo());
Testes Automatizados

Se durante o desenvolvimento ao terminar cada classe nós fizermos testes unitários antes de usá-la nos
sistema, nós diminuímos drasticamente as chances do sistema como um todo ter problema depois.

Isso é essencial em grandes projetos, desenvolvidos por grandes empresas. O


prejuízo causado pelo atraso de projetos que apresentam problemas é
geralmente muito grande.

Testes automatizados na verdade são testes que partem dessa ideia mas nos quais
você implementa também o que se espera de resultado das chamadas dos
métodos. Dessa forma, eles podem ser rodados de forma automática, evitando que
você tenha que executar cada programa de teste de classe manualmente.

Veremos mais adiante na disciplina como criar testes automatizados.

Obs: para os curiosos, podem conferir mais informações no Capítulo 5 do livro do


Barnes e Kolling.
Estratégias de Depuração

Vamos conhecer agora um pouco também das estratégias de depuração.

Imagine que ao testar um software ele não apresentou o


comportamento que era esperado. O que geralmente é a
primeira coisa que pensamos em fazer?

Nossa primeira estratégia de depuração é ler o código


novamente para tentar verificar porque a lógica dele está
errada.

Em sistemas pequenos e simples, nós geralmente já


conseguimos achar o problema fazendo isso.
2ª Estratégia de Depuração: Teste de Mesa

Quando não conseguimos achar o problema apenas lendo o código podemos


partir para a segunda estratégia: teste de mesa e verificação passo a passo.
2ª Estratégia de Depuração: Teste de Mesa

Muitas vezes o sistema possui muitas variáveis ou atributos envolvidos, cujos valores são alterados dentro
da lógica do sistema.

O teste de mesa nada mais é do que montar uma tabela com as chamadas de métodos, os parâmetros e
variáveis, e colocar os valores que estes vão assumindo a cada passo da execução o algoritmo.

Método ParamA VariavelX VariavelY


adiciona 10 true 18
adiciona 10 false 15

Ou seja, vamos lendo o código passo a passo e executando “manualmente” o


código de forma a tentar encontrar em que ponto a lógica está falhando.
3ª Estratégia de Depuração: Explicar para alguém

Nossa terceira estratégia de depuração, pode parecer boba e simples, mas é


extremamente útil na prática: tente explicar para alguém como seu método funciona.
3ª Estratégia de Depuração: Explicar para alguém

Quando não conseguimos encontrar o erro lógico em um código


podemos pedir apoio, chamando alguém para nos ajudar a descobrir
porque ele não está funcionando.

Mas é extremamente comum que, ao fazermos isso, nós mesmos


descubramos o problema.
Ou seja, o fato de ter que organizar nosso pensamento para explicar
para outra pessoa já nos faz perceber o que estava errado no código.

Portanto, não subestime o poder do trabalho em equipe e tenha sempre contato


próximo com os colegas.
Obs: caso esteja trabalhando sozinho, você pode tentar explicar para você mesmo.
4ª Estratégia de Depuração: Comandos de impressão

A quarta estratégia que provavelmente muitos de vocês já fizeram


é a inclusão de comandos de impressão ao longo do código.
4ª Estratégia de Depuração: Comandos de impressão

A ideia é colocar imprimir na saída padrão informações que nos permitam acompanhar:
• A sequência de chamadas dos métodos.
• Os valores dos parâmetros.
• Os valores de variáveis locais e atributos em pontos estratégicos.

Essa estratégia é útil pois não depende de nenhum recurso especial da linguagem e
nem de alguma IDE específica. Podendo ser facilmente utilizado em qualquer situação.

Para quem programa em C++ é


o famoso:
cout << “cheguei aqui” << endl;

Lembre-se que essas impressões não devem aparecer no software final.


Portanto devem ser apagados ou comentados, ou pode-se criar uma lógica
para que sejam exibidas apenas enquanto o sistema estiver em depuração.
5ª Estratégia de Depuração: Usar depurador da IDE

Por fim, nossa última estratégia de depuração é usar um depurador


(ou debugger) da IDE de desenvolvimento que estamos utilizando.
5ª Estratégia de Depuração: Usar depurador da IDE

Um depurador é uma ferramenta que nos permite escolher um ponto no código no


qual a execução do programa será pausada (breakpoint), e a ferramenta então nos
permitirá:
• Ver o estado dos objetos e os valores de variáveis em um dado momento.
• Executar os próximos comandos do programa passo a passo.
• De modo que consigamos acompanhar as mudanças nos estados dos objetos
e nos valores de variáveis.
• Ver a pilha de execução, ou seja, a sequência de chamadas realizadas até aquele
ponto.
5ª Estratégia de Depuração: Usando depurador do Eclipse

Pilha de Execução
Aqui podemos verificar os valores dos estados dos
objetos, dos parâmetros e variáveis locais.

Todas as IDEs de uso


profissional possuem
depurador integrado.
Aqui vemos um
exemplo do Eclipse.

Breakpoint
Estratégias de Depuração

Temos, portanto, diversas


estratégias de depuração
de código:
A melhor estratégia depende muito de cada caso.
• Examinar / ler o código. Geralmente, usamos cada uma delas em uma
determinada situação.
• Teste de mesa e verificação
passo a passo. • Ao fazer um código pela primeira vez geralmente
fazemos a primeira ou a segunda.
• Explicar o problema para
alguém. • Muitas vezes usamos o depurador.

• Instruções de impressão. • Mas há situações que não podemos usar o


depurador. Muitas vezes o erro só ocorre no
• Uso de depurador. cliente e o uso de instruções de impressão
(gerando logs de erros) pode ser a melhor saída.
• E explicar para alguém um erro muitas vezes pode
ser a forma mais fácil de resolver.

Вам также может понравиться