Código limpio: consecuencias de métodos cortos con pocos parámetros

12

Recientemente, durante una revisión del código, encontré un código, escrito por un nuevo colega, que contiene un patrón con olor. Sospecho que las decisiones de mi colega se basan en las reglas propuestas por el famoso libro Clean Code (y quizás también por otros libros similares).

Tengo entendido que el constructor de la clase es completamente responsable de la creación de un objeto válido y que su tarea principal es la asignación de las propiedades (privadas) de un objeto. Por supuesto, podría ocurrir que los valores de las propiedades opcionales se puedan establecer mediante métodos distintos al constructor de la clase, pero estas situaciones son bastante raras (aunque no necesariamente erróneas, siempre que el resto de la clase tenga en cuenta la opcionalidad de dicha propiedad). Esto es importante porque permite garantizar que el objeto esté siempre en un estado válido.

Sin embargo, en el código que encontré, la mayoría de los valores de las propiedades se establecen en realidad por métodos distintos al constructor. Los valores que resultan de los cálculos se asignan a las propiedades que se utilizarán dentro de varios métodos privados en toda la clase. El autor aparentemente usa las propiedades de la clase como si fueran variables globales que deberían ser accesibles en toda la clase, en lugar de parametrizar estos valores a las funciones que los necesitan. Además, los métodos de la clase deben llamarse en un orden específico, porque la clase no hará mucho de lo contrario.

Sospecho que este código se ha inspirado en el consejo de mantener los métodos cortos (< = 5 líneas de código), para evitar grandes listas de parámetros (< 3 parámetros) y que los constructores no deben trabajar (como realizar un cálculo de algún tipo que es esencial para la validez del objeto).

Ahora, por supuesto, podría argumentar en contra de este patrón si puedo demostrar que potencialmente todos los tipos de errores indefinidos surgen cuando los métodos no se llaman en un orden específico. Sin embargo, predigo que la respuesta a esto será la adición de validaciones que verifiquen que las propiedades deben establecerse una vez que se llaman los métodos que necesitan que esas propiedades estén establecidas.

Sin embargo, me gustaría proponer cambiar por completo el código, de modo que la clase se convierta en una copia impresa de un objeto real, en lugar de una serie de métodos que deberían llamarse (de manera procesal) en un orden específico.

Siento que el código que encontré huele. De hecho, creo que existe una distinción bastante clara en cuanto a cuándo guardar un valor en una propiedad de clase y cuándo ponerlo en un parámetro para que se use un método diferente. Realmente no creo que puedan ser alternativas entre sí. . Estoy buscando las palabras para esta distinción.

    
pregunta user2180613 21.10.2016 - 00:03

3 respuestas

12

Como alguien que ha leído Clean Code y ha visto la serie Clean Coders, varias veces, y con frecuencia enseña y enseña a otras personas a escribir un código más limpio. De hecho, puedo confirmar que sus observaciones son correctas: todas las métricas que señala son todas mencionadas. en el libro.

Sin embargo, el libro continúa con otros puntos que también deben aplicarse junto con las pautas que usted señaló. Estos fueron aparentemente ignorados en el código con el que estás tratando. Esto puede haber ocurrido porque su colega todavía está en la fase de aprendizaje, en cuyo caso, por más que sea necesario señalar los olores de su código, es bueno recordar que lo están haciendo. De buena voluntad, aprendiendo y tratando de escribir mejor código.

Clean Code propone que los métodos sean cortos, con la menor cantidad de argumentos posibles. Pero a lo largo de esos lineamientos, propone que debemos seguir S los principios OLID, aumentar la cohesión y reducir el acoplamiento .

El S en SOLID significa Principio de responsabilidad única, que establece que un objeto debe ser responsable de una sola cosa. "Cosa" no es un término muy preciso, por lo que las descripciones de este principio varían enormemente. Sin embargo, el tío Bob, autor de Clean Code, es también la persona que acuñó este principio, describiéndolo como: "Reúna las cosas que cambian por las mismas razones. Separe las cosas que cambian por diferentes razones". Continúa diciendo lo que quiere decir con razones para cambiar aquí y aquí (una explicación más larga aquí sería demasiado). Si este principio se aplicó a la clase con la que está tratando, es muy probable que las partes que tratan con los cálculos se separen de las que tienen que ver con el estado de espera, al dividir la clase en dos o más, dependiendo de cuántos < em> razones para cambiar esos cálculos tienen.

Además, las clases Limpiar deben ser cohesivas , lo que significa que la mayoría de sus métodos utilizan la mayoría de sus atributos. Como tal, una clase cohesiva máxima es aquella en la que todos los métodos usan todos sus atributos; como ejemplo, en una aplicación gráfica puede tener una clase Vector con atributos Point a y Point b , donde los únicos métodos son scaleBy(double factor) y printTo(Canvas canvas) , ambos operando en ambos atributos. En contraste, una clase mínimamente cohesiva es aquella en la que cada atributo se usa en un solo método, y nunca se usa más de un atributo por cada método. En promedio, una clase presenta "grupos" no cohesivos de partes cohesivas, es decir, algunos métodos usan los atributos a , b y c , mientras que el resto usa c y d - lo que significa que si Dividimos la clase en dos, terminamos con dos objetos cohesivos.

