Reteniendo los vicios del imperativo

El paradigma orientado a objetos es extremadamente conveniente para las empresas: le permite implementar casi cualquier idea, proporcionando un rendimiento aceptable del producto. En este caso, por producto nos referimos a una aplicación iOS, por lo tanto, en conclusión, procederemos del desarrollo específicamente en esta plataforma.

Haciendo la vista gorda ante las conocidas deficiencias de este paradigma popular, la lista de sus desventajas incluye su ventaja más importante: la flexibilidad del desarrollo. ¿Por qué es esto un menos? Es bastante obvio que la flexibilidad, además de la capacidad básica para resolver problemas comerciales, hace que esto sea posible de varias maneras. Es cierto que hay una docena de incorrectos para un enfoque correcto, a pesar del hecho de que la tarea empresarial se resolverá correctamente en cualquiera de los casos, pero con diferencias en la implementación, cuya extensibilidad y transparencia ya dependerán de la corrección del enfoque aplicado.

Teniendo en cuenta la ley de Murphy, la conclusión es que, en ausencia de las restricciones arquitectónicas correctas, es más probable que siga el camino del caos, es decir, la calidad del código disminuirá rápidamente, solo porque el paradigma lo permite. Este artículo discutirá una de las posibles limitaciones arquitectónicas que ayudarán a mantener el equilibrio de las fuerzas del bien y del mal, en otras palabras, la dinámica del crecimiento de la entropía en la base del código del proyecto. Es importante que esta dinámica se correlacione directamente con el número de personas involucradas en la redacción del código, por lo tanto, para proyectos grandes, la corrección de las restricciones seleccionadas es especialmente crítica. Cual es el punto?

El punto es simple. Salgamos del siguiente axioma: cuantas más propiedades haya en un objeto, "peor" es. Esta declaración se puede explicar de la siguiente manera: con un aumento en el número de estados internos, todos los indicadores positivos del objeto (extensibilidad, modularidad, transparencia, comprobabilidad) disminuyen. Uno puede objetar, diciendo que la complejidad del objeto y su funcionalidad están interconectados y que sin el primero, el segundo no sucede. Todo es cierto, pero, siguiendo el camino "con estado", el crecimiento de la complejidad ocurre exponencialmente, mientras que idealmente el crecimiento debería ser lineal o, más simplemente, una nueva característica no debería complicar las características existentes.

PD: Vale la pena aclarar que las características se entienden como capas semánticamente relacionadas de la lógica empresarial, por lo tanto, a menudo se toma la decisión de complicar una clase existente, en lugar de crear una nueva. En tales casos, es difícil encontrar contradicciones al primer principio SÓLIDO.

Un ejemplo es una pantalla completamente estándar con una lista de entidades con una opción. Algunas de las propiedades podrían hacerse no estables, pero IB nos une al constructor predeterminado del controlador y esto nos obliga a "hacer tierra". El acceso a estas propiedades se obtiene implícitamente por cada método de clase dentro del mismo archivo. Y el momento más desagradable es que su mutabilidad tampoco está cubierta por nada, y esto de alguna manera conduce a consecuencias irreversibles en forma de una conexión fuerte y, en consecuencia, al hecho de que la fijación de algunos defectos provoca la aparición de otros nuevos:



Podemos concluir que un aumento lineal en la complejidad de un objeto con un estado común es prácticamente inalcanzable. La funcionalidad con este enfoque se vuelve monolítica y difícil para cualquier tipo de segregación. Un buen ejemplo es el controlador de vista masiva antipatrón, que es bastante común y demuestra claramente el resultado de la ausencia de restricciones en el proyecto.



Usando UIKit como ejemplo, puede ver exactamente cómo el estilo imperativo de desarrollo afecta la complejidad del código y en qué puntos el marco "obliga" a crear propiedades en las clases.
El caso más simple, el procesamiento de presionar un botón, se realiza de manera estándar solo definiendo el "método sucio", es decir, mediante una función que no puede recibir nada útil, por lo que tendrá que salir para acceder a las propiedades:



Por lo tanto, la "característica" del botón complicará tiránicamente el resto de la funcionalidad del objeto, ya que todos los residentes de la clase tendrán acceso a estos datos. De hecho, casi cualquier control de IU de iOS funciona de manera similar. Por lo tanto, viene a la mente implementar algún tipo de envoltorio sobre los elementos de la interfaz de usuario, por ejemplo, que requiere un cierre, como un procesamiento de la "operación" de un elemento en particular, pero la dificultad es encontrar la manera de hacer que dicho envoltorio sea conciso y confiable. Después de todo, el asunto no es solo en la entrada, sino también en la salida de información, por ejemplo, la tabla ubicua con datos también funciona "sucia" y no tiene idea de los datos que muestra, por lo que debe guardarla en un objeto:



¿Es conveniente? Convenientemente, todos siempre tienen acceso a los datos. Flexible, rápido, imperativo. Pero todo se convierte con el tiempo, por regla general, en un obelisco concreto para mil líneas de código y en las lágrimas de aquellos que tuvieron la tarea de trabajar en esta clase.

Volviendo a las limitaciones, el siguiente principio se puede formar a partir de lo anterior: el código sin propiedades en las clases es mucho más limpio y más rentable con soporte y extensión. Idealmente, la lógica del objeto debería estar en sus métodos "puros", que toman todas sus dependencias como entrada y tienen el resultado de su actividad en la salida.
La idea es reducir el estado privado del objeto un nivel más bajo. Es decir, si lo privado cierra las propiedades del mundo exterior, entonces nuestra tarea es ir aún más lejos y fortalecer la encapsulación al nivel de los métodos mismos. A primera vista, a pesar de toda la naturaleza asincrónica de la plataforma y el imperativo del marco principal, puede parecer que toda esta idea no solo es imposible, sino que al menos su implementación se sentirá poco natural. Y lo más probable es que sea así, pero este es uno de esos problemas que se pueden resolver mediante la introducción de un nivel adicional de abstracciones.

Si queremos ajustar toda la lógica de las clases en sus métodos sin recurrir a las propiedades, debemos trabajar estrechamente con los cierres. IOS tiene herramientas estándar para administrar operaciones asincrónicas, como GCD y OperationQueue. Parece que serían suficientes, pero cuando tratas de hacer realidad la idea, todo resultará lejos de ser tan optimista como quisieras. Con este enfoque, además del hecho de que hay una gran posibilidad de recibir una devolución de llamada, el código en sí resultará engorroso, tendrá muchos agujeros lógicos y estará fuertemente conectado. Es posible que dicho código sea aún más complicado de lo que estamos intentando escapar tan rápido.

Si miras a tu alrededor, puedes ver que hay formas mucho más hermosas y funcionales de lograr este objetivo. La programación reactiva se ha utilizado durante mucho tiempo en el desarrollo de software comercial y es ideal para el mundo front-end asíncrono. En este caso, consideraremos una implementación (bastante exitosa) del paradigma reactivo para Swift - Rx.



Ofrece una entidad simple llamada Observable. Este es un tipo de abstracción sobre el flujo de eventos, se puede suscribir, y luego el suscriptor recibirá estos eventos con el tiempo:



La forma más fácil de imaginar el flujo de eventos es presentando un botón regular. El evento de su operación es aquí una secuencia, por lo que cualquier objeto puede suscribirse y recibir eventos de su clic, y lo más importante, el botón no sabe nada sobre sus propios suscriptores. Convenientemente, casi cualquier acción se puede convertir en una secuencia de valores similar, y esto es importante, porque Observable se puede combinar entre sí, ya que ningún marco estándar lo hará posible.

