¿Es un código como este un "choque de trenes" (en violación de la Ley de Demeter)?

23

Al navegar por el código que he escrito, me encontré con la siguiente construcción que me hizo pensar. A primera vista, parece lo suficientemente limpio. Sí, en el código real, el getLocation()método tiene un nombre un poco más específico que describe mejor exactamente qué ubicación se obtiene.

service.setLocation(this.configuration.getLocation().toString());

En este caso, servicees una variable de instancia de un tipo conocido, declarada dentro del método. this.configurationproviene de ser pasado al constructor de la clase, y es una instancia de una clase que implementa una interfaz específica (que exige un getLocation()método público ). Por lo tanto, this.configuration.getLocation()se conoce el tipo de retorno de la expresión ; específicamente en este caso, es un java.net.URL, mientras que service.setLocation()quiere un String. Dado que los dos tipos String y URL no son directamente compatibles, se requiere algún tipo de conversión para ajustar la clavija cuadrada en el agujero redondo.

Sin embargo , de acuerdo con la Ley de Demeter como se cita en código limpio , un método de f en la clase C sólo debe llamar a métodos en C , los objetos creados por o pasado como argumentos a f , y objetos celebrada en variables de instancia de C . Cualquier cosa más allá de eso (la final toString()en mi caso particular anterior, a menos que considere un objeto temporal creado como resultado de la invocación del método en sí, en cuyo caso toda la Ley parece ser discutible) no está permitido.

¿Existe un razonamiento válido por el cual una llamada como la anterior, dadas las restricciones enumeradas, debe ser desalentada o incluso rechazada? ¿O solo estoy siendo demasiado quisquilloso?

Si tuviera que implementar un método URLToString()que simplemente llama toString()a un URLobjeto (como el devuelto por getLocation()) pasado como parámetro y devuelve el resultado, podría ajustar la getLocation()llamada para lograr exactamente el mismo resultado; efectivamente, solo movería la conversión un paso hacia afuera. ¿Eso de alguna manera lo haría aceptable? (Me parece , intuitivamente, que no debería hacer ninguna diferencia de ninguna manera, ya que todo lo que hace es mover un poco las cosas. Sin embargo, siguiendo la letra de la Ley de Deméter como se citó, sería aceptable, ya que yo entonces estaría operando directamente en un parámetro a una función).

¿Habría alguna diferencia si se tratara de algo un poco más exótico que recurrir toString()a un tipo estándar?

Al responder, tenga en cuenta que alterar el comportamiento o la API del tipo de la servicevariable no es práctico. Además, en aras de la discusión, digamos que alterar el tipo de retorno de getLocation()tampoco es práctico.

un CVn
fuente

Respuestas:

34

El problema aquí es la firma de setLocation. Está mecanografiado en cadena .

Para elaborar: ¿Por qué esperaría String? A Stringrepresenta cualquier tipo de datos textuales . Potencialmente puede ser cualquier cosa menos una ubicación válida.

De hecho, esto plantea una pregunta: ¿qué es una ubicación? ¿Cómo yo sé sin mirar en su código? Si fuera un URL, sabría mucho más sobre lo que este método espera.
Tal vez tendría más sentido que fuera una clase personalizada Location. Ok, al principio no sabría qué es eso, pero en algún momento (probablemente antes de escribir this.configuration.getLocation()me tomaría un minuto para descubrir qué es lo que devuelve este método).
De acuerdo, en ambos casos necesito buscar otro lugar para entender lo que se espera. Sin embargo, en el último caso, si entiendo, qué Locationes, puedo usar su API, en el primer caso, si entiendo, qué Stringes (lo que se puede esperar), todavía no sé qué espera su API.

En el escenario poco probable, que una ubicación es cualquier tipo de datos textuales , reinterpretaría esto a cualquier tipo de datos que tengan una representación textual . Dado el hecho de que Objecttiene un toStringmétodo, podría ir con eso, aunque esto exige un gran salto de fe de los clientes de su código.

También debe considerar que está hablando de Java, que tiene muy pocas características por diseño. Eso es lo que te obliga a llamar toStringal final.
Si toma C #, por ejemplo, que también está estáticamente tipado, entonces podría omitir esa llamada definiendo el comportamiento para una conversión implícita .
En lenguajes de tipo dinámico, como Objective-C, tampoco necesita la conversión, porque siempre que el valor se comporte como una cadena, todos estarán contentos.

Se podría argumentar que la última llamada a toStringes menos una llamada, que en realidad solo ruido generado por la demanda de explicidad de Java. Está llamando a un método que tiene cualquier objeto Java, por lo tanto, en realidad no codifica ningún conocimiento sobre una "unidad distante" y, por lo tanto, no viola el Principio de Menos Conocimiento. No hay forma, no importa lo que getLocationregrese, de que no tenga un toStringmétodo.

