Null, pra que te quero?

O Null Pattern, também conhecido como Active Nothing, é um padrão onde criamos classes para representar a ausência de valor, ou seja, utilizamos objetos ao invés de null.

Antes de discutirmos esse padrão, precisamos entender o que há de errado com passar e retornar null em nossos códigos.

Qual é o problema do null?

Toda vez que passamos ou retornamos null, precisamos espalhar vários testes pelo nosso sistema para checar se o valor contém alguma coisa antes de ser utilizado:

Isso aumenta consideravelmente a quantidade de trabalho para manter um sistema, pois em todos os lugares onde usamos ou aceitamos null, precisamos checar pelo valor recebido como um argumento ou retornado por um método.

Além disso, ele torna nosso código muito frágil, pois qualquer null introduzido em um local inesperado pode causar erros do tipo “call to a member function foo() on null”, ou até mesmo problemas que podem passar despercebidos, como assumir o número zero ao invés de null em um cálculo.

Sir Tony Hoare, a pessoa responsável por introduzir o conceito de ponteiros nulos em 1965, se desculpa por ter feito isso dizendo:

“Eu chamo isso de meu erro de um bilhão de dólares. […] Na época, eu estava projetando o primeiro sistema de tipos para referências em uma linguagem orientada a objetos (ALGOL W). Meu objetivo era garantir que todas as referências fossem absolutamente seguras, com a checagem realizada automaticamente pelo compilador. No entanto, eu não pude resistir à tentação de colocar uma referência nula, simplesmente porque era fácil demais de implementar. Isso levou a incontáveis erros, vulnerabilidades e quedas de sistemas, que provavelmente causaram um bilhão de dólares de dor e danos nos últimos quarenta anos.”

Testes não resolvem o problema?

Hoje em dia costumamos escrever testes automatizados, o que diminui a possibilidade de introduzir erros por causa do null, mas ainda somos obrigados a poluir o código com essas checagens de "is_null", "isset", "empty", etc.

As checagens por null se multiplicam. Toda vez que você chama aquele mesmo método que retorna null, você precisa fazer as mesmas verificações (if is null…) para evitar que o sistema quebre.

Além disso, você precisa escrever ainda mais testes unitários para cobrir os casos com o valor null.

PHP 7 com tipagem rigorosa

No PHP 7, se quisermos definir os tipos de retornos dos métodos e ativar a flag strict types para checar os tipos rigorosamente, não conseguimos retornar null no lugar de outro tipo.

Por exemplo, no código abaixo, o método getUser() só pode retornar uma instância de User. O valor null não é aceito como um retorno válido. Se você tentar retornar null, o PHP lança uma exceção do tipo TypeError:

Veremos três alternativas bastante difundidas para resolver os problemas do null: falhar rapidamente e os padrões do objeto nulo e do “talvez”.

1. Falhar Rapidamente

Falhar rapidamente é lançar uma exceção assim que for detectado um valor null, tratando essa exceção no cliente que invoca o método:

Essa maneira tem a vantagem de ser mais explícita, é muito mais fácil de encontrar o erro com a mensagem “User is not authenticated” do que com “Uncaught TypeError: Return value of getUser() must be an instance of User, null returned”.

Infelizmente, continuamos tendo a mesma quantidade de trabalho ou mais, pois só trocamos nossas checagens de is_null para blocos de try/catch. Essa é a melhor alternativa quando realmente precisamos do valor, quando não faz sentido prosseguir com a execução do código com um “valor provisório”.

2. Padrão do “Talvez”

Outra alternativa é criar ou utilizar uma biblioteca que implemente o Maybe Pattern. A ideia deste padrão é encapsular o acesso ao método ou propriedade em um objeto do tipo “talvez”. Esse objeto retorna o valor do método ou propriedade somente se não for null, caso contrário, retorna o valor pré-determinado.

Isso soa um pouco complexo, mas é muito simples, considere o exemplo abaixo, que utiliza a biblioteca pirminis/maybe-monad:

O problema deste método é que ele é bastante verboso e continuamos espalhando os testes por null em todo o nosso código, pois sempre precisamos chamar ->val() para informar o retorno alternativo.

Você poderia criar sua própria biblioteca que implementa este padrão e centralizar o valor alternativo quando um objeto é null, mas isso se aproximaria muito da nossa terceira alternativa, porém com mais trabalho.

3. Padrão do Objeto Nulo

A ideia deste padrão é que nada é alguma coisa. Precisamos representar essa ausência de valor de alguma forma mais significativa, criando uma classe que representa a ausência de valor.

A primeira questão que precisamos resolver ao implementar esse padrão é pensar em um bom nome para representar nossa classe, como por exemplo:

Depois disso, tudo o que precisamos fazer é implementar os mesmos métodos que a classe original implementa, porém retornando valores apropriados para um valor nulo, como por exemplo:

Como nunca teremos um valor null, mas sim ou o objeto original ou o objeto que representa a ausência dele, nunca precisamos checar por null: basta invocar os métodos que o sistema funcionará normalmente.

Exemplo de Aplicação

No exemplo abaixo, um usuário pode ou não pagar por uma assinatura em um site. Caso ele seja um assinante, a assinatura é associada com o seu perfil, caso contrário, ela simplesmente tem o valor null:

Vamos refatorar o código acima introduzindo uma classe chamada “AssinaturaGratis” para nos referirmos à ausência de uma assinatura vinculada à conta do usuário:

Alternativamente, poderíamos criar uma interface que as duas classes implementam, ao invés da classe AssinaturaGratuita estender de Assinatura.

Agora que temos nosso objeto nulo, podemos remover todos os "if's" das assinaturas e invocar os métodos dela normalmente, sabendo que ela nunca será null:

Todos os "if ($assinatura)" foram removidos, e agora chamamos $this->assinatura diretamente sem medo.

Formas de Implementar a Classe Nula

Para implementar o padrão, é importante considerar como isso será feito: se você prefere criar uma interface e fazer ambas as classes implementarem-na ou se fará a classe nula estender a original, ou vice-versa:

Apesar de ter utilizado a segunda forma nos exemplos para mantê-los simples, particularmente prefiro utilizar interfaces, pois assim evito problemas caso os métodos da classe-mãe mudem no decorrer da vida do programa.

Conclusão

Utilizar objetos nulos é uma ótima forma de reduzir a complexidade do nosso código e tornar o nosso trabalho mais feliz.

Como na vida nem tudo são flores, eventualmente precisamos apelar ao bom e velho null, pois nem sempre será uma boa ideia introduzir um objeto ou lançar uma exceção na ausência de um valor.

Referências

A imagem da capa foi recortada a partir de uma foto de Michael Hull.

--

--

Software development nerd. In 💙 with Ruby, PHP, JavaScript, Crystal, and other techy stuff.

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store