Variáveis opcionais
Algum tempo atrás publiquei um artigo falando sobre a história do null e seus potenciais problemas. Então, já sabemos que o null ficou conhecido como o erro de um bilhão de dólares. Cheguei a citar que era possível desenvolver softwares sem utilizar este recurso, mas não entrei em detalhes. Agora vou explicar como podemos utilizar o optional.
Existem várias situações do cotidiano em que encontro colegas de profissão utilizando o nulo, mas as duas principais são: quando querem expressar a ocorrência de uma falha durante um processamento ou para representar a ausência de um valor. Neste artigo vou me concentrar no segundo caso.
O conceito de vazio
Ao desenvolver um software é comum nos depararmos com cenários onde determinadas variáveis podem ou não referenciar algum valor. Podemos dividir este problema em duas categorias: a primeira quando podemos representar os dados através de um conjunto de elementos e a segunda quando trata-se de um único elemento, que pode ou não estar presente.
Conjunto vazio
Quando a variável em questão pode ser representada por um conjunto, então o problema de representa-la como vazia passa a ser solucionado de forma trivial: basta criar uma estrutura sem nenhum elemento. Normalmente utilizamos estruturas de dados como lists, sets, arrays, dependendo da linguagem de programação e do contexto do problema. Mas o importante é que estas estruturas podem conter zero elementos.
Imagine que estivéssemos desenvolvendo um sistema bancário onde uma correntista pode obter empréstimos. Para representar que uma pessoa não possui dívidas, basta retornarmos uma coleção vazia. No exemplo da Fig. 2, João possui 7 dívidas e Maria zero. Para implementar esta solução poderíamos utilizar, por exemplo, uma coleção contendo 7 referências às dívidas de João e uma segunda coleção, sem nenhum elemento, representando a ausência de dívidas de Maria. Desta forma eliminamos o uso do null, pelo menos nestes cenários.
Um único elemento opcional
A solução apresentada acima funciona bem quando estamos trabalhando com uma coleção de elementos, que ficam encapsulados em uma estrutura maior, capaz de representar a situação de ausência de valores. Assim, conseguimos implicitamente ter uma noção de estado: o conjunto pode estar vazio ou pode conter um ou mais elementos. Mas e quando trata-se de um único elemento?
Suponha que você precise implementar um sistema em que durante o cadastro a usuária pode ou não digitar o número do ramal de seu telefone. Ou seja, esta informação é opcional. Poderíamos solucionar este problema da mesma forma que o exemplo anterior se o número do ramal fosse uma coleção: teríamos um conjunto de números de ramais associados à cada usuária, que poderia conter zero elementos, representando assim a ausência de um valor cadastrado. Mas e se o requisito do sistema especificar que uma pessoa pode ter no máximo um ramal? Então precisamos restringir o cadastro para 0 ou 1 número de ramal de telefone por usuária.
Com um pouco de imaginação, é possível encarar este problema ainda como um conjunto de dados. Pense em uma coleção que pode conter no máximo um elemento. Esta coleção teria duas possibilidades de estados: ou não teria nenhum elemento (representando uma situação onde o número de telefone não foi informado) ou teria um único elemento, representando o número do ramal. No exemplo da Fig. 3 temos uma situação onde Maria digitou um número de ramal representado por r1 e João não possui um número de ramal registrado. Temos então dois conjuntos de dados: um contendo um ramal e outro vazio.
Em teoria, poderíamos resolver este problema com uma estrutura de dados convencional, como um set, array ou list. Precisaríamos apenas nos certificar ao longo do código que a estrutura em questão não ultrapasse o limite de 1 elemento. Uma forma de assegurar esta regra seria criando a nossa própria estrutura de dados, que seria muito similar a uma coleção, mas adicionando a restrição de permitir apenas 0 ou no máximo 1 elemento. Felizmente muitas linguagens de programação já possuem uma implementação similar a esta, normalmente conhecidas como Optional ou Maybe.
Em Java, por exemplo, a partir da versão 8 da JDK temos disponível a classe Optional. Uma instância desta classe pode estar em um de dois possíveis estados: ou o objeto está no estado empty ou está encapsulando um objeto que obrigatoriamente não é nulo. Isso permite representar a ausência de valor onde antes utilizávamos uma referência para o null. Voltando ao exemplo da Fig. 3, em Java poderíamos implementar o ramal como sendo do tipo Optional<Integer>. Isso significa que a variável em questão pode estar vazia ou pode conter uma referência para um objeto do tipo Integer.
A solução que descrevi acima é uma das formas de representarmos a ausência de um valor sem utilizar o nulo, mas existem outras maneiras. Algumas linguagens que possuem um sistema de tipagem de dados mais avançado (como Haskell ou Scala) permitem a criação de tipos de dados que representam esses dois estados (vazio ou com um valor não nulo) de forma muito simples. Mas esta abordagem já extrapola o escopo deste artigo.
O uso do interrogação para representar variáveis opcionais
Algumas linguagens de programação, como Kotlin e Swift, optaram por manter a referência ao null, mas para que uma variável possa fazer esta referencia é necessário indicar explicitamente esta possibilidade em sua declaração, adicionando o símbolo de interrogação após a identificação de seu tipo. No exemplo abaixo, escrito em Kotlin, temos duas variáveis. A primeira, nome, é do tipo String e não pode ser nula (esta verificação é feita em tempo de compilação). Já a segunda, ramal, definida para ser do tipo Integer?, pode ter seu valor referenciado para o nulo.
var nome: String = "Marcio"
var ramal: Integer? = 22ramal = null // ramal pode ser nulo
nome = null // esta linha lançará um erro durante a compilação!
Esta abordagem não exclui por completo a possibilidade de erros em tempo de execução, mas torna explícito os pontos onde a pessoa desenvolvedora precisa tomar as devidas ações necessárias para tratar as situações em que uma variável pode ser nula.
Conclusão
Neste artigo apresentei algumas práticas para evitar o uso do nulo. Embora apenas esta mudança já traga alguns benefícios (diminuindo o risco de enfrentarmos falhas em tempo de execução devido a referências nulas), os ganhos se ampliam exponencialmente quando associados a outras técnicas provenientes do paradigma funcional, como o uso do map, filter, fold, … mas isso vai ter que ficar para os próximos artigos.
Obrigado por ler até aqui! Se gostou deste texto, tenho mais alguns publicados em minha página do medium e também mantenho um podcast onde falo sobre livros que impactaram na minha carreira.