Dise√Īo de clase: ¬Ņqu√© es bueno?



Publicado por Denis Tsyplakov , arquitecto de soluciones, DataArt

Con los a√Īos, descubr√≠ que los programadores repiten los mismos errores de vez en cuando. Desafortunadamente, los libros sobre aspectos te√≥ricos del desarrollo no ayudan a evitarlos: los libros generalmente no tienen consejos concretos y pr√°cticos. Y hasta adivino por qu√© ...

La primera recomendaci√≥n que viene a la mente cuando se trata, por ejemplo, de registro o dise√Īo de clase es muy simple: "No hagas tonter√≠as". Pero la experiencia muestra que definitivamente no es suficiente. Solo el dise√Īo de clases en este caso es un buen ejemplo: un dolor de cabeza eterno que surge del hecho de que todos analizan este tema a su manera. Por lo tanto, decid√≠ reunir consejos b√°sicos en un art√≠culo, despu√©s de lo cual evitar√° una serie de problemas t√≠picos y, lo m√°s importante, salvar√° a sus colegas de ellos. Si algunos principios te parecen banales (¬°porque son realmente banales!), Bueno, entonces ya se han asentado en tu subcorteza y puedes felicitar a tu equipo.

Har√© una reserva, de hecho, nos centraremos en las clases √ļnicamente por simplicidad. Casi lo mismo se puede decir sobre las funciones o cualquier otro componente b√°sico de la aplicaci√≥n.
Si la aplicaci√≥n funciona y realiza la tarea, entonces su dise√Īo es bueno. O no? Depende de la funci√≥n objetivo de la aplicaci√≥n; lo que es bastante adecuado para una aplicaci√≥n m√≥vil que debe mostrarse una vez en la exposici√≥n puede no ser adecuado para la plataforma de negociaci√≥n que cualquier banco ha estado desarrollando durante a√Īos. Hasta cierto punto, la respuesta a esta pregunta puede llamarse el principio S√ďLIDO , pero es demasiado general: quiero algunas instrucciones m√°s espec√≠ficas a las que se pueda hacer referencia en una conversaci√≥n con colegas.

Aplicación de destino


Como no puede haber una respuesta universal, propongo reducir el alcance. Supongamos que estamos escribiendo una aplicaci√≥n comercial est√°ndar que acepta solicitudes a trav√©s de HTTP u otra interfaz, implementa cierta l√≥gica por encima de ellas y luego realiza una solicitud al siguiente servicio de la cadena o almacena los datos recibidos en alg√ļn lugar. Para simplificar, supongamos que estamos utilizando Spring IoC Framework, ya que es bastante com√ļn ahora y el resto de los marcos son bastante similares. ¬ŅQu√© podemos decir sobre tal aplicaci√≥n?

  • El tiempo que el procesador pasa procesando una solicitud es importante, pero no cr√≠tico; un aumento del 0.1% en el clima no lo har√°.
  • No hay terabytes de memoria a nuestra disposici√≥n, pero si la aplicaci√≥n requiere 50-100 Kbytes adicionales, esto no ser√° un desastre.
  • Por supuesto, cuanto m√°s corto sea el tiempo de inicio, mejor. Pero no hay una diferencia fundamental entre 6 segundos y 5,9 segundos.

Criterios de optimización.


¬ŅQu√© es importante para nosotros en este caso?

Es probable que la empresa utilice el c√≥digo del proyecto durante varios a√Īos, tal vez m√°s de diez a√Īos.

El código en diferentes momentos será modificado por varios desarrolladores que no están familiarizados entre sí.
Es posible que en unos a√Īos, los desarrolladores quieran usar la nueva biblioteca LibXYZ o el marco FrABC.

En alg√ļn momento, parte del c√≥digo o todo el proyecto pueden fusionarse con la base del c√≥digo de otro proyecto.

