La historia de refactorizar la aplicación Citimobil



Hace poco más de un año, me uní al equipo de CityMobil como desarrollador de Android. Me acostumbré a un nuevo proyecto para mí, nuevos enfoques y tecnologías. En ese momento, Citimobil ya tenía una historia bastante larga, como el proyecto que adopté, una aplicación de Android para pedir un taxi. Sin embargo, como suele suceder en tales casos, el código llevaba los rastros característicos de las soluciones antiguas. Y ahora, después de refactorizar con éxito el código, quiero compartir ideas que, en mi opinión, pueden ser útiles para aquellos que tienen que refactorizar un proyecto existente. Y, sobre todo, puede ser útil para pequeñas empresas con pequeños equipos de desarrollo.

Una empresa a menudo prueba sus ideas, le dirige recursos limitados e intenta obtener retroalimentación, prueba sus hipótesis lo más rápido posible. En tales momentos, como regla general, el pensamiento de alta calidad y la implementación de la arquitectura del proyecto, teniendo en cuenta el futuro, se desvanecen en un segundo plano. Poco a poco, el proyecto adquiere una nueva funcionalidad, aparecen nuevos requisitos comerciales y todo esto afecta la base del código. "CityMobil" a este respecto no fue la excepción. El proyecto fue desarrollado secuencialmente por varios equipos en la antigua oficina, luego, durante la mudanza, fue apoyado y correspondió parcialmente con la subcontratación. Luego comenzaron a formar un nuevo equipo y me entregaron trabajo en el proyecto.

En ese momento, el "desarrollo" se trasladó a la oficina de Moscú, el trabajo estaba en su apogeo: constantemente aparecían nuevas tareas interesantes y ambiciosas. Sin embargo, el legado cada vez más pone palos en las ruedas, y una vez que nos dimos cuenta de que había llegado el momento de grandes cambios. Desafortunadamente, no se encontró tanta literatura útil entonces. Es comprensible, se conoce por experiencia, es casi imposible encontrar o encontrar la receta perfecta que funcione en el 100% de los casos.

Lo primero que debe hacer es entender si realmente necesita refactorizar. Esto debe considerarse si:

  1. La velocidad de introducción de nuevas características es irrazonablemente baja, a pesar del alto nivel de especialistas en el equipo.
  2. Los cambios en el código en una parte del programa pueden conducir a un comportamiento inesperado en otra parte.
  3. La adaptación de los nuevos miembros del equipo se retrasa.
  4. La prueba de código se ve obstaculizada por una fuerte conectividad.

Después de darse cuenta de la existencia de un problema, uno debe encontrar respuestas a las siguientes preguntas:

  1. ¿Qué, de hecho, está mal?
  2. ¿Qué llevó a esto?
  3. ¿Qué hay que hacer para evitar que esto vuelva a suceder?
  4. ¿Cómo arreglar la situación?

Es casi imposible construir un buen proyecto de larga duración sin establecer una determinada arquitectura. En nuestro proyecto, decidimos introducir una arquitectura "en capas", que ya ha demostrado su eficacia.

Inicialmente, el proyecto fue escrito principalmente con la ayuda de las herramientas proporcionadas por el SDK de Android. El enfoque indudablemente funciona, pero te obliga a escribir mucho código repetitivo, lo que inhibe en gran medida el desarrollo. Y considerando que hoy en día muchos están acostumbrados a ciertas pilas de tecnología, la adaptación de los nuevos desarrolladores tomó más tiempo. Poco a poco, llegamos a tecnologías más convenientes que muchos conocen y valoran, y que han demostrado su fiabilidad y consistencia:

  • MVP - Patrón de diseño de interfaz de usuario (Model-View-Presenter).
  • Dagger 2 es un marco para implementar dependencias.
  • RxJava2 es una implementación de ReactiveX, una biblioteca para crear programas asíncronos y basados ​​en eventos utilizando el patrón Observer para la JVM.
  • Cicerone es una biblioteca que le permite simplificar la navegación en la aplicación.
  • Una serie de bibliotecas específicas para trabajar con mapas y ubicaciones.

Es muy importante adoptar un estilo de código común para el equipo, para desarrollar un conjunto de mejores prácticas. También debe cuidar la infraestructura y los procesos. Es mejor escribir pruebas para el nuevo código de inmediato, ya que hay mucha información sobre este tema.

Dentro del equipo, comenzamos a realizar una revisión del código sin falta, no lleva tanto tiempo, pero la calidad del código se ha vuelto mucho más alta. Incluso si está solo en el equipo, le recomiendo trabajar en Git Flow, crear solicitudes de fusión y al menos verificarlas usted mismo.

