Safe Navigation Operator (&.) em Ruby

Artigos - 23/Jul/2020 - por André Kanamura

Em programação orientada a objetos, o Safe Navigation Operator pode ser útil para evitar checagens sequenciais de objeto nulo. Ele está presente em várias linguagens de programação e em Ruby ele foi incorporado como &.. Vamos compreender melhor como ele funciona.

Considere, por exemplo, uma aplicação em que usuários (User) possuem um perfil (Profile) com alguns atributos (nome, sobrenome, cidade, etc). Quando tentamos obter o nome no perfil de um determinado usuário pode ser levantado um erro se user ou profile forem nulos:

class User
# código da classe
end

class Profile
# código da classe
end

user = User.new(profile: nil)
user.profile.name
# => NoMethodError (undefined method `name' for nil:NilClass)

Se quisermos fazer o mesmo de forma mais segura, evitando chamar métodos em objetos nulos, seria necessário executar uma série de checagens como:

user && user.profile && user.profile.name
# => nil

Outra forma de obter o mesmo resultado seria utilizando o método try:

user.try(:profile).try(:name)
# => nil

É importante lembrar que o try é um método do Active Suport e por isso só funciona no Rails ou se você adicionar a gem.

O Safe Navigation Operator retorna nil se o primeiro argumento for nulo ou executa a operação do segundo argumento. Dessa forma ele ajuda a evitar as checagens sequenciais:

user&.profile&.name
# => nil

Apesar de tornar o código mais enxuto, existem algumas motivações para evitarmos seu uso. O motivo mais facilmente perceptível é que o &. torna o código mais difícil de ser lido e compreendido. Sabemos que algum elemento na sequência pode ser nulo, mas não sabemos o motivo. Existem formas de evidenciar o que está acontecendo por trás do código implementado, como por exemplo definir métodos com nomes claros que validam a presença de objetos:

class User
# código da classe
  def has_profile?
    profile.present?
  end
end

Vale também destacar que o &. e try nem sempre vão funcionar da mesma maneira. Por exemplo, o que acontece se o objeto não responder ao método chamado?

user = User.new(profile: Profile.new)
user.try(:profile).try(:passport_number)
# => nil

user&.profile&.passport_number
# => NoMethodError (undefined method `passport_number' for #<Profile:0x000055d67aa86780>)

O try não verifica se o objeto responde ao método, retornando apenas nil. O &. levanta o NoMethodError uma vez que passport_number não existe para profile neste exemplo.

O Safe Navigation Operator pode ajudar tornando o código mais enxuto, mas devemos atentar para as diferentes situações em que é aplicado para evitarmos problemas. Ele pode ser especialmente útil para prototipar algo de forma rápida ou para realizar testes dentro do IRB. No entanto, se houver tempo hábil, é possível utilizar alternativas como implementar o Null Object Pattern, por exemplo.

Referências

Foto de perfil do autor
André Kanamura

Dev na Campus Code