Es tentador usar una sentencia if-else
. Es basica. Es de las primeras cosas que aprendemos. Parece natural, elemental, y un tipo de operacion primitiva, axiomatica, que no se puede dividir mas, o evitar.
Que ingenuos...
La sentencia "else" en un bloque condicional puede ocasionar problemas, e incluso, muchisimas veces (por no decir la mayoria) ni siquiera es necesario.
Podemos identificar estos problemas si somos adictos a la sentencia else
:
El codigo debe estar optimizado para la lectura de otras personas en el futuro, en lugar de para que se ejecute mejor en las computadoras (ya no es 1990, tenemos N cores en los CPU y GBs de RAM).
Podemos adherir a la siguiente afirmacion:
“Los programas estan hechos para ser leidos por seres humanos, y solo de paso para ser ejecutados por computadoras"
Donald Knuth, The Art of Computer Programming.
Para eliminar la subjetividad de "codigo legible" del camino, utilicemos lo que dice la regla de "Linea de Vision": basicamente, el "camino feliz" del codigo debe estar lo menos indentado posible. Solo el manejo de errores o casos especiales deberian estar indentados mas en profundidad.
El resultado es simple: es mucho mas facil entender el codigo resultante.
Ahora, cual es el problema con else
? Es que fuerzan un nivel de indentacion. Esto hace que sea menos claro que parte del codigo se relaciona con el "camino feliz", que es un caso especial y que no, y la legibilidad en general.
Es importante la posibilidad de escanear y leer codigo de forma eficiente y rapida. Manejar pequeños bloques de codigo es fundamental para ello. Queremos evitar tener que leer cada linea de codigo para entender una parte minuscula de codigo.
Las sentencias else
hacen esto mas complciado ya que estan desconectados, separados de la sentencia if
.
Podemos usar dos ejemplos simples, primero, podemos deducir facilmente que pasa cuando estas 3 lineas de codigo se ejecutan?
if ($variable == null) {
return "";
}
Es bastante obvio. Ahora, contrastemos con otro ejemplo opuesto:
} else {
return "";
}
Sin la sentencia if
no podemos determinar que hace exactamente eso, en que contexto se ejecutaria o en que caso lo haria. Porque retornaria un string vacio? Es un error o un comportamiento normal?
Ese codigo se basa en que tengamos que recordar cosas, y que tengamos que leer otro codigo para entender que hace y en que contexto.
No importa demasiado con bloques simples de if
, pero cuando tenemos logica complicada en el bloque o estamos leyendo rapido el codigo, la separacion de contexto hace pedazos la legibilidad. Ni hablar del daño masivo que proboca cuando tenemos anidadas varias sentencias ìf-else-elseif-else`(las cuales TAMBIEN hay que evitar), o hay varias en una funcion.
Por ejemplo, este caso puede ser un infierno:
} else {
else {
else{
else {
else {
return "";
}
}
}
}
}
Estamos de acuerdo que las sentencias else
apestan. Pero reconocer el problema es solo el primer paso. El verdadero truco esta en evitarlos.
Hay 2 formas faciles de hacerlo:
- Invertir la condicion del
if
y retornar tempranamente - Crear funciones de apoyo (helpers)
Este es el caso mas simple y comun.
Podemos cambiar esto:
function hacerAlgo($algo){
if($algo::OK()){
$valor = $algo::hacerAlgo();
return $valor
}
else {
return "Algo no esta bien";
}
}
Simplemente, podemos invertir la condicion del if
para que no necesitemos utilizar el else
:
function hacerAlgo($algo){
if(!$algo::OK()){
return "Algo no esta bien";
}
$valor = $algo::hacerAlgo();
return $algo::hacerAlgo();
}
Hermoso. Poetico. Una Gioconda del codigo.
Podemos por un lado, entender la condicion de error, y el contexto de su ejecucion, ademas de ver y entender claramente en que caso tenemos esa condicion, y cual es el flujo normal de la funcion.
Podemos tener casos de sentencias else
que no retornan un valor directamente, sino que ejecutan codigo en su interior.
Esto normalmente se debe a alguna logica de algun caso especial que no esta aislada correctamente.
Por ejemplo:
if($pais !== ""){
if($tier !== ""){
$caridades = obtenerCaridadesPorPaisPorPlanYPorTier($pais, $plan, $tier);
}
else {
$caridades = obtenerCaridadesPorPaisYPorPlan($pais,$plan);
}
}
else {
$caridades = obtenerCaridadesPorPlan($plan);
}
Podemos mejorar la legibilidad separando la logica de obtenerCaridades
en su propia funcion e invirtiendo un poco las condiciones. Esto permite que los casos especiales se manejen correctamente y poder returnar rapido de la funcion.
Podemos tener algo asi:
function obtenerCaridades($plan, $pais, $tier){
if($pais !== "")
return obtenerCaridadesPorPlan($plan);
if($tier !== "")
return obtenerCaridadesPorPlanYPorPais($plan,$pais);
return obtenerCaridadesPorPaisPorPlanYPorTier($pais, $plan, $tier);
}
Esta funcion helper encapsula toda la logica necesaria y elimina toda necesidad de sentencias else
. Es mas facil de leer rapidamente, ademas de entender mejor la logica al no tener tantos niveles de indentacion.
Vamos con otro caso simple, comparar si un numero es par o impar:
// Amateur, Trainee, Junior, principiante, novato
function VerificarSiEsParImpar($numero){
if(($numero % 2) == 0){
return "es par";
}
else {
return "es impar";
}
}
$resultado = verificarSiesParImpar($_GET['numero']);
Podemos asumir simplemente, que si no se entra al bloque de if
, con 100% de certezas se va a entrar al bloque de else
, por lo tanto, el mismo no es necesario, ya que la sentencia if
retorna un valor, por lo que al entrar a ella, nunca seguira la ejecucion del codigo hacia el bloque else
.
Por lo tanto, podemos eliminarlo asi:
// Semi-Senior, avanzado, alguien a quien le importa la vida
function VerificarSiEsParImpar($numero){
if(($numero % 2) == 0)
return "es par";
return "es impar";
}
$resultado = verificarSiesParImpar($_GET['numero']);
En este caso, incluso podemos eliminar la funcion completamente, usando un operador ternario:
// Senior, filantropo, un poeta del codigo
$resultado = ($numero % 2) == 0 ? "es par" : "es impar";
Es AUN MAS LEGIBLE, pero no es para todos los casos.
Podemos tomar esta clase:
class Libro(){
public $Isbn;
public $Autor;
public $Genero;
public function Insertar(){
$conexion = new mysqli("IP","usuario","password","db");
if($conexion -> error){
$html = "<b>No se pudo conectar</b>";
return $html;
}
else {
$sql = "INSERT INTO libro(isbn,autor,genero) VALUES($this->Isbn,'$this->Autor','$this->Genero')";
if($conexion -> query($sql) === TRUE){
$html = "<b>Libro insertado correctamente</b>";
return $html;
else {
$html = "<b>No se pudo insertar el libro</b>";
return $html;
}
}
}
}
Sabemos que los return
impiden que se siga ejecutando el codigo por debajo de ellos, por lo cual hace que los else
sean totalmente inutiles:
class Libro(){
public $Isbn;
public $Autor;
public $Genero;
public function Insertar(){
$conexion = new mysqli("IP","usuario","password","db");
if($conexion -> error){
$html = "<b>No se pudo conectar</b>";
return $html;
}
$sql = "INSERT INTO libro(isbn,autor,genero) VALUES($this->Isbn,'$this->Autor','$this->Genero')";
if($conexion -> query($sql) !== TRUE){
$html = "<b>No se pudo insertar el libro</b>";
return $html;
$html = "<b>Libro insertado correctamente</b>";
return $html;
}
}
Mejor aun, podemos agregar funciones helpers para mejorar la legibilidad aun mas:
class Libro(){
public $Isbn;
public $Autor;
public $Genero;
public function Insertar(){
$conexion = new mysqli("IP","usuario","password","db");
if($conexion -> error) return $this -> returnErrorConexion();
$sql = "INSERT INTO libro(isbn,autor,genero) VALUES($this->Isbn,'$this->Autor','$this->Genero')";
if($conexion -> query($sql) !== TRUE) return $this -> returnErrorInsert();
$this -> returnExito();
}
private function returnErrorConexion(){
return "<b>No se pudo conectar</b>";
}
private function returnErrorInsert(){
return "<b>No se pudo insertar el libro</b>";
}
private function returnExito(){
return "<b>Libro insertado correctamente</b>";
}
}
Ademas de eliminar las sentencias else
se gana muchisimo mas en legibilidad.
NOTA AL PIE: soy TOTALMENTE CONSCIENTE de que se puede mejorar aun mas separando la conexion incluso en otra clase, usando prepared statements, y que esta clase no deberia manejar HTML. ES SOLO UN EJEMPLO!!!!!
Las sentencias else
son mas molestos que utiles. Destruyen la legibilidad del codigo forzando niveles de identacion, e igualando en indentancion los casos de error y los caminos felices, lo cual no deberia pasar. Ademas, aislan un bloque de codigo de su contexto de ejecucion y, la logia que lleva a su ejecution.