Refatoração de Código: Introduzir Objeto Nulo
(2110)
CategoriasAndroid, Design, Protótipo
AutorVinÃcius Thiengo
VÃdeo aulas186
Tempo15 horas
ExercÃciosSim
CertificadoSim
CategoriaEngenharia de Software
Autor(es)Kent Beck
EditoraNovatec
Edição1ª
Ano2024
Páginas112
Tudo bem?
Neste artigo continuamos com a série Refatoração de Código, para códigos de alta performance.
Dessa vez vamos falar sobre o método de refatoração Introduzir Objeto Nulo.
Ressalto que os métodos de refatoração e padrões de projeto dessa série podem ser utilizados em qualquer software no paradigma orientado a objetos.
Antes de prosseguir com o conteúdo desse artigo é importante que você saiba o que é o padrão de projeto Objeto Nulo.
No artigo Padrão de Projeto: Objeto Nulo falo somente sobre esse padrão, que é bem simples de entender e utilizar.
Antes de prosseguir, não esqueça de se inscrever na 📫 lista de e-mails do Blog para receber em primeira mão todos os conteúdos exclusivos sobre desenvolvimento e codificação limpa.
Abaixo os tópicos que estaremos abordando em artigo:
- Método de refatoração Extrair Subclasse;
- Motivação;
- Código de exemplo;
- Mecânica;
- Conclusão;
- Outros artigos da série;
- Fonte.
Método de refatoração Extrair Subclasse
Já explicado em outros artigos, mas caso ainda não conheça esse método de refatoração, segue explanação.
Método bem simples e dispensa um artigo somente para ele.
Esse é aplicado quando temos classes respondendo a mais de uma tarefa principal e com isso é possível, na maioria dos casos, extrair ao menos duas novas subclasses (somente uma também é válido).
Digo subclasses, pois alguns comportamentos (métodos) e estados (atributos) ainda serão compartilhados.
Logo, para evitar repetição de código, e manter um contexto consistente, se tornam em subclasses, as novas classes.
Motivação
Seu código tem vários trechos de verificação de valor nulo:
if( variavel != null ){
variavel.fazAlgumaCoisa();
}
E esses trechos não têm um significado importante na lógica de negócio de seu sistema.
Significado importante?
Sim, quando parte da lógica requer que ou exista um objeto ou seja nulo.
Nesse caso não se utiliza o padrão de projeto Objeto Nulo, pois a lógica vai trabalhar também com valores nulos.
Esses scripts de verificação de null deixam seu código inflado e de certa forma atrapalha o crescimento do projeto quando outros programadores não souberem se devem ou não sempre trabalhar com verificação de valores nulos.
Nesse contexto, aplicar o padrão Objeto Nulo pode aliviar em muito o atraso na evolução do sistema.
Pois o código para quando há ou não o objeto esperado será exatamente o mesmo, sem condicionais de verificação de null.
Código de exemplo
Vamos utilizar como exemplo um código antigo (que utiliza Applets Java).
O código é parte de um algoritmo de um Web site que fazia com que o Menu do site funcionasse utilizando Applets Java para capturar os eventos de mouse dos usuários.
Mais precisamente: Applet com uma variável do tipo MouseEventHandler para manipular os eventos de mouse.
Segue trecho do código:
public class NavigationApplet extends Applet {
private MouseEventHandler mouseEventHandler;
...
public boolean mouseMove(
Event event,
int x, int y ){
if( mouseEventHandler != null ){ /* VERIFICAÇÃO DE NULL */
return mouseEventHandler.mouseMove( context, event, x, y );
}
return true;
}
public boolean mouseDown(
Event event,
int x,
int y ){
if( mouseEventHandler != null ){ /* VERIFICAÇÃO DE NULL */
return mouseEventHandler.mouseDown( context, event, x, y );
}
return true;
}
public boolean mouseUp(
Event event,
int x,
int y ){
if( mouseEventHandler != null ){ /* VERIFICAÇÃO DE NULL */
return mouseEventHandler.mouseUp( context, event, x, y );
}
return true;
}
public boolean mouseExit(
Event event,
int x,
int y ){
if( mouseEventHandler != null ){ /* VERIFICAÇÃO DE NULL */
return mouseEventHandler.mouseExit( context, event, x, y );
}
return true;
}
...
}
Se você pensou algo como: Mas não faço a mínima ideia do que seja um Applet!
Não há problemas, nem mesmo vou explicar ele aqui, pois, além de não atrapalhar no entendimento da refatoração, esse recurso não mais é utilizado.
O que tem de entender é como utilizar o padrão de projeto Objeto Nulo em um código já existente, isso aplicando o método proposto nesse artigo.
Como curiosidade, o Applet, para Web sites, era algo muito similar ao que é o Ajax hoje.
Bom, com isso podemos prosseguir com a refatoração em si.
Mecânica
Nosso primeiro passo no código de exemplo é aplicar a refatoração Extrair Subclasse (a explicação desse método de refatoração está logo no início do artigo, ele é bem simples).
Mas extrair subclasse de onde?
De nossa classe fonte.
A classe fonte é a classe na qual vamos criar uma classe Objeto Nulo.
Em nosso caso, a classe fonte é a MouseEventHandler.
Note que não há necessidade de apresentar o código dessa classe aqui e sim o código de alguma classe cliente, como fizemos com a classe NavigationApplet.
Com isso, ainda no primeiro passo da refatoração, temos a nova classe, a classe que representa o Objeto Nulo, NullMouseEventHandler:
public class NullMouseEventHandler {
public NullMouseEventHandler( Context context ){
super( context );
}
}
Veja que já implementamos o construtor e que ele têm um parâmetro de entrada para preencher o construtor de MouseEventHandler.
Esse parâmetro é opcional.
Não precisamos desse construtor com parâmetro de entrada, apesar de ser uma boa prática deixar todos os métodos e construtores públicos da classe de objeto nulo com a exata mesma interface da classe herdada ou Interface implementada (essa última você não tem escolha, terá de ter a mesma interface).
Logo, o código da classe NullMouseEventHandler abaixo, também seria válido:
public class NullMouseEventHandler {
public NullMouseEventHandler(){
super( null );
}
}
Mesmo sabendo que você seguiu a dica do início do artigo e primeiro leu do Objeto Nulo, ressalto que poderíamos ter implementado uma Interface ao invés de herdado uma classe.
Nesse caso, a Interface deveria ser a responsável por todos os métodos públicos em MouseEventHandler, caso contrário poderíamos estar criando uma.
Aqui vamos seguir somente com a herança de nossa classe fonte.
Seguramente podemos prosseguir para o segundo passo da refatoração.
Nesse passo dois, devemos buscar ao menos uma verificação de null para nossa variável de instância mouseEventHandler.
Nada de difícil, logo em mouseMove() temos essa verificação:
...
public boolean mouseMove(
Event event,
int x,
int y ){
if( mouseEventHandler != null ){ /* VERIFICAÇÃO DE NULL */
return mouseEventHandler.mouseMove( context, event, x, y );
}
return true;
}
...
O que devemos entender nesse passo da mecânica é que o método mouseMove() de mouseEventHandler deve ser implementado pela classe de objeto nulo, NullMouseEventHandler, e o conteúdo dessa implementação deve ser o código alternativo executado caso mouseEventHandler seja null.
Nesse caso é o simples return true.
Segue código de NullMouseEventHandler atualizado:
public class NullMouseEventHandler {
...
public boolean mouseMove(
MetaGraphicsContext mgc,
Event event,
int x,
int y ){
return true;
}
}
No terceiro passo temos que aplicar o passo dois em todas as outras verificações de null do projeto para a variável do tipo MouseEventHandler.
Logo, a classe NullMouseEventHandler agora fica da seguinte forma:
public class NullMouseEventHandler {
...
public boolean mouseMove(
MetaGraphicsContext mgc,
Event event,
int x,
int y ){
return true;
}
public boolean mouseDown(
MetaGraphicsContext mgc,
Event event,
int x,
int y ){
return true;
}
public boolean mouseUp(
MetaGraphicsContext mgc,
Event event,
int x,
int y ){
return true;
}
public boolean mouseExit(
MetaGraphicsContext mgc,
Event event,
int x,
int y ){
return true;
}
}
Você provavelmente deve estar se perguntando:
E se não houvesse códigos caso mouseEventHandler fosse null? O que colocaria como conteúdo desse método em NullMouseEventHandler?
Nesse caso, não colocaria nada.
Um exemplo é o método doMouseClick() que também faz parte do código de exemplo (tinha sido omitido até aqui).
Segue:
public class NavigationApplet extends Applet {
...
public void doMouseClick(
Event event,
String imageMapName,
String APID ){
if( mouseEventHandler != null ){ /* VERIFICAÇÃO DE NULL */
return mouseEventHandler.doMouseClick( imageMapName, APID );
}
}
}
Em NullMouseEventHandler ele ficaria:
public class NullMouseEventHandler {
...
public void doMouseClick(
Event event,
String imageMapName,
String APID ){}
}
Exatamente assim: vazio.
No quarto passo devemos inicializar a variável, local ou de instância, que é utilizada na verificação de valor nulo.
A inicialização deve ser com uma instância da classe de Objeto Nulo.
Porém toda essa alteração no código não pode influenciar em nada no código que atribui uma instância da classe fonte (quando o objeto dessa classe está pronto) a variável que inicializará com uma instância de Objeto Nulo.
Em nosso caso a variável é a mouseEventHendler e logo na declaração dela, dentro da classe, podemos inicializa-la com uma instância de objeto nulo:
public class NavigationApplet extends Applet {
private MouseEventHandler mouseEventHandler = new NullMouseEventHandler( null );
...
}
Note que a instância do objeto nulo atribuída a variável de instância no código anterior não atrapalha em nada a atribuição de um novo valor, mas não nulo, a essa variável.
Somente assegure-se de remover ou atualizar códigos do tipo:
...
if( mouseEventHandler == null ){
mouseEventHandler = getInstanciaRealMouseEventHandle();
}
...
No código anterior, dependendo da lógica de negócio de seu projeto, pode remover o condicional e deixar somente o corpo dele. Caso contrário terá que mudar para algo similar a:
...
if( mouseEventHandler instanceof NullMouseEventHandler ){
mouseEventHandler = getInstanciaRealMouseEventHandle();
}
...
Comentei sobre os códigos anteriores mais para lhe deixar ciente que esse tipo de coisa pode acontecer, nesses casos pode ser que não seja possível remover o condicional.
Isso não indica refatoração ruim, pois muito provavelmente a maioria das verificações de nulo de seu algoritmo é para evitar processamento com variável não inicializada, e esses condicionais poderão sim ser removidos.
Agora, no quinto passo, somente apagamos todas as ocorrências de condicionais de verificação de nulo para nossa variável do tipo da classe fonte, mouseEventHandler.
Note que devemos apagar o condicional e suas chaves de bloco e não o conteúdo do condicional.
Segue nova versão de NavigationApplet:
public class NavigationApplet extends Applet {
private MouseEventHandler mouseEventHandler = new NullMouseEventHandler( null );
public boolean mouseMove(
Event event,
int x,
int y ){
return mouseEventHandler.mouseMove( context, event, x, y );
}
public boolean mouseDown(
Event event,
int x,
int y ){
return mouseEventHandler.mouseDown( context, event, x, y );
}
public boolean mouseUp(
Event event,
int x,
int y ){
return mouseEventHandler.mouseUp( context, event, x, y );
}
public boolean mouseExit(
Event event,
int x,
int y ){
return mouseEventHandler.mouseExit( context, event, x, y );
}
public void doMouseClick(
Event event,
String imageMapName,
String APID ){
mouseEventHandler.doMouseClick( imageMapName, APID );
}
}
O último e sexto passo é nada mais nada menos que a re-aplicação dos passos 4 e 5 em todas as outras classes clientes de objetos do tipo MouseEventHandler e que tenham verificações de null para a variável desse tipo.
Conclusão
Se encontrado em seu código trechos de script verificando o valor null para executar ou não uma tarefa, verifique se a utilização de objetos nulos é aceitável e caso sim, aplique esse padrão por meio do método de refatoração proposto aqui.
Pois dessa forma seu código, além dos ganhos da aplicação do padrão, vai sempre ser o mesmo.
Digo, para objetos com valores reais ou com valores vazio ou padrão (vulgo: objeto nulo).
Somente fique atento com os problemas de utilizar superclasse ao invés de Interface.
Então é isso.
Por fim, não deixe de se inscrever na 📩 lista de e-mails do Blog para receber os conteúdos de desenvolvimento e codificação limpa exclusivos, em primeira mão e também...
... na versão em PDF (versão liberada somente para os inscritos da lista de e-mails).
Abraço.
Outros artigos da série
Abaixo toda a lista de artigos sobre refatoração de código, aqui do Blog:
- Internalizar Singleton;
- Mover Embelezamento Para Decorator;
- Substituir Condicionais que Alteram Estado por State;
- Unificar Interfaces Com Adapter;
- Extrair Adapter;
- Mover Conhecimento de Criação Para Factory;
- Substituir Notificações Hard-Coded Por Observer;
- Substituir Código de Tipo Por Classe;
- Extrair Parâmetro;
- Unificar Interfaces;
- Limitar Instanciação Com Singleton;
- Mover Acumulação Para Parâmetro Coletor;
- Compor Method;
- Formar Template Method;
- Substituir Lógica Condicional Por Strategy;
- Introduzir Criação Polimórfica com Factory Method;
- Encapsular Classes Com Factory;
- Encadear Construtores;
- Substituir Construtores Por Métodos de Criação.
Comentários Facebook