En medio de los gerentes, generalmente se acepta que tales problemas se resuelven con la ayuda de la documentaci√≥n. La documentaci√≥n, por supuesto, es buena y √ļtil, porque es tan genial cuando comienzas a trabajar en el proyecto que tienes cinco tickets abiertos, el gerente del proyecto pregunta c√≥mo has progresado y necesitas leer (y recordar) unos 150 p√°ginas de texto escritas lejos de escritores brillantes. Por supuesto, tuvo unos d√≠as o incluso un par de semanas para dedicar al proyecto, pero, si usa aritm√©tica simple, por un lado, 5,000,000 bytes de c√≥digo, por el otro, digamos, 50 horas de trabajo. Resulta que, en promedio, era necesario inyectar 100 Kb de c√≥digo por hora. Y aqu√≠ todo depende mucho de la calidad del c√≥digo. Si est√° limpio: f√°cil de ensamblar, bien estructurado y predecible, entonces verterlo en un proyecto parece ser un proceso notablemente menos doloroso. No es el √ļltimo papel en esto el dise√Īo de clase. No es el √ļltimo

Lo que queremos del dise√Īo de clase


De todo lo anterior, se pueden sacar muchas conclusiones interesantes sobre la arquitectura general, la pila de tecnolog√≠a, el proceso de desarrollo, etc. Pero desde el principio decidimos hablar sobre el dise√Īo de la clase, veamos qu√© cosas √ļtiles podemos aprender de lo que se dijo anteriormente con respecto a √©l.

  • Me gustar√≠a que un desarrollador que no est√© completamente familiarizado con el c√≥digo de la aplicaci√≥n pueda comprender lo que esta clase est√° haciendo cuando mira una clase. Y viceversa: al observar un requisito funcional o no funcional, pude adivinar r√°pidamente d√≥nde se encuentra la aplicaci√≥n en las clases responsables. Bueno, es deseable que la implementaci√≥n de los requisitos no se ‚Äúextienda‚ÄĚ en toda la aplicaci√≥n, sino que se concentre en una clase o en un grupo compacto de clases. Perm√≠tanme explicar con un ejemplo a qu√© tipo de antipatr√≥n me refiero. Supongamos que necesitamos verificar que 10 solicitudes de cierto tipo solo puedan ser ejecutadas por usuarios que tengan m√°s de 20 puntos en su cuenta (sin importar lo que eso signifique). Una mala manera de implementar dicho requisito es insertar un cheque al comienzo de cada solicitud. Entonces la l√≥gica se extender√° por 10 m√©todos, en diferentes controladores. Una buena manera es crear un filtro o WebRequestInterceptor y verificar todo en un solo lugar.
  • Quiero que los cambios en una clase que no afectan el contrato de clase no afecten, bueno, o (¬°seamos realistas!) Al menos no afectan mucho a otras clases. En otras palabras, quiero encapsular la implementaci√≥n de un contrato de clase.
  • Me gustar√≠a que fuera posible, al cambiar el contrato de clase, al pasar por la cadena de llamadas y al hacer el uso de encontrar, encontrar las clases a las que afecta este cambio. Es decir, quiero que las clases no tengan dependencias indirectas.
  • Si es posible, me gustar√≠a ver que los procesos de procesamiento de solicitudes que consisten en varios pasos de un solo nivel no est√°n manchados por el c√≥digo de varias clases, sino que se describen en el mismo nivel. Es muy bueno si el c√≥digo que describe dicho proceso de procesamiento cabe en una pantalla dentro de un m√©todo con un nombre claro. Por ejemplo, necesitamos encontrar todas las palabras en una l√≠nea, hacer una llamada a un servicio de terceros para cada palabra, obtener una descripci√≥n de la palabra, aplicar formato a la descripci√≥n y guardar los resultados en la base de datos. Esta es una secuencia de acciones en 4 pasos. Es muy conveniente entender el c√≥digo y cambiar su l√≥gica cuando hay un m√©todo en el que estos pasos van uno tras otro.
  • Realmente quiero que las mismas cosas en el c√≥digo se implementen de la misma manera. Por ejemplo, si accedemos a la base de datos inmediatamente desde el controlador, es mejor hacerlo en todas partes (aunque no llamar√≠a una buena pr√°ctica de dise√Īo). Y si ya hemos ingresado a los niveles de servicios y repositorios, entonces es mejor no contactar a la base de datos directamente desde el controlador.
  • Me gustar√≠a que el n√ļmero de clases / interfaces no directamente responsables de los requisitos funcionales y no funcionales no sea muy grande. Trabajar con un proyecto en el que hay dos interfaces para cada clase con l√≥gica, una jerarqu√≠a compleja de herencia de cinco clases, una f√°brica de clases y una f√°brica de clases abstractas, es bastante dif√≠cil.

