¿Qué tiene de malo la validación de datos y qué tiene que ver con el principio de sustitución de Liskov?



Si a veces se hace la pregunta: "¿está todo bien para mí en este método?"

Corrección: Como notaron lorc y 0xd34df00d , lo que se discute a continuación se llama tipos dependientes. Puedes leer sobre ellos aquí . Bueno, a continuación se muestra el texto fuente con mis pensamientos sobre esto.

Durante el desarrollo, a menudo es necesario verificar la validez de los datos para algún algoritmo. Formalmente, esto puede describirse de la siguiente manera: supongamos que obtenemos una determinada estructura de datos, verificamos su valor para verificar que cumpla con un cierto rango de valores permitidos (ODZ) y lo transmitimos. Posteriormente, la misma estructura de datos puede estar sujeta a la misma verificación. Si la estructura permanece sin cambios, volver a verificar su validez es obviamente una acción innecesaria.

Aunque la validación realmente puede ser larga, el problema no es solo en el rendimiento. Mucho más desagradable es el exceso de responsabilidad. El desarrollador no está seguro de si es necesario verificar nuevamente la validez de la estructura. Además de la verificación innecesaria, por el contrario, podemos suponer la ausencia de cualquier verificación, suponiendo incorrectamente que la estructura ha sido verificada anteriormente.

Por lo tanto, se permiten fallas en los métodos que esperan una estructura probada y no funcionan correctamente con una estructura cuyo valor está fuera de un cierto rango de valores aceptables.

Ahí radica un problema más profundo y obvio. De hecho, una estructura de datos válida es un subtipo de la estructura original. Desde este punto de vista, el problema con un método que acepta solo objetos válidos es equivalente al siguiente código en un lenguaje ficticio:

class Parent { ... } class Child : Parent { ... } ... void processValidObject(Parent parent) { if (parent is Child) { // process } else { // error } } 

De acuerdo en que ahora el problema es mucho más claro. Ante nosotros hay una violación canónica del principio de sustitución de Liskov. Lea por qué violar el principio de sustitución es malo, por ejemplo, aquí .

El problema de transferir objetos no válidos se puede resolver creando un subtipo para la estructura de datos original. Por ejemplo, puede crear objetos a través de una fábrica que devuelve un objeto de subtipo válido o nulo de acuerdo con la estructura original. Si cambiamos la firma de los métodos que esperan una estructura válida para que solo acepten un subtipo, entonces el problema desaparecerá. Además de la confianza de que el sistema funciona exactamente, la cantidad de validaciones por centímetro cuadrado de código disminuirá. Otra ventaja es que con tales acciones cambiamos la responsabilidad de validar los datos del desarrollador al compilador.

En Swift, en el nivel de sintaxis, se resuelve el problema de verificar nulo. La idea es separar los tipos en tipos anulables y no anulables. Al mismo tiempo, está hecho en forma de azúcar de tal manera que el programador no necesita declarar un nuevo tipo. Al declarar un tipo de variable, ClassName garantiza que la variable es distinta de cero, y al declarar ClassName? La variable es nula. Al mismo tiempo, existe covarianza entre tipos, es decir, puede pasar un objeto de tipo ClassName a métodos que aceptan ClassName.

Esta idea se puede ampliar a DLD definido por el usuario. Proporcionar objetos con metadatos que contengan DLD almacenados en el tipo eliminará los problemas descritos anteriormente. Sería bueno obtener soporte para dicha herramienta en un lenguaje, pero este comportamiento también se implementa en lenguajes OO "ordinarios", como Java o C #, utilizando la herencia y una fábrica.

La situación con la validación de datos es otra confirmación de que las entidades en la OOP no se toman del mundo real, sino de las necesidades de ingeniería.

UPD: Como se señaló correctamente en los comentarios, vale la pena crear subtipos solo si obtenemos confiabilidad adicional y reducimos el número de validaciones idénticas.

Además, el artículo carece de un ejemplo. Deje que algunas rutas de archivo nos lleguen a la entrada. Nuestro sistema en algunos casos funciona con todos los archivos, y en algunos casos solo con los archivos a los que tenemos acceso. A continuación, queremos transferirlos a diferentes subsistemas, que también funcionan con archivos accesibles e inaccesibles. Además, estos subsistemas transfieren archivos aún más, donde nuevamente no está claro si el archivo está disponible o no. Por lo tanto, en cualquier lugar dudoso, aparecerá una verificación de acceso o se puede olvidar por el contrario. Debido a esto, el sistema se volverá más complicado debido a la ambigüedad generalizada y los controles. Pero estas comprobaciones cargan el disco y generalmente pesan. Puede guardar esta verificación en caché en un campo booleano, pero esto no nos salvará del hecho mismo de la necesidad de verificación. Sugiero cambiar la responsabilidad de verificar del desarrollador al compilador.

Source: https://habr.com/ru/post/476220/


All Articles