Finalmente, las clases Limpiar deberían reducir el acoplamiento tanto como sea posible. Si bien hay muchos tipos de acoplamiento que vale la pena discutir aquí, parece que el código en cuestión sufre principalmente de acoplamiento temporal , donde, como señalaste, los métodos del objeto solo funcionarán como se espera cuando se llaman en el orden correcto. Y al igual que las dos pautas mencionadas anteriormente, las soluciones a esto generalmente implican dividir la clase en dos o más objetos cohesivos . La estrategia de división en este caso usualmente involucra patrones como Builder o Factory, y en casos altamente complejos, State-Machines.

The TL; DR: Las pautas del Código de limpieza que siguió su colega son buenas, pero solo cuando también se siguen los principios, prácticas y patrones restantes mencionados en el libro. La versión Limpia de la "clase" que está viendo se dividirá en varias clases, cada una con una única responsabilidad, métodos coherentes y sin acoplamiento temporal. Este es el contexto en el que los métodos pequeños y los argumentos de poco o nada tienen sentido.

    
respondido por el MichelHenrich 21.10.2016 - 04:06
9
  

Tengo entendido que el constructor de la clase es completamente responsable de la creación de un objeto válido y que su tarea principal es la asignación de las propiedades (privadas) de un objeto.

Por lo general, es responsable de poner el objeto en un estado inicial válido, sí; otras propiedades o métodos pueden cambiar el estado a otro estado válido.

  

Sin embargo, en el código que encontré, la mayoría de los valores de propiedad son   En realidad establecido por otros métodos que el constructor. Valores que resultan   Los cálculos se asignan a las propiedades para ser utilizados dentro de varios   Métodos privados en toda la clase. El autor aparentemente usa la clase   Propiedades como si fueran variables globales que deberían ser accesibles.   en toda la clase, en lugar de parametrizar estos valores a la   Funciones que los necesitan. Adicionalmente, los métodos de la clase.   debe llamarse en un orden específico, porque la clase no hará mucho   de lo contrario.

Además de los problemas de legibilidad y mantenibilidad a los que alude, parece que hay múltiples etapas de flujo / transformación de datos dentro de la propia clase, lo que puede indicar que la clase no cumple con el principio de responsabilidad única. .

  

sospecho que este código ha sido inspirado por el consejo de mantener   Métodos cortos (< = 5 líneas de código), para evitar grandes listas de parámetros (< 3   parámetros) y que los constructores no deben trabajar (como realizar   Un cálculo de algún tipo que es esencial para la validez de la   objeto).

Seguir algunas pautas de codificación, mientras que ignorar otras, a menudo conduce a un código tonto. Si queremos evitar que un constructor haga un trabajo, por ejemplo, la forma razonable generalmente sería hacer el trabajo antes de la construcción y pasar el resultado de ese trabajo al constructor. (Un argumento para ese enfoque podría ser que está evitando darle a su clase dos responsabilidades: el trabajo de su inicialización y su 'trabajo principal', sea lo que sea).

En mi experiencia, hacer que las clases y los métodos sean pequeños rara vez es algo que tengo que tener en cuenta como una consideración por separado, más bien, se desprende de forma natural de una sola responsabilidad.

  

Sin embargo, me gustaría proponer cambiar completamente el código, para que   la clase se convierte en una impresión azul de un objeto real, en lugar de una   serie de métodos que deberían ser llamados (de manera procesal) en un   orden.

Probablemente sería correcto hacerlo. No hay nada de malo en escribir código de procedimiento directo; hay un problema al abusar del paradigma OO para escribir código de procedimiento ofuscado.

  

Creo que existe una distinción bastante clara de cuándo guardar un   valor en una propiedad de clase y cuándo ponerlo en un parámetro para una   Método diferente de usar - realmente no creo que puedan ser   alternativas entre sí. Estoy buscando las palabras para esto.   distinción.

Generalmente , no deberías poner un valor en un campo como una forma de pasarlo de un método a otro; un valor en un campo debe ser una parte significativa del estado de un objeto en un momento dado. (Puedo pensar en algunas excepciones válidas, pero no en aquellas donde dichos métodos son públicos o donde hay una dependencia de orden que un usuario de la clase debería conocer)

    
respondido por el topo morto 21.10.2016 - 00:32
5

Lo más probable es que estés luchando contra el patrón equivocado aquí. Las funciones pequeñas y la baja aridad rara vez son problemáticas por sí solas. El problema real aquí es el acoplamiento que causa la dependencia del orden entre las funciones, así que busque formas de solucionarlo sin desechar los beneficios de las funciones pequeñas.

El código habla más fuerte que las palabras. Las personas reciben este tipo de correcciones mucho mejor si realmente puede hacer parte de la refactorización y mostrar la mejora, tal vez como un ejercicio de programación en pareja. Cuando hago esto, a menudo encuentro que es más difícil de lo que pensé lograr un diseño correcto, equilibrando todos los criterios.

    
respondido por el Karl Bielefeldt 21.10.2016 - 03:00

Lea otras preguntas en las etiquetas