Por ejemplo, puede enviar varias solicitudes presionando el botón (filtrado de doble toque), esperar a que cada una responda, luego combinar la respuesta con lo que el usuario ingresó en la pantalla, ejecutar otra solicitud y pasar a la siguiente pantalla, transfiriéndole el resultado, cuando este Rx permitirá manejar de manera sucinta los errores y completar esta cadena (cancelar solicitudes) al salir de la pantalla, y toda esta lógica tomará dos docenas de líneas de código escrito:



Vale la pena hablar de la única propiedad de disposeBag. Como se puede ver en las capturas de pantalla, cada suscripción se coloca en ella, y esto es necesario para controlar su vida útil. Es decir, las suscripciones son válidas mientras la "bolsa" viva en el lugar en el que se colocaron, en este caso, mientras el controlador viva.

Además de la compacidad, es difícil cometer un error en el código anterior, ya que cada cierre devuelve algo y no contiene efectos secundarios. Este es el poder que estábamos buscando.

Puedes notar un punto más importante: dado que la clase no tiene propiedades, no es necesario escribir [self débil], lo que afecta positivamente la legibilidad del código. Todas las funciones pueden y mejor definirse localmente en el método donde se usan, o se toman en clases separadas. Por cierto, los enlaces a dependencias (ViewModel, Presenter, etc.) en este caso se pueden pasar como un argumento al método del controlador, en este caso no es necesario guardarlos en las propiedades. Esto es correcto

Después de revisar, es hora de usar Observable en aras de simplificar el desarrollo. Como exactamente Volvamos a la idea de los métodos "limpios" e intentemos implementar la lógica de una pantalla pequeña en su único método, para mayor claridad, elegiremos el método para finalizar la carga de la vista (viewDidLoad). Naturalmente, si la pantalla está hecha en IB, tendremos que crear propiedades para los puntos de venta, pero esto no da miedo, porque los elementos en sí mismos no representan la lógica empresarial, por lo tanto, no afectarán en gran medida la complejidad de la pantalla. Pero si la pantalla está compuesta de código, puede prescindir de las propiedades (excepto el disposeBag), creando elementos en nuestro método y usándolos allí mismo. ¿Qué pasa con la imperativa de los elementos UIKit que se describieron anteriormente? Rx, además del enfoque en sí, proporciona envoltorios reactivos para componentes estándar de la interfaz de usuario, por lo que en la mayoría de los casos puede obtener la secuencia necesaria de eventos en el acto. O, por el contrario, vincule el Observable existente a, por ejemplo, una tabla: presente una solicitud para que actualice su contenido inmediatamente después de su finalización:



El enlace a colecciones es bastante flexible, pero de forma predeterminada, solo funciona a través de reloadData. Para actualizaciones puntuales hay una maravillosa biblioteca depurada del mismo autor: RxDataSources. Con él, puede olvidarse de los bloqueos durante las actualizaciones por lotes.

¿Qué pasará después? Un método de pantalla única crecerá y, en un caso bastante complicado, será difícil de mantener. Y cuando esto sucede, de repente queda claro que este método no depende de otra cosa que no sea él mismo, y el enfoque reactivo ha dividido el código en bloques lógicos que se pueden poner fácilmente en objetos separados al diseñarlos de manera similar. Pero esta vez, los métodos ya tomarán algunas dependencias en la pantalla y devolverán algunos resultados. El punto positivo es que la firma en este caso se obtiene con el mayor contenido posible, se puede ver que la función requiere su trabajo y cuál es su resultado. Puede verse más o menos así:



Las estructuras separadas ayudan a mantener el encabezado del método legible y ordenado, ya que puede haber muchas dependencias.

Es importante comprender que el método no tiene que ser el único en todo el objeto, la esencia de su independencia entre sí. Gracias a Rx, su entrada y salida pueden ser asíncronas y representar uno o más observables, proporcionando otra dimensión para la manipulación de datos.

Este enfoque desata sus manos y le permite implementar pantallas de casi cualquier complejidad, mientras mantiene su código explícito y vagamente acoplado.

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


All Articles