El documento proporciona recomendaciones para hacer que el código sea más legible, incluyendo usar un orden lógico en las comparaciones, elegir primero el caso positivo en los bloques if/else, evitar el uso excesivo de anidamiento, minimizar el uso de ciclos do-while y goto, y seguir un flujo de ejecución claro. El objetivo es reducir la carga cognitiva del lector al interpretar el código.
1. Haciendo estructuras
de control fáciles de leer
Juan David Hernández
https://www.fing.edu.uy/inco/cursos/fpr
/wiki/images/4/47/Flujoiteracion.png
2. Haz que todos tus condicionales,
ciclos y cualquier otra estructura de
control tan “natural” como sea
posible, escribiendo de una forma
que no haga parar o releer al lector
3. El orden de los argumentos en
condicionales
– Si bien es cierto que el orden no altera el producto, sí altera la lectura
if (length >= 10) if (10 <= length)
while (bytes_received < bytes_expected)
while (bytes_expected > bytes_received)
– Regla: primero el valor que se va a comparar y luego contra el que se comparará.
Ejemplo:
Si 18 años es mayor o igual a tu edad
Si tienes al menos 18 años
4. El orden de los bloques if/else
Reglas:
– Elegir primero el caso positivo.
– Empezar por el caso más simple para que no moleste luego.
Ejemplo: if (file == null) // Bloque de Error
– Lidiar primero con el caso más llamativo (lo contrario a la
imagen)
Clase, primero pongamos toda nuestra atención a
Bobby mientras habla de su sapo mascota
if (!url.HasParameter(“id")) {
// bloque 1
} else {
// bloque 2
}
if (url.HasParameter(“id")) {
// bloque 2
} else {
// bloque 1
}
5. El operador ternario (el
condicional ?)
– Nótese la diferencia…
time_str += (hour >= 12) ? "pm" : "am"; // Entendible
return exponent >= 0 ? mantissa * (1 << exponent) : mantissa / (1 << -exponent);
– Regla: La idea no es reducir código sino reducir el tiempo de lectura (No usar el
operador).
if (exponent >= 0) { // Mucho más entendible
return mantissa * (1 << exponent);
} else {
return mantissa / (1 << -exponent);
}
6. Evitar ciclos do-while
Caída peligrosa – ¿Por qué no
pusieron esa señal arriba?
– No se sabe la condición del ciclo hasta que se termina de leer el interior.
– Regla: Siempre hay una forma de hacer lo mismo que un do-while de
otra manera, pero teniendo cuidado de hacer lo siguiente:
// Bloque de código
while (condition) {
// Bloque de código, otra vez
}
do {
// Bloque de código
} while (condition);
do { // ¿Qué pasa aquí?
continue;
} while (false);
7. // Busca a través de la lista, empezando en 'node', por el'name' dado
// No considera más de 'max_length' nodos
public boolean ListHasNode(Node node, String name, int max_length) {
do {
if (node.name().equals(name))
return true;
node = node.next();
} while (node != null && --max_length > 0);
return false;
}
Evitar ciclos do-while: Ejemplo
public boolean ListHasNode(Node node, String name, int max_length) {
while (node != null && max_length-- > 0) {
if (node.name().equals(name))
return true;
node = node.next();
}
return false;
}
8. El infame goto
– Hace el flujo del código difícil de seguir
– Hay mejores formas de hacer lo mismo
– Pueden generar cruces de secciones. Ejemplo:
:label1;
if (condition) flag = true;
:label2
createSomething();
– Sin embargo, en C sigue siendo muy utilizado para indicar acciones de salida y
errores, lo cual no suele ser un problema mientras sea bien usado.
9. Minimizar anidación
– Cada anidación es un push en el stack mental del lector, hacer un pop
implica leer nuevamente la condición actual al encontrar un cierre.
if (user_result == SUCCESS) {
if (permission_result != SUCCESS) {
reply.WriteErrors("error reading permissions");
reply.Done();
return;
}
reply.WriteErrors("");
} else {
reply.WriteErrors(user_result);
}
reply.Done();
– En este caso es aún peor porque la condición interna difiere de la externa
10. – Mientras escribes código tienes un contexto de lo que estás escribiendo y no te das
cuenta que tan difícil es leerlo, por tanto, es recomendable releer con una
perspectiva fresca.
– Reducir anidación mediante retornos tempranos (sí, son recomendable):
if (user_result != SUCCESS) {
reply.WriteErrors(user_result);
reply.Done();
return;
}
if (permission_result != SUCCESS) {
reply.WriteErrors(permission_result);
reply.Done();
return;
}
reply.WriteErrors("");
reply.Done();
Minimizar anidación: Reglas
11. – Reducir anidación en ciclos mediante continue:
for (int i = 0; i < results.length; i++) {
if (results[i] == NULL) continue;
non_null_count++;
if (results[i]->name == "") continue;
console.log("Considering candidate..." + endl);
}
Minimizar anidación: Reglas
12. Puedes seguir el flujo de
ejecución?
– Si el código es legible, es más fácil entender, seguir el flujo de ejecución y hacer debug.
– Evitar usar código que se ejecute tras bambalinas indiscriminadamente
Tipo de llamado Cómo se oscurece el código
Hilos y procesos No es claro qué código se está ejecutando
Manejo de señales/interrupciones Cierto código podría ejecutarse en cualquier momento
Excepciones Termina haciendo “Bubble up” a través de múltiples llamadas a funciones
Punteros de funciones o funciones
anónimas
No se sabe qué código se ejecutará en tiempo de compilación
Métodos virtuales object.virtualMethod() puede invocar código de una subclase
desconocida
13. Referencias:
– The Art of Readable Code, D. Boswell & T. Foucher, Capítulo 7,
http://dl.finebook.ir/book/5f/14474.pdf
– Control Flow, Wikipedia, https://en.wikipedia.org/wiki/Control_flow