Permítanme introducir esto diciendo que este no es mi código ni el código de mis compañeros de trabajo. Hace años, cuando nuestra empresa era más pequeña, teníamos algunos proyectos que necesitábamos hacer para los que no teníamos capacidad, por lo que se subcontrataron. Ahora, no tengo nada en contra de la contratación externa o los contratistas en general, pero la base de código que produjeron es una masa de WTF. Dicho esto, funciona (principalmente), por lo que supongo que se encuentra en el 10% de los proyectos subcontratados que he visto.
A medida que nuestra empresa ha crecido, hemos tratado de aprovechar más nuestro desarrollo internamente. Este proyecto en particular aterrizó en mi regazo, así que lo he estado revisando, limpiando, agregando pruebas, etc.
Hay un patrón que veo que se repite mucho y parece tan increíblemente horrible que me pregunté si tal vez hay una razón y simplemente no lo veo. El patrón es un objeto sin miembros o métodos públicos, solo un constructor público que hace todo el trabajo del objeto.
Por ejemplo, (el código está en Java, si eso importa, pero espero que sea una pregunta más general):
public class Foo {
private int bar;
private String baz;
public Foo(File f) {
execute(f);
}
private void execute(File f) {
// FTP the file to some hardcoded location,
// or parse the file and commit to the database, or whatever
}
}
Si se pregunta, este tipo de código a menudo se llama de la siguiente manera:
for(File f : someListOfFiles) {
new Foo(f);
}
Ahora, me enseñaron hace mucho tiempo que los objetos instanciados en un bucle generalmente son una mala idea, y que los constructores deben hacer un mínimo de trabajo. Al mirar este código, parece que sería mejor soltar el constructor y crear execute
un método estático público.
Le pregunté al contratista por qué se hizo de esta manera, y la respuesta que obtuve fue "Podemos cambiarlo si lo desea". Lo cual no fue realmente útil.
De todos modos, ¿hay alguna razón para hacer algo como esto, en algún lenguaje de programación, o es solo otra presentación al Daily WTF?
public static void main(string[] args)
y han oído hablar de objetos, y luego trataron de combinarlos.Respuestas:
Ok, bajando la lista:
Hace mucho tiempo me enseñaron que los objetos instanciados en un bucle generalmente son una mala idea
No en ningún idioma que haya usado.
En C es una buena idea declarar sus variables por adelantado, pero eso es diferente de lo que dijo. Puede ser un poco más rápido si declara objetos por encima del ciclo y los reutiliza, pero hay muchos lenguajes en los que este aumento de velocidad no tendrá sentido (y probablemente algunos compiladores que hacen la optimización por usted :)).
En general, si necesita un objeto dentro de un bucle, cree uno.
los constructores deben hacer un mínimo de trabajo
Los constructores deben crear instancias de los campos de un objeto y hacer cualquier otra inicialización necesaria para que el objeto esté listo para usar. Esto generalmente significa que los constructores son pequeños, pero hay escenarios en los que esto supondría una cantidad considerable de trabajo.
¿Hay alguna razón para hacer algo como esto, en algún lenguaje de programación, o es solo otra presentación al Daily WTF?
Es una presentación al Daily WTF. De acuerdo, hay cosas peores que puedes hacer con el código. El problema es que el autor tiene un gran malentendido sobre qué son las clases y cómo usarlas. Específicamente, esto es lo que veo que está mal con este código:
fuente
Para mí, es un poco sorprendente cuando obtienes un objeto que hace mucho trabajo en el constructor, así que solo recomendaría eso (principio de menor sorpresa).
Un intento de un buen ejemplo :
No estoy seguro de si este uso es un antipatrón, pero en C # he visto código que estaba en la línea de (ejemplo inventado):
En este caso, la
ImpersonationContext
clase hace todo su trabajo en el constructor y el método Dispose. Aprovecha lausing
declaración de C # , que los desarrolladores de C # entienden bastante bien, y por lo tanto garantiza que la configuración que se produce en el constructor se revierte una vez que estamos fuera de lausing
declaración, incluso si se produce una excepción. Este es probablemente el mejor uso que he visto para esto (es decir, "trabajo" en el constructor), aunque todavía no estoy seguro de considerarlo "bueno". En este caso, es bastante autoexplicativo, funcional y sucinto.Un ejemplo de un patrón bueno y similar sería el patrón de comando . Definitivamente no ejecuta la lógica en el constructor allí, pero es similar en el sentido de que toda la clase es equivalente a una invocación de método (incluidos los parámetros necesarios para invocar el método). De esta manera, la información de invocación del método se puede serializar o pasar para su uso posterior, lo cual es inmensamente útil. Quizás sus contratistas intentaron algo similar, aunque lo dudo mucho.
Comentarios sobre tu ejemplo:
Yo diría que es un antipatrón muy definido. No agrega nada, va en contra de lo que se espera y realiza un procesamiento demasiado largo en el constructor (¿FTP en el constructor? WTF de hecho, aunque supongo que la gente llama a los servicios web de esta manera).
Estoy luchando por encontrar la cita, pero el libro de Grady Booch "Object Solutions" tiene una anécdota sobre un equipo de desarrolladores de C en transición a C ++. Aparentemente, habían recibido capacitación y la gerencia les dijo que tenían que hacer cosas "orientadas a objetos". Presentado para descubrir qué está mal, el autor usó una herramienta de métrica de código y descubrió que el número promedio de métodos por clase era exactamente 1, y eran variaciones de la frase "Hazlo". Debo decir que nunca antes me he encontrado con este problema en particular, pero aparentemente puede ser un síntoma de que las personas se ven obligadas a crear código orientado a objetos, sin comprender realmente cómo hacerlo y por qué.
fuente
No.
Si todo el trabajo se realiza en el constructor, significa que el objeto nunca fue necesario en primer lugar. Lo más probable es que el contratista simplemente estuviera usando el objeto para modularidad. En ese caso, una clase no instanciada con un método estático habría obtenido el mismo beneficio sin crear ninguna instancia de objeto superflua.
Solo hay un caso en el que hacer todo el trabajo en un constructor de objetos es aceptable. Es entonces cuando el propósito del objeto es específicamente representar una identidad única a largo plazo, en lugar de realizar un trabajo. En tal caso, el objeto se mantendría por otras razones además de realizar un trabajo significativo. Por ejemplo, una instancia singleton.
fuente
Ciertamente, he creado muchas clases que hacen mucho trabajo para calcular algo en el constructor, pero el objeto es inmutable, por lo que hace que los resultados de ese cálculo estén disponibles a través de propiedades, y luego nunca hace nada más. Esa es la inmutabilidad normal en acción.
Creo que lo extraño aquí es poner código con efectos secundarios en un constructor. Construir un objeto y tirarlo sin acceder a las propiedades o métodos parece extraño (pero al menos en este caso es bastante obvio que algo más está sucediendo).
Esto sería mejor si la función se moviera a un método público, y luego se inyectara una instancia de la clase, luego se hiciera que el bucle for llame al método repetidamente.
fuente
No.
Uno podría cuestionar razonablemente estas pautas y decir: "¡Pero no sigo estas reglas y mi código funciona bien!" A eso, respondería: "Bueno, eso puede ser cierto, hasta que no lo sea".
fuente
Me parece que la persona que estaba haciendo esto intentaba hackear la limitación de Java que no permite pasar funciones como ciudadanos de primera clase. Siempre que tenga que pasar una función, debe envolverla dentro de una clase con un método similar
apply
o similar.Así que supongo que solo usó un atajo y utilizó directamente una clase como reemplazo de una función. Cada vez que desee ejecutar la función, solo crea una instancia de la clase.
Definitivamente no es un buen patrón, al menos porque estamos tratando de resolverlo, pero supongo que puede ser la forma menos detallada de hacer algo similar a la programación funcional en Java.
fuente
Para mí, la regla general es no hacer nada en el constructor, excepto inicializar las variables miembro. La primera razón de esto es que ayuda a seguir el principio SRP, ya que generalmente un proceso de inicialización prolongado es una indicación de que la clase hace más de lo que debería hacer, y la inicialización debe hacerse en algún lugar fuera de la clase. La segunda razón es que de esta manera solo se pasan los parámetros necesarios, por lo que crea menos código acoplado. Un constructor con una inicialización complicada generalmente necesita parámetros que se usan solo para construir otro objeto, pero que no son utilizados por la clase original.
fuente
Voy a ir contra la corriente aquí: en idiomas donde todo tiene que estar en alguna clase Y no puedes tener una clase estática, puedes toparte con la situación en la que tienes una funcionalidad aislada que debe ser poner en algún lugar y no encaja con nada más. Sus opciones son una clase de utilidad que cubre varios bits de funcionalidad no relacionados, o una clase que básicamente hace solo esto.
Si tiene solo un bit como este, entonces su clase estática es su clase específica. Entonces, o tienes un constructor que hace todo el trabajo, o una sola función que hace todo el trabajo. La ventaja de hacer todo en el constructor es que ocurre solo una vez, le permite usar campos privados, propiedades y métodos sin tener que preocuparse de que se vuelvan a usar o enhebrar. Si tiene un constructor / método, puede sentirse tentado a dejar que los parámetros pasen al método único, pero si usa campos o propiedades privadas que introducen posibles problemas de subprocesos.
Incluso con una clase de utilidad, la mayoría de las personas no pensarán en tener clases estáticas anidadas (y eso podría no ser posible dependiendo del idioma).
Básicamente, esta es una forma relativamente comprensible de aislar el comportamiento del resto del sistema, dentro de lo permitido por el lenguaje, al tiempo que le permite aprovechar la mayor cantidad de lenguaje posible.
Francamente, habría respondido mucho como lo hizo su contratista, es un pequeño ajuste convertirlo en dos llamadas, no hay mucho para recomendar una sobre la otra. ¿Qué desventajas hay ?, probablemente sean más hipotéticas que reales, ¿por qué tratar de justificar la decisión cuando no importa y probablemente no se decidió tanto como la acción predeterminada?
fuente
Hay patrones comunes en lenguajes donde las funciones y las clases son mucho más lo mismo, como Python, donde uno usa un objeto básicamente para funcionar con demasiados efectos secundarios o con múltiples objetos con nombre devueltos.
En este caso, la mayor parte, si no todo el trabajo se puede hacer en el constructor, ya que el objeto en sí mismo es solo un envoltorio alrededor de un solo paquete de trabajo, y no hay una buena razón para incluirlo en una función separada. Algo como
realmente no es mejor que
En lugar de tener directamente los efectos secundarios, puede llamar al objeto para que aplique el efecto secundario en el momento justo, lo que le brinda una mejor visibilidad. Si voy a tener un efecto secundario negativo en un objeto, puedo hacer todo mi trabajo y luego pasar el objeto que afectaré gravemente y le haré daño. Identificar el objeto y aislar la llamada de esta manera es más claro que pasar varios de estos objetos a una función a la vez.
Puede realizar los múltiples valores de retorno a través de los accesos en el objeto. Todo el trabajo está hecho, pero quiero que todo esté en contexto, y realmente no quiero pasar múltiples referencias a mi función.
Entonces, como programador conjunto de Python / C #, esto no me parece tan extraño.
fuente
Se me ocurre un caso en el que el constructor / destructor hace todo el trabajo:
Cabellos. Normalmente nunca interactúa con un candado durante su vida útil. El uso de la arquitectura de C # es bueno para manejar estos casos sin referirse explícitamente al destructor. Sin embargo, no todos los bloqueos se implementan de esta manera.
También he escrito lo que equivalía a un bit de código solo de constructor para parchear un error de biblioteca en tiempo de ejecución. (En realidad, arreglar el código habría causado otros problemas). No hubo destructor porque no había necesidad de deshacer el parche.
fuente
Estoy de acuerdo con AndyBursh. Parece una cuestión de falta de conocimiento.
Sin embargo, es importante mencionar marcos como NHibernate que requieren que solo codifique en el constructor (al crear clases de mapas). Sin embargo, esto no es una limitación del marco, es justo lo que necesita. Cuando se trata de reflexión, el constructor es el único método que siempre existirá (aunque podría ser privado) y esto podría ser útil si tiene que llamar a un método de una clase dinámicamente.
fuente
Sí, quizás recuerdes por qué necesitamos el StringBuilder.
Hay dos escuelas de Java.
El constructor fue promovido por el primero , que nos enseña a reutilizar (porque compartir = identidad a eficiencia). Sin embargo, a principios de 2000 comencé a leer que crear objetos en Java es tan barato (a diferencia de C ++) y GC es tan eficiente que la reutilización no tiene valor y lo único que importa es la productividad del programador. Para la productividad, debe escribir código manejable, lo que significa modularización (es decir, reducir el alcance). Entonces,
esto es malo
Esto es bueno.
Hice esta pregunta cuando existía forum.java.sun, y las personas acordaron que preferirían declarar la matriz lo más estrecha posible.
El programador moderno de Java nunca duda en declarar una nueva clase o crear un objeto. Se le anima a hacerlo. Java da todas las razones para hacerlo, con su idea de que "todo es un objeto" y una creación barata.
Además, el estilo moderno de programación empresarial es, cuando necesita ejecutar una acción, crea una clase especial (o incluso mejor, un marco) que ejecutará esa acción simple. Buenos IDEs de Java, que ayudan aquí. Generan toneladas de basura automáticamente, como la respiración.
Entonces, creo que sus objetos carecen del patrón Builder o Factory. No es decente crear instancias directamente, por constructor. Se recomienda una clase de fábrica especial para cada objeto.
Ahora, cuando entra en juego la programación funcional, la creación de objetos se vuelve aún más simple. Creará objetos a cada paso como respiración, sin siquiera darse cuenta de esto. Entonces, espero que su código sea aún más "eficiente" en la nueva era
Personalmente, no veo ninguna razón en la guía. Lo considero un método más. El código de factorización es necesario solo cuando puede compartirlo.
fuente