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.