HE:labs

Postado por Mauro George em 07/04/2013

Remova Ifs e Elses em Ruby utilizando o Null Object Pattern

O Null Object pattern tem como principal objetivo tratar o comportamento do valor Null, ou em ruby o nil. Sabemos que em algum momento de nosso código podemos ter referências nulas, sendo assim, temos que verificar se tal refêrencia é nula ou não para não recebermos uma exception como a seguinte:

NoMethodError: undefined method `name' for nil:NilClass

Vamos a um exemplo: Temos uma classe Game responsável pelas informações de um dado jogo e um classe Report, responsável por imprimir os dados deste tal jogo.

Vamos a classe Game:

1 require 'ostruct'
2 
3 class Game < OpenStruct
4 end

Uma simples classe que herda de OpenStruct.

Agora vamos a nossa classe Report:

 1 class Report
 2 
 3   def initialize(game)
 4     @game = game
 5   end
 6 
 7   def show
 8     %Q{Game: #{name}
 9     Platform: #{platform}
10     Description: #{description}}
11   end
12 
13   private
14 
15     def name
16       @game.name
17     end
18 
19     def platform
20       @game.platform
21     end
22 
23     def description
24       @game.description
25     end
26 end

Como podem ver, a nossa classe funciona muito bem para o Game. No entanto, se em algum momento recebermos uma referência nula (por exemplo de um find do ActiveRecord), receberemos a seguinte exception:

1 game = nil
2 report = Report.new(game)
3 puts report.show # undefined method `name' for nil:NilClass (NoMethodError) ...

Vamos resolvê-la utilizando os mais comuns: if e else.

 1 class Report
 2 
 3   def initialize(game)
 4     @game = game
 5   end
 6 
 7   def show
 8     %Q{Game: #{name}
 9     Platform: #{platform}
10     Description: #{description}}
11   end
12 
13   private
14 
15     def name
16       if @game
17         @game.name
18       else
19        'no name'
20       end
21     end
22 
23     def platform
24       if @game
25         @game.platform
26       else
27         'no platform'
28       end
29     end
30 
31     def description
32       if @game
33         @game.description
34       else
35         'no description'
36       end
37     end
38 end

Como podem ver, alteramos os métodos da classe Report, responsável por criar os campos de Game na exibição do relatório, para tratar quando recebemos um valor nil. Até funciona, mas como notamos, estamos adicionando mais complexidade a simples métodos que apenas delegam o valor, além de, claramente, estarmos repetindo código. E é neste ponto que o Null Object Pattern vem para nos ajudar. Vamos aos refactories.

 1 class NullGame
 2 
 3   def name
 4     'no name'
 5   end
 6 
 7   def platform
 8     'no platform'
 9   end
10 
11   def description
12     'no description'
13   end
14 end
15 
16 class Report
17 
18   def initialize(game)
19     @game = game || NullGame.new
20   end
21 
22   def show
23     %Q{Game: #{name}
24     Platform: #{platform}
25     Description: #{description}}
26   end
27 
28   private
29 
30     def name
31       @game.name
32     end
33 
34     def platform
35       @game.platform
36     end
37 
38     def description
39       @game.description
40     end
41 end

Primeiro criamos uma classe NullGame responsável por definir os valores quando um Game for nulo. Em Seguida, alteramos a classe Report para instanciar um NullGame (caso o game seja nulo) e assim, podemos alterar nossos métodos que funcionam como delegators para continuarem fazendo apenas isto. Veja como seria o comportamento de Report ao receber um nil:

1 game = nil
2 report = Report.new(game)
3 puts report.show # Game: no name
4                  # Platform: no platform
5                  # Description: no description

Como podem ver, ao utilizarmos o Null Object Pattern, conseguimos manter o nosso código muito mais Ruby Way utilizando classes coesas, com responsabilidades bem definidas e one line methods.

Para finalizar fica a dica da excelente palestra do Ben Orenstein Refactoring from Good to Great , de onde este post foi inspirado.

Comentários
Included file post/disqus_thread.html not found in _includes directory