En programación informática , la Pirámide de la Perdición es un término que se refiere al código que se anida profundamente con sentencias condicionales, bucles u otras estructuras de control, lo que reduce la legibilidad, la capacidad de prueba y el mantenimiento. Se denomina así porque el código comienza a asemejarse a una pirámide.
Un desafío común para los programadores de sistemas es que, antes de realizar una operación, deben verificarse varias condiciones para confirmar que se puede llevar a cabo correctamente. Por ejemplo, antes de escribir datos en un archivo, se debe confirmar que: 1) el programa tiene el archivo abierto para escritura; 2) el programa tiene los permisos necesarios para escribir los datos; 3) los datos que se van a escribir están disponibles; 4) los datos que se van a escribir tienen un tamaño válido. Si falla cualquiera de estos pasos, la operación de escritura no se puede completar y se debe devolver un error al programa que la invocó.
Existen varias maneras de escribir estas múltiples pruebas requeridas en el código fuente . Una forma consiste en verificar cada condición sucesivamente y, si alguna falla, salir de la subrutina en ese punto, indicando que existe una condición de error. Este estilo de codificación tiene la desventaja de que la subrutina regresa desde múltiples (posiblemente muchos) puntos, y algunos estándares de codificación desaconsejan tener múltiples puntos de retorno.
Otra forma es comprobar cada condición y, si se cumple, insertar un bloque de código más profundo que compruebe la siguiente condición, y así sucesivamente. El bloque de código más profundo solo se alcanza si todas las pruebas de precondición son exitosas. Este estilo de codificación tiene la desventaja de que el nivel de sangría aumenta con cada prueba realizada. Si se requieren muchas pruebas, los bloques de código pueden salirse de la página hasta el margen derecho. Este efecto tipográfico se conoce como la pirámide de la perdición .
Por ejemplo, la pirámide de la perdición se observa comúnmente al comprobar punteros nulos o al manejar devoluciones de llamada . [ 1 ] Dos ejemplos del término están relacionados con un estilo de programación particular en versiones tempranas de JavaScript , [ 2 ] y el anidamiento de sentencias if que ocurre en lenguajes de programación orientados a objetos cuando uno de los objetos puede ser un puntero nulo. [ 3 ] [ 4 ]
Ejemplos
La mayoría de los lenguajes de programación orientados a objetos modernos utilizan el encadenamiento de métodos , como se muestra a continuación:
Mapa < Cadena , Ventana > ventanas = Mapa . de ( // contenido aquí... );int ancho = windows . get ( "Main" ) . getViews () . get ( 5 ) . size () . ancho ();Este código contiene cuatro instrucciones diferentes; primero busca en la colección de ventanas una ventana con el nombre "Main", luego busca en la colección de vistas de esa ventana la quinta subvista dentro de ella, luego llama al size()método para devolver una estructura con las dimensiones de la vista y, finalmente, llama al width()método en esa estructura para producir un resultado que se asigna a un nombre de variable width.
El problema con este enfoque es que el código asume que todos estos valores existen. Si bien es razonable esperar que una ventana tenga un tamaño y que este tamaño tenga un ancho, no es razonable asumir que exista una ventana llamada "Main" ni que tenga cinco subvistas. Si alguna de estas suposiciones es incorrecta, la llamada correspondiente generará un error de puntero nulo.
Para evitar este error, el programador podría tener que verificar cada llamada a un método para asegurarse de que devuelva un valor. Otra versión del mismo código sería:
int ancho ; si ( windows.containsKey ( " Main" )) { Window mainWindow = windows.get ( " Main " ) ;if ( mainWindow . getViews (). size () > 5 ) { View view = mainWindow . getViews (). get ( 5 ); width = view . getSize (). getWidth (); } }Si el programador desea utilizar ese valor en función de si existe y es válido, todo el código funcional dentro de las ifinstrucciones se desplaza hacia la derecha, lo que dificulta la lectura de líneas largas. Esto suele dar lugar a intentos de "aplanar" el código.
Ventana ventana = ventanas.get ( " Main " ) ; int ancho ; if ( ventana ! = null ) { List <View> vistas = laVentana.getViews ( ) ;if ( views != null && views . size () > 5 ) { View view = views . get ( 5 );if ( view != null ) { width = view . getSize (). getWidth (); } } }O alternativamente:
Ventana ventana = ventanas.get ( " Main" ) ; int ancho ; if ( ventana == null ) { // Manejar el error cuando no se encuentra la ventana "Main" System.out.println ( " Error : No se encontró la ventana 'Main'." ) ; } else { List <View> vistas = laVentana.getViews ( );if ( views == null || views . size () <= 5 ) { // Manejar el error cuando la quinta vista no existe System . err . println ( "Error: No se encontró la quinta vista en la ventana 'Main'." ); } else { View view = views . get ( 5 );if ( view != null ) { width = view . getSize (). getWidth (); System . err . println ( "El ancho de la vista es: %d" , width ); } else { // Manejar el error si la quinta vista es nula System . err . println ( "Error: la quinta vista es nula." ); } } }Este tipo de construcción de programación es muy común y varios lenguajes de programación han añadido algún tipo de azúcar sintáctico para abordarlo. Por ejemplo, Swift de Apple añadió el concepto de encadenamiento opcional en las sentencias if [ 5 ], mientras que C# 6.0 y Visual Basic 14 de Microsoft añadieron los operadores condicionales nulos?. y ?[]para el acceso a miembros y la indexación, respectivamente. [ 6 ] [ 7 ] [ 8 ] JavaScript añadió soporte para el operador de encadenamiento opcional en 2020. [ 9 ] La idea básica es permitir que una cadena de llamadas a métodos devuelva inmediatamente null si alguno de sus miembros es null, por ejemplo:
Diccionario < cadena , Ventana > ventanas = nuevo () { // contenido aquí... }int? ancho = windows [ "Main" ] ?. Vistas ?. ElementoEnOPredeterminado ( 5 ) ?. Tamaño ?. Ancho ;asignaría null widthsi falta "Main" o la quinta subvista, o completaría la instrucción y devolvería el ancho si ambas son válidas. Hay muchas ocasiones en las que el programador quiere realizar acciones diferentes en estos dos casos, por lo que Swift añade otra forma de azúcar sintáctico para esta función, la if letinstrucción, también conocida como "enlace opcional":
if let view = windows [ "Main" ] ?. views [ 5 ] { // Hacer cosas sabiendo que la vista existe ... let width = view.size.width }Esta construcción existe de forma similar en Rust :
sea número : Opción < i32 > = Algunos ( 5 );if let Some ( i ) = number { println! ( "Coincidencia {:?}!" , i ); } else { println! ( "Ningún número coincide..." ); }Véase también
- Promesas , una técnica para evitar la pirámide de la perdición, por ejemplo, utilizada en JavaScript [ 10 ].
- Ley de Deméter
- Operador de navegación segura , un operador de lenguaje de programación que permite evitar la pirámide de la perdición.
Referencias
- ↑ Dave Herman (14 de diciembre de 2011). "Por qué las corrutinas no funcionan en la web" . The Little Calculist . Archivado del original el 6 de marzo de 2016.
- ↑ "La pirámide de la perdición: una trampa de estilo JavaScript" . 27 de noviembre de 2012. Archivado del original el 9 de diciembre de 2015.
- ↑ Eberhardt, Colin (8 de diciembre de 2014). "Derribando la pirámide opcional de la perdición de Swift" . Archivado del original el 31 de julio de 2016.
- ↑ "Nuevas características del lenguaje en Visual Basic 14" . 9 de diciembre de 2014. Archivado del original el 25 de diciembre de 2014.
- ↑ "Encadenamiento opcional" . Apple .
- ↑ "Operadores condicionales nulos (C# y Visual Basic)" . Microsoft . 7 de marzo de 2024.
- ↑ "Novedades de Visual C#" . Microsoft . 21 de mayo de 2024.
- ↑ "Novedades de Visual Basic" . Microsoft . 21 de febrero de 2023.
- ↑ "Encadenamiento opcional en JavaScript" . 28 de octubre de 2024.
- ↑ Joe Zimmerman (28 de marzo de 2013). "¿De qué sirven las promesas?" . telerik.com .
- Estructuras de programación