Todo el trabajo "sucio" se puede delegar a CI; en nuestro caso, esto es TeamCity usando fastlane. Lo configuramos para crear ramas de características, ejecutar las pruebas y diseñar la prueba interna. En nuestro lugar, configuramos ensamblajes por separado para el entorno de producción / preparación, característica- (los llamamos por el número de tarea con la plantilla TASK # task_number) y liberamos ramas. Esto facilita las pruebas y, si se produce un error, sabemos de inmediato qué debe corregirse y dónde.

Después de llevar a cabo todas las acciones preliminares, nos ponemos a trabajar. Comenzamos una nueva vida en un proyecto antiguo creando un paquete (cleanarchitecture). Es importante no olvidarse del alias de actividad al mover puntos de entrada a la aplicación (a-la ActivitySplash). Si descuida esto, entonces, en el mejor de los casos, perderá el ícono en el iniciador y, en el peor de los casos, se violará la compatibilidad con otras aplicaciones.

<!-- android:name=".SplashActivity" - old launcher activity --> <!-- android:targetActivity=".cleanarchitecture.presentation.SplashActivity" - new launcher activity --> <activity-alias android:name=".SplashActivity" android:targetActivity=".cleanarchitecture.presentation.SplashActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity-alias> 

Como sugiere la experiencia, es mejor comenzar a refactorizar con pantallas pequeñas y partes menores de la aplicación. Y cuando llegue el momento de procesar la parte más compleja y voluminosa del programa, una parte considerable del código ya estará escrita para otros módulos y podrá reutilizarse.

Además, tuvimos la gran tarea de rediseñar completamente la aplicación, lo que, a veces, resultó en una reescritura completa de las pantallas. Comenzamos mejorando las pantallas auxiliares, preparándonos para pasar a lo principal.

Después de reescribir la siguiente parte de la aplicación, buscamos secciones de código en la parte anterior de la aplicación y las marcamos con anotaciones @Deprecated y análogos de estas: https://github.com/VitalyNikonorov/UsefulAnnotation . En ellos indicamos qué se debe hacer al reescribir esta parte del programa, qué funcionalidad y dónde se implementa.

 /** * This class deprecated, you have to use * com.project.company.cleanarchitecture.utils.ResourceUtils * for new refactored classes */ @Deprecated public class ResourceHelper {...} 

Después de que todo estaba listo para funcionar en la pantalla principal, decidieron no lanzar nuevas funciones durante 6-8 semanas. Realizamos una reescritura global en nuestra propia sucursal, a la que luego agregamos solicitudes de fusión. Al final de la refactorización, recibieron la codiciada solicitud de extracción y una aplicación casi completamente actualizada.

Después de refactorizar, los cambios en la funcionalidad de la aplicación se hicieron mucho más fáciles. Entonces, recientemente volvimos a participar en el procesamiento de pantallas de autorización.

Inicialmente, se veían de la siguiente manera:



Después del primer procesamiento y refactorización, comenzaron a verse así:



Ahora se ven así:



Como resultado, la primera iteración tomó más del doble de tiempo que la segunda. Como además de procesar la interfaz de usuario, también tuve que entender el código de lógica de negocios ubicado en el mismo lugar, aunque esto no era necesario, pero se eliminó la falla, lo que redujo el tiempo dedicado a la tarea en la segunda iteración.

¿Qué tenemos en este momento?

Para que el código sea conveniente para su uso y desarrollo en el futuro, nos adherimos al principio de "arquitectura limpia". No diría que tenemos Clean canónico, pero adoptamos muchos enfoques. La capa de presentación se escribe utilizando el patrón MVP (Modelo-Vista-Presentador).

  • Anteriormente, teníamos que discutir interminablemente cada paso entre nosotros, para aclarar si el cambio en un módulo afecta la funcionalidad de otro. Y ahora los gastos generales por correspondencia se han reducido significativamente.
  • Debido a la unificación de componentes y fragmentos individuales, el volumen de la base del código ha disminuido considerablemente.
  • Como resultado de la misma unificación y procesamiento de la arquitectura, hay muchas más clases, pero ahora hay una clara división de responsabilidades en ellas, lo que simplifica la comprensión del proyecto.
  • La base del código se divide en capas, para su separación e interacción, se utiliza el marco de inyección de dependencia Dagger 2. Esto redujo la coherencia del código y aumentó la velocidad de prueba.

Hay muchos más puntos interesantes relacionados con la refactorización de código heredado. Si los lectores estarán interesados, escribiré más sobre ellos la próxima vez. También me alegrará si también compartes tu experiencia.

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


All Articles