Pero, por favor, no use cadenas, a menos que sean realmente la opción más natural (o a menos que esté usando un lenguaje, que ni siquiera tiene enumeraciones ... estado allí).

back2dos
fuente
Tendería a estar de acuerdo con usted, pero ya dijo que no puede modificar la API del servicio.
jhocking
1
@jhocking: No dijo que no puede. Dijo que no es práctico. Estoy en desacuerdo. Intentar aplicar las mejores prácticas al código que funciona alrededor de fallas en el diseño de la API realmente solo tiene sentido si tales soluciones alternativas son la única opción posible. Sin embargo, establecer la API directamente es la mejor opción. Eliminar las deficiencias siempre es preferible a solucionarlas.
back2dos
así de todos modos por 1 "es menos que una llamada en realidad sólo el ruido generado por la demanda de Java para explícito"
jhocking
1
Daría este +1 incluso si solo fuera por "mecanografiado en cadena". En mi código, trato de pasar tipos que expresan significado / intención tanto como sea posible, pero cuando trabajo con bibliotecas y API de terceros, a veces te quedas atascado usando lo que decidieron los autores de esa API. Y IIRC, una ubicación puede ser "cualquier tipo de datos textuales", pero para la implementación en la que estoy trabajando, una ubicación no URL no tiene ningún sentido.
un CVn
1
En cuanto a cambiar la API; este es un software de computadora, por lo que prácticamente siempre es posible cambiar las cosas. (Si nada más, siempre puede escribir una capa de abstracción.) Sin embargo, a veces hacerlo implica demasiado esfuerzo tanto a corto como a largo plazo para ser justificable. Entonces, podría ser perfectamente posible hacer tales cambios, pero aún no es práctico.
un CVn
21

La ley de Demeter es una directriz de diseño, no una ley a seguir religiosamente.

Si cree que sus clases están lo suficientemente desacopladas, no hay nada de malo en la línea, this.configuration.getLocation()especialmente si, como usted dice, no es práctico cambiar otras partes de la API.

Estoy bastante seguro de que el cliente estará perfectamente feliz incluso si hace la Ley de Demeter en pedazos, siempre que entregue lo que quiere y a tiempo. Lo cual no es una excusa para crear un mal software, sino un recordatorio para ser pragmático al desarrollar software.

Trasplazio Garzuglio
fuente
1
Dado que this.configurationes una variable de instancia de un tipo de interfaz conocida, llamar a un método definido por esa interfaz parece estar bien incluso de acuerdo con una interpretación estricta. Sí, sé que es una guía, al igual que KISS, SOLID, YAGNI, etc. Hay muy pocas o ninguna "ley" (en el sentido legal) en el desarrollo de software en general.
un CVn
44
+1 por ser pragmático ;-)
Treb
1
No creo que la Ley del Dementador deba aplicarse incluso en un caso como este: la configuración es básicamente un contenedor. En cada API con la que he trabajado, mirar dentro de los contenedores es un comportamiento normal y esperado.
Loren Pechtel
2

Lo único que se me ocurre al no escribir código como este es ¿qué pasa si this.configuration.getLocation()devuelve nulo? Depende de su código circundante y del público objetivo que usa este código. Pero como dice Marco, la ley de Demeter es una regla general, es bueno seguirla, pero no te rompas la espalda innecesariamente.

Martyn
fuente
Si this.configuration.getLocation()devuelve nulo, entonces (a) lo más probable es que nunca hubiéramos llegado tan lejos, o (b) algo catastrófico ha sucedido en el ínterin, en cuyo caso me gustaría que el código fallara. Entonces, si bien este es definitivamente un punto válido en general, en este caso particular es bastante seguro decir que no se aplica. Además, todo esto y mucho más es un controlador de excepción diseñado específicamente para lidiar con ese tipo de fallas inesperadas.
un CVn
2

Seguir estrictamente la Ley de Demeter significaría que debe implementar un método en el objeto de configuración como:

function getLocationAsString() {
  return getLocation().toString();
}

pero personalmente no me molestaría porque esta es una situación tan pequeña, y de todos modos tus manos están un poco atadas debido a la API que no puedes cambiar. Las reglas de programación son sobre lo que debe hacer cuando tiene una opción, pero a veces no tiene otra opción.

jhocking
fuente
1
Eso es lo mismo sugerido aquí: c2.com/cgi/wiki?TrainWreck "Cree un método que represente el comportamiento deseado y le diga al cliente qué hacer. Esto sigue el principio" diga, no pregunte ".
heltonbiker