Recomendaciones pr√°cticas


Una vez formulados los deseos, podemos esbozar pasos específicos que nos permitirán alcanzar nuestros objetivos.

Métodos estáticos


Como calentamiento, comenzaré con una regla relativamente simple. No debe crear métodos estáticos a menos que sean necesarios para el funcionamiento de una de las bibliotecas utilizadas (por ejemplo, debe crear un serializador para un tipo de datos).

En principio, no hay nada de malo en usar m√©todos est√°ticos. Si el comportamiento de un m√©todo depende completamente de sus par√°metros, ¬Ņpor qu√© no hacerlo realmente est√°tico? Pero debe tener en cuenta el hecho de que utilizamos Spring IoC, que sirve para vincular los componentes de nuestra aplicaci√≥n. Spring IoC se ocupa de los conceptos de frijoles y su alcance. Este enfoque se puede mezclar con m√©todos est√°ticos agrupados en clases, pero comprender esta aplicaci√≥n e incluso cambiar algo en ella (si, por ejemplo, necesita pasar alg√ļn par√°metro global a un m√©todo o clase) puede ser muy dif√≠cil.

Al mismo tiempo, los métodos estáticos en comparación con los contenedores de IoC ofrecen una ventaja muy insignificante en la velocidad de invocación de métodos. Y sobre esto, tal vez, las ventajas terminan.

Si no está creando una función empresarial que requiera una gran cantidad de llamadas ultrarrápidas entre diferentes clases, es mejor no utilizar métodos estáticos.

Aqu√≠ el lector puede preguntar: "¬ŅPero qu√© pasa con las clases StringUtils e IOUtils?" De hecho, se ha desarrollado una tradici√≥n en el mundo de Java: poner funciones auxiliares para trabajar con cadenas y flujos de entrada-salida en m√©todos est√°ticos y recopilarlos bajo el paraguas de las clases SomethingUtils. Pero esta tradici√≥n me parece bastante musgosa. Si lo sigue, por supuesto, no se espera un gran da√Īo: todos los programadores de Java est√°n acostumbrados. Pero no tiene sentido en una acci√≥n tan ritual. Por un lado, por qu√© no hacer que el bean StringUtils, por otro lado, si no hace que el bean y todos los m√©todos auxiliares sean est√°ticos, hagamos las clases paraguas est√°ticas StockTradingUtils y BlockChainUtils. Comenzar a poner la l√≥gica en m√©todos est√°ticos, dibujar un borde y detenerse es dif√≠cil. Te aconsejo que no comiences.

Finalmente, no olvide que con Java 11 muchos de los métodos auxiliares que habían estado desviando a los desarrolladores de un proyecto a otro durante décadas, se convirtieron en parte de la biblioteca estándar o se fusionaron en bibliotecas, por ejemplo, en Google Guava.

Contrato de clase atómico, compacto


Hay una regla simple que se aplica al desarrollo de cualquier sistema de software. Al observar cualquier clase, deber√≠a ser capaz de explicar de manera r√°pida y compacta, sin recurrir a largas excavaciones, lo que hace esta clase. Si es imposible encajar la explicaci√≥n en un p√°rrafo (no es necesario, sin embargo, expresado en una oraci√≥n), podr√≠a valer la pena pensar y dividir esta clase en varias clases at√≥micas. Por ejemplo, la clase "Busca archivos de texto en un disco y cuenta el n√ļmero de letras Z en cada uno de ellos" - un buen candidato para la descomposici√≥n "busca en un disco" + "cuenta el n√ļmero de letras".

Por otro lado, no haga clases demasiado peque√Īas, cada una de las cuales est√° dise√Īada para una acci√≥n. ¬ŅPero de qu√© tama√Īo deber√≠a ser la clase entonces? Las reglas b√°sicas son las siguientes:

  • Idealmente, cuando el contrato de clase coincide con la descripci√≥n de la funci√≥n comercial (o subfunci√≥n, dependiendo de c√≥mo se arreglen los requisitos). Esto no siempre es posible: si un intento de cumplir con esta regla lleva a la creaci√≥n de c√≥digo engorroso y no obvio, es mejor dividir la clase en partes m√°s peque√Īas.
  • Una buena m√©trica para evaluar la calidad de un contrato de clase es la relaci√≥n entre su complejidad intr√≠nseca y la complejidad del contrato. Por ejemplo, un contrato de clase muy bueno (aunque fant√°stico) puede verse as√≠: "La clase tiene un m√©todo que recibe una l√≠nea con una descripci√≥n del tema en ruso en la entrada y, como resultado, compone una historia de calidad o incluso una historia sobre un tema determinado". Aqu√≠, el contrato es simple y generalmente entendido. Su implementaci√≥n es extremadamente compleja, pero la complejidad est√° oculta dentro de la clase.

¬ŅPor qu√© es importante esta regla?

  • En primer lugar, la capacidad de explicarse claramente lo que hace cada una de las clases siempre es √ļtil. Desafortunadamente, lejos de cada proyecto, los desarrolladores pueden hacer esto. A menudo puede escuchar algo como: ‚ÄúBueno, este es un envoltorio sobre la clase Path, que de alguna manera creamos y a veces usamos en lugar de Path. Tambi√©n tiene un m√©todo que puede duplicar todas las rutas de File.separator. Necesitamos este m√©todo para guardar informes en la nube y, por alguna raz√≥n, termin√≥ en la clase Ruta ".
  • El cerebro humano es capaz de operar simult√°neamente con no m√°s de cinco a diez objetos. La mayor√≠a de las personas no tienen m√°s de siete. En consecuencia, si un desarrollador necesita operar con m√°s de siete objetos para resolver un problema, perder√° algo o se ver√° obligado a empaquetar varios objetos bajo un "paraguas" l√≥gico. Y si todav√≠a tiene que empacarlo, ¬Ņpor qu√© no hacerlo de inmediato, conscientemente, y darle a este paraguas un nombre significativo y un contrato claro?

¬ŅC√≥mo verificar que todo sea lo suficientemente granular? P√≠dale a un colega que le d√© 5 (cinco) minutos. Tome parte de la aplicaci√≥n que est√° trabajando actualmente en crear. Para cada clase, explique a un colega qu√© hace exactamente esta clase. Si no encaja en 5 minutos, o un colega no puede entender por qu√© se necesita esta o aquella clase, tal vez deber√≠a cambiar algo. Bueno, o no cambiar y volver a realizar el experimento, con otro colega.

Dependencias de clase


Supongamos que necesitamos seleccionar secciones de texto vinculadas de más de 100 bytes para un archivo PDF empaquetado en un archivo ZIP y guardarlas en la base de datos. Un antipatrón popular en tales casos se ve así:

  • Hay una clase que abre un archivo ZIP, busca un archivo PDF y lo devuelve como InputStream.
  • Esta clase tiene un enlace a una clase que busca en p√°rrafos de texto PDF.
  • La clase que funciona con PDF, a su vez, tiene un enlace a la clase que almacena datos en la base de datos.

Por un lado, todo parece lógico: datos recibidos, llamados directamente a la siguiente clase en la cadena. Pero al mismo tiempo, los contratos de la clase en la parte superior de la cadena se mezclan en los contratos y las dependencias de todas las clases que van en la cadena detrás de ella. Es mucho más correcto hacer que estas clases sean atómicas e independientes entre sí, y crear otra clase que realmente implemente la lógica de procesamiento conectando estas tres clases entre sí.

Cómo no hacerlo



¬ŅQu√© est√° mal aqu√≠? La clase que funciona con archivos ZIP pasa datos a la clase que procesa el PDF, y que, a su vez, pasa los datos a la clase que trabaja con la base de datos. Esto significa que la clase que funciona con ZIP, como resultado, por alguna raz√≥n depende de las clases que funcionan con la base de datos. Adem√°s, la l√≥gica de procesamiento se distribuye en tres clases y, para comprenderla, debemos repasar las tres clases. ¬ŅQu√© sucede si necesita p√°rrafos de texto obtenidos de PDF para pasar a un servicio de terceros a trav√©s de una llamada REST? Deber√° cambiar la clase que funciona con PDF, y dibujar en ella tambi√©n funciona con REST.

Cómo hacerlo:



Aquí tenemos cuatro clases:

  • Una clase que funciona solo con un archivo ZIP y devuelve una lista de archivos PDF (se puede argumentar, devolver archivos es malo, son grandes y romper√°n la aplicaci√≥n. Pero en este caso, leamos la palabra "retornos" en sentido amplio. Por ejemplo, devuelve Stream desde InputStream )
  • La segunda clase es responsable de trabajar con PDF.
  • La tercera clase no sabe y no puede hacer nada excepto guardar p√°rrafos en la base de datos.
  • Y la cuarta clase, que consiste literalmente en varias l√≠neas de c√≥digo, contiene toda la l√≥gica de negocios que cabe en una pantalla.

Destaco una vez m√°s que en 2019 en Java hay al menos dos buenas (y algo menos
bueno) formas de no transferir archivos y una lista completa de todos los p√°rrafos como objetos en la memoria. Esto es:

  1. API Java Stream
  2. Devoluciones de llamada Es decir, una clase con una función empresarial no transfiere datos directamente, pero dice ZIP Extractor: aquí hay una devolución de llamada para usted, busque archivos PDF en un archivo ZIP, cree un InputStream para cada archivo y llame a la devolución de llamada transferida con él.

Comportamiento implícito


Cuando no estamos tratando de resolver un problema completamente nuevo que nadie resolvi√≥ anteriormente, sino que, por el contrario, hacemos algo que otros desarrolladores ya han hecho varios cientos (o cientos de miles) antes, todos los miembros del equipo tienen algunas expectativas con respecto a la complejidad ciclom√°tica y el consumo de recursos de la soluci√≥n. . Por ejemplo, si necesitamos encontrar en un archivo todas las palabras que comienzan con la letra z, esta es una lectura secuencial y √ļnica del archivo en bloques del disco. Es decir, si se enfoca en https://gist.github.com/jboner/2841832, tal operaci√≥n tomar√° varios microsegundos por 1 MB, bueno, tal vez, dependiendo del entorno de programaci√≥n y la carga del sistema, varias decenas o incluso cien microsegundos, pero Ni un segundo en absoluto. Tomar√° varias decenas de kilobytes de memoria (omitimos la pregunta de qu√© estamos haciendo con los resultados, esta es la preocupaci√≥n de otra clase), y el c√≥digo probablemente ocupar√° aproximadamente una pantalla. Al mismo tiempo, esperamos que no se utilicen otros recursos del sistema. Es decir, el c√≥digo no crear√° hilos, escribir√° datos en el disco, enviar√° paquetes a trav√©s de la red y guardar√° datos en la base de datos.

Esta es la expectativa habitual de una llamada al método:

zWordFinder.findZWords(inputStream). ... 

Si el c√≥digo de su clase no cumple con estos requisitos por alguna raz√≥n razonable, por ejemplo, para clasificar una palabra en z y no en z, debe llamar al m√©todo REST cada vez (no s√© por qu√© esto podr√≠a ser necesario, pero imaginemos esto) es necesario escribir con mucho cuidado en el contrato de clase, y es muy bueno si el nombre del m√©todo indica que el m√©todo se est√° ejecutando en alg√ļn lugar para consultar.

Si no tiene una razón razonable para un comportamiento implícito, reescriba la clase.

¬ŅC√≥mo entender las expectativas de la complejidad y la intensidad de los recursos del m√©todo? Debe recurrir a una de estas formas simples:

  1. Con experiencia, gane horizontes bastante amplios.
  2. Preg√ļntele a un colega: esto siempre se puede hacer.
  3. Antes de comenzar el desarrollo, hable con los miembros del equipo sobre el plan de implementación.
  4. Para hacerse la pregunta: "¬ŅPero no uso demasiados recursos redundantes en este m√©todo?" Esto suele ser suficiente.

No necesita involucrarse en la optimización también: guardar 100 bytes cuando los usa la clase 100,000 no tiene mucho sentido para la mayoría de las aplicaciones.

Esta regla abre una ventana al rico mundo de la sobre ingenier√≠a, ocultando respuestas a preguntas como "¬Ņpor qu√© no deber√≠a pasar un mes para ahorrar 10 bytes de memoria en una aplicaci√≥n que necesita 10 GB para funcionar"? Pero no desarrollar√© este tema aqu√≠. Ella merece un art√≠culo separado.

Nombres de métodos implícitos


En la programación Java, actualmente hay varias convenciones implícitas con respecto a los nombres de clase y su comportamiento. No hay muchos de ellos, pero es mejor no romperlos. Trataré de enumerar los que se me ocurren:

  • Constructor: crea una instancia de la clase, puede crear algunas estructuras de datos bastante ramificadas, pero no funciona con la base de datos, no escribe en el disco, no env√≠a datos a trav√©s de la red (dir√©, el registrador incorporado puede hacer todo esto, pero esta es una historia diferente en en cualquier caso, recae en la conciencia del configurador de registro).
  • Getter - getSomething () - devuelve alg√ļn tipo de estructura de memoria desde las profundidades del objeto. Nuevamente, no escribe en el disco, no realiza c√°lculos complejos, no env√≠a datos a trav√©s de la red, no funciona con la base de datos (excepto cuando este es un campo ORM lento, y esta es solo una de las razones por las que los campos perezosos deben usarse con mucho cuidado) .
  • Setter - setSomething (Algo algo) - establece el valor de la estructura de datos, no hace c√°lculos complejos, no env√≠a datos a trav√©s de la red, no funciona con la base de datos. Por lo general, no se espera que el emisor implique un comportamiento o consumo de recursos inform√°ticos significativos.
  • equals () y hashcode (): no se espera nada, excepto c√°lculos simples y comparaciones en una cantidad que depende linealmente del tama√Īo de la estructura de datos. Es decir, si llamamos a hashcode para un objeto de tres campos primitivos, se espera que se ejecuten N * 3 instrucciones computacionales simples.
  • toSomething (): tambi√©n se espera que sea un m√©todo que convierta un tipo de datos en otro, y para la conversi√≥n solo necesita una cantidad de memoria comparable al tama√Īo de las estructuras y el tiempo del procesador que depende linealmente del tama√Īo de las estructuras. Aqu√≠ debe tenerse en cuenta que la conversi√≥n de tipos no siempre se puede realizar de forma lineal, por ejemplo, convertir una imagen de p√≠xeles a un formato SVG puede ser una acci√≥n muy poco trivial, pero en este caso es mejor nombrar el m√©todo de manera diferente. Por ejemplo, el nombre computeAndConvertToSVG ‚Äč‚Äč() parece algo inc√≥modo, pero inmediatamente sugiere que se est√°n realizando algunos c√°lculos significativos en el interior.

Dar√© un ejemplo. Recientemente hice una auditor√≠a de la aplicaci√≥n. Por la l√≥gica del trabajo, s√© que la aplicaci√≥n en alg√ļn lugar del c√≥digo se suscribe a la cola RabbitMQ. Estoy caminando por el c√≥digo, no puedo encontrar este lugar. Estoy buscando directamente un atractivo para el conejo, estoy empezando a subir, voy al lugar en el flujo de negocios, donde la suscripci√≥n realmente se lleva a cabo, estoy empezando a jurar. C√≥mo se ve en el c√≥digo:

  1. Se llama al m√©todo service.getQueueListener (tickerName): se ignora el resultado devuelto. Esto puede alertar, pero ese c√≥digo en el que se ignoran los resultados del m√©todo no es el √ļnico en la aplicaci√≥n.
  2. En el interior, tickerName se verifica para nulo y se llama a otro método getQueueListenerByName (tickerName).
  3. En su interior, una instancia de la clase QueueListener se toma del hash con el nombre del ticker (si no es así, se crea), y se llama al método getSubscription ().
  4. Y ahora, dentro del m√©todo getSubscription (), la suscripci√≥n realmente tiene lugar. Y sucede en alg√ļn lugar en medio de un m√©todo del tama√Īo de tres pantallas.

Te diré francamente: sin recorrer toda la cadena y sin leer una docena de pantallas atentas de código, no era realista adivinar dónde está la suscripción. Si el método se llamara subscribeToQueueByTicker (tickerName), me ahorraría mucho tiempo.

Clases de utilidad


Hay un maravilloso libro Design Patterns: Elements of Reusable Object-Oriented Software (1994), a menudo se llama GOF (Gang of Four, por el n√ļmero de autores). El beneficio de este libro radica principalmente en que brind√≥ a los desarrolladores de diferentes pa√≠ses un solo idioma para describir los patrones de dise√Īo de clase. Ahora, en lugar de "se garantiza que la clase existe en una sola instancia y que tiene un punto de acceso est√°tico", podemos decir "singleton". El mismo libro caus√≥ da√Īos notables a las mentes fr√°giles. Este da√Īo est√° bien descrito por una cita de uno de los foros "Colegas, necesito crear una tienda web, d√≠ganme con qu√© plantillas debo comenzar". En otras palabras, algunos programadores tienden a abusar de los patrones de dise√Īo, y donde sea que se pueda administrar con una clase, a veces crean cinco o seis a la vez, por si acaso, "para mayor flexibilidad".

¬ŅC√≥mo decidir si necesita una f√°brica de clases abstractas (u otro patr√≥n m√°s complicado que una interfaz) o no? Hay algunas consideraciones simples:

  1. Si est√° escribiendo una solicitud en Spring, no se necesita el 99% del tiempo. Spring te ofrece bloques de construcci√≥n de nivel superior, √ļsalos. Lo m√°ximo que puede encontrar √ļtil es una clase abstracta.
  2. Si el punto 1 a√ļn no le dio una respuesta clara, recuerde que cada plantilla es +1000 puntos a la complejidad de la aplicaci√≥n. Analice cuidadosamente si los beneficios de usar la plantilla son mayores que los da√Īos causados ‚Äč‚Äčpor ella. Volviendo a una met√°fora, recuerde que cada medicamento no solo cura, sino que tambi√©n da√Īa levemente. No tome todas las pastillas a la vez.


Un buen ejemplo de cómo no lo necesita, puede ver aquí .

Conclusión


Para resumir, quiero se√Īalar que he enumerado las recomendaciones m√°s b√°sicas. Ni siquiera los sacar√≠a en forma de art√≠culo, son tan obvios. Pero durante el a√Īo pasado, me he encontrado con aplicaciones muy a menudo en las que se han violado muchas de estas recomendaciones. Escribamos un c√≥digo simple que sea f√°cil de leer y f√°cil de mantener.

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


All Articles