concepto-null-undefined-none

Null, Undefined, None y sus problemas en programación

  • 6 min

En programación, los conceptos de null, nothing, undefined y none son fundamentales para manejar la ausencia de valores.

Parace una cosa sencilla, que la mayoría de programadores usan todos los días. Pero, en realidad, el concepto de “ausencia de valor” es sorprendentemente complejo y ha sido fuente de innumerables debates y dolores de cabeza.

Recordemos que habíamos dicho que una variable es una caja. En el mundo físico, si tienes una caja, esta puede tener algo dentro o estar vacía. Es un concepto sencillo.

Pero, en programación, una caja vacía no suele ser una caja vacia. Es una caja que contiene ‘nada’. Es decir, contiene esto

black-hole

Un agujero negro que amenaza con destruir el universo y… colgar tu programa

El “Error del billón de dólares”

Antes de entrar en cómo funciona en cada lenguaje, remarcamos. El null tiene muy mala fama (y con razón).

El concepto de referencia nula fue introducido en 1965 por Tony Hoare (creador del algoritmo QuickSort) mientras diseñaba el lenguaje ALGOL W. Décadas después, en una conferencia, pidió disculpas públicamente diciendo:

“Lo llamo mi error del billón de dólares. […] Simplemente, no pude resistir la tentación de poner una referencia nula, simplemente porque era muy fácil de implementar. Esto ha provocado innumerables errores, vulnerabilidades y fallos del sistema.” — Tony Hoare

El problema fundamental es que null rompe el sistema de tipos. Si tienes una variable de tipo Alumno, esperas que tenga alumno. No una bomba de relojería esperando explotar.

Pero si vale null, y tratas de pedirle su longitud (alumno.length), tu programa explota. Es el famoso NullReferenceException o Segmentation Fault.

Para evitar este error, que es el error que más veces aparece en programación, los programadores se ves obligados a “blindar” sus códigos con comprobaciones como.

if(alumno == null) return;
Copied!

La “Nada” en los diferentes lenguajes

Dependiendo del lenguaje que uses, te encontrarás con null, undefined, None, nil o nullptr. Todos intentan decir lo mismo “esta variable contiene nada”.

Vamos a ver cómo aborda cada ecosistema este problema, porque hay matices importantes.

En los lenguajes de bajo nivel, las variables son direcciones de memoria. Aquí, la ausencia de valor es literalmente el número 0.

  • NULL: En C clásico, NULL es simplemente una macro que vale 0. Es decir, la dirección de memoria 0.
  • nullptr: En C++ moderno (C++11 en adelante), se introdujo nullptr. Esto soluciona problemas de ambigüedad. Mientras que NULL podía confundirse con el número entero 0, nullptr es un tipo propio seguro.
// C++
int* puntero = nullptr; // El puntero no apunta a ninguna dirección válida
Copied!

Si intentas leer lo que hay en la dirección de memoria de un nullptr, el Sistema Operativo detiene tu programa inmediatamente para evitar que corrompas la memoria.

En estos lenguajes orientados a objetos, distinguimos entre

  • Tipos por valor (como un int)
  • Tipos por referencia (como una class).

Tradicionalmente, un int nunca puede ser null (siempre es 0 o un número). Pero cualquier objeto (una string, un Usuario) es una referencia que puede apuntar a un objeto en el Heap o a null.

// C#
string nombre = null; // Válido
int edad = null;      // ¡Error de compilación! (en versiones antiguas)

Copied!

Aquí null significa “esta variable de referencia no está asociada a ninguna instancia de objeto”.

Python tiene una aproximación muy elegante. Aquí no existe null, existe None.

La gran diferencia es que None es un objeto en sí mismo. Es la única instancia de la clase NoneType.

# Python
valor = None

if valor is None:
    print("No hay nada")

Copied!

Al ser un objeto real, None se comporta de manera más predecible que el puntero nulo de C++.

JavaScript, en su infinita peculiaridad, decidió que tener una sola forma de representar la nada era poco, así que tiene dos:

  • undefined: Significa “esta variable ha sido declarada, pero aún no se le ha asignado valor”. Es el valor por defecto de las cosas.
  • null: Significa “esta variable tiene intencionalmente una ausencia de valor”.
let a;
console.log(a); // undefined (el sistema me dice que está vacío)

let b = null;
console.log(b); // null (yo, programador, digo que esté vacío)

Copied!

Intentando arreglar el error: Opcionales y Nullables

Dado que el null causa tantos cierres inesperados de aplicaciones, los lenguajes modernos han desarrollado mecanismos para obligarnos a gestionar la ausencia de valor.

Lenguajes como C# o Kotlin introducen el concepto de tipos nulables. Por ejempli C# moderno introdujo una distinción estricta.

Ahora, si quieres que algo pueda ser nulo, tienes que marcarlo explícitamente con una interrogación ?.

string  texto  = null; // Warning o Error: ¡Esto no debería ser nulo!
string? texto2 = null; // Correcto: He dicho explícitamente que puede serlo.

Copied!

El tipo Optional (Java, C++, Rust) es una solución más funcional. En lugar de devolver un objeto o null, envuelves el objeto en una “caja” llamada Optional.

La caja puede estar llena o vacía. Para usar el valor, estás obligado a comprobar primero si la caja tiene algo.

// C++ std::optional
std::optional<int> buscarValor() { ... }

auto resultado = buscarValor();
if (resultado.has_value()) {
    // Usamos el valor
}

Copied!

Esto elimina la sorpresa. Ya no asumes que el valor existe; el tipo de dato te obliga a gestionar el caso vacío.

Para facilitar la vida, muchos lenguajes han añadido Operadores de “Navegación Segura” para trabajar con nulos sin llenar el código de if (x != null).

  • Null Coalescing (??): “Si es nulo, usa este valor por defecto”.
let nombre = inputUsuario ?? "Anónimo"; 

Copied!
  • Optional Chaining (?.): “Si es nulo, no sigas y devuelve nulo, pero no explotes”.
// Si usuario es null, no intenta acceder a dirección.
// Si dirección es null, no intenta acceder a calle.
var calle = usuario?.direccion?.calle; 

Copied!