¿Son los métodos init () un olor a código?

20

¿Hay algún propósito para declarar un init()método para un tipo?

No estoy preguntando si deberíamos preferir init()a un constructor o cómo evitar declararinit() .

Estoy preguntando si hay alguna razón para declarar un init()método (ver qué tan común es) o si es un olor a código y debe evitarse.


El init()idioma es bastante común, pero aún no he visto ningún beneficio real.

Estoy hablando de tipos que fomentan la inicialización a través de un método:

class Demo {
    public void init() {
        //...
    }
}

¿Cuándo será esto útil en el código de producción?


Siento que puede ser un olor a código, ya que sugiere que el constructor no inicializa completamente el objeto, lo que resulta en un objeto creado parcialmente. El objeto no debería existir si su estado no está establecido.

Esto me hace creer que puede ser parte de algún tipo de técnica utilizada para acelerar la producción, en el sentido de las aplicaciones empresariales. Es la única razón lógica por la que puedo pensar en tener un idioma así, simplemente no estoy seguro de cómo sería beneficioso si es así.

Vince Emigh
fuente
1
A "... viendo lo común que es ...": ¿es común? ¿Puedes dar algunos ejemplos? Tal vez se trata de un marco que requiere separar la inicialización y la construcción.
VENIDO del
¿El método se encuentra en una clase base o una clase derivada, o en ambas? (O: ¿el método se encuentra en una clase que pertenece a una jerarquía de herencia? ¿La clase base llama init()a la clase derivada, o viceversa?) Si es así, es un ejemplo de permitir que la clase base ejecute un "post-constructor "que solo se puede ejecutar después de que la clase más derivada haya terminado la construcción. Es un ejemplo de inicialización de múltiples fases.
rwong
Si no desea inicializar en el punto de instanciación, tendría sentido separar los dos.
JᴀʏMᴇᴇ
tú podrías estar interesado. is-a-start-run-or-execute-method-a-good-practice
Laiv

Respuestas:

39

Sí, es un olor a código. Un olor a código no es algo que necesariamente siempre deba eliminarse. Es algo que te hace echar un segundo vistazo.

Aquí tiene un objeto en dos estados fundamentalmente diferentes: pre-init y post-init. Esos estados tienen diferentes responsabilidades, diferentes métodos que se les permite llamar y diferentes comportamientos. Es efectivamente dos clases diferentes.

Si físicamente las convierte en dos clases separadas, eliminará estáticamente toda una clase de errores potenciales, a costa de que su modelo no coincida tan estrechamente con el "modelo del mundo real". Por lo general, el nombre del primero Configo Setupo algo por el estilo.

Así que la próxima vez, intente refactorizar sus modismos de inicio de construcción en modelos de dos clases y vea cómo resulta para usted.

Karl Bielefeldt
fuente
66
Su sugerencia para probar el modelo de dos clases es buena. Proponer un paso concreto para abordar un olor a código es útil.
Ivan
1
Esta es la mejor respuesta hasta ahora. Sin embargo, una cosa me molesta: " Esos estados tienen diferentes responsabilidades, diferentes métodos que pueden llamarse y diferentes comportamientos ": no separar estas responsabilidades viola el SRP. Si el propósito del software fuera replicar un escenario del mundo real en todos los aspectos, esto tendría sentido. Pero en la producción, los desarrolladores escriben principalmente código que fomenta la facilidad de administración, revisando el modelo si es necesario para adaptarse mejor a un entorno basado en software (continúa en el siguiente comentario)
Vince Emigh
1
El mundo real es un entorno diferente. Los programas que intentan replicarlo parecen específicos del dominio, ya que en la mayoría de los proyectos no debería / no debería tenerlo en cuenta. Se aceptan muchas cosas que están mal vistas cuando se trata de proyectos específicos de dominio, así que estoy tratando de mantener esto lo más general posible (por ejemplo, cómo llamar thisal constructor es un olor a código y podría dar lugar a un código propenso a errores, y se recomienda evitarlo independientemente del dominio en el que se encuentre su proyecto).
Vince Emigh
14

Depende.

Un initmétodo es un olor a código cuando no es necesario separar la inicialización del objeto del constructor. A veces hay casos en los que tiene sentido separar estos pasos.

Una búsqueda rápida en Google me dio este ejemplo. Puedo imaginar fácilmente más casos en los que el código ejecutado durante la asignación de objetos (el constructor) podría separarse mejor de la inicialización misma. Quizás tenga un sistema nivelado y la asignación / construcción se realice en el nivel X, pero la inicialización solo en el nivel Y, porque solo Y puede proporcionar los parámetros necesarios. Tal vez el "init" es costoso y debe ejecutarse solo para un subconjunto de los objetos asignados, y la determinación de ese subconjunto solo puede hacerse en el nivel Y. O desea anular el método (virtual) "init" en un derivado clase que no se puede hacer con un constructor. Tal vez el nivel X le proporciona objetos asignados de un árbol de herencia, pero el nivel Y no tiene conocimiento de la derivación concreta, solo de la interfaz común (dondeinit tal vez definido).

Por supuesto, según mi experiencia, estos casos son solo un pequeño porcentaje del caso estándar en el que toda la inicialización se puede hacer directamente en el constructor, y cada vez que vea un initmétodo separado , podría ser una buena idea cuestionar su necesidad.

Doc Brown
fuente
2
La respuesta en ese enlace dice que es útil para reducir la cantidad de trabajo en el constructor, pero alienta los objetos creados parcialmente. Los constructores más pequeños se pueden lograr mediante la descomposición, por lo que para mí parece que las respuestas crean un nuevo problema (el potencial para olvidar llamar a todos los métodos de inicialización requeridos, dejando el objeto propenso a errores) y cae dentro de la categoría de olor de código
Vince Emigh
@VinceEmigh: ok, fue el primer ejemplo que podría encontrar aquí en la PLATAFORMA SE, quizás no sea el mejor, pero no son casos de uso legítimo para una separada initmétodo. Sin embargo, cada vez que vea dicho método, no dude en cuestionar su necesidad.
Doc Brown
Estoy cuestionando / desafiando cada caso de uso, ya que siento que no hay una situación en la que sea necesario. Para mí, es un mal momento de creación de objetos, y debe evitarse ya que es un candidato para errores que pueden evitarse mediante un diseño adecuado. Si hubiera un uso adecuado de un init()método, estoy seguro de que me beneficiaría aprender sobre su propósito. Disculpe mi ignorancia, estoy asombrado de lo difícil que es encontrarle un uso, evitando que lo considere algo que debería evitarse
Vince Emigh
1
@VinceEmigh: cuando no puedes pensar en una situación así, necesitas trabajar en tu imaginación ;-). O lea mi respuesta nuevamente, no la reduzca solo a "asignación". O trabaje con más marcos de diferentes proveedores.
Doc Brown
1
@DocBrown El uso de la imaginación en lugar de las prácticas comprobadas conduce a un código funky, como la inicialización de doble llave: inteligente, pero ineficiente y debe evitarse. Sé que los vendedores lo usan, pero eso no significa que el uso esté justificado. Si cree que es así, hágame saber por qué, ya que eso es lo que he estado tratando de resolver. Parecía que tenía un cierto propósito beneficioso, pero parece que le está costando dar un ejemplo que fomente el buen diseño. Sé bajo qué circunstancias podría usarse, pero ¿ debería usarse?
Vince Emigh
5

Mi experiencia se divide en dos grupos:

  1. Código donde init () es realmente requerido. Esto puede ocurrir cuando una superclase o marco impide que el constructor de su clase obtenga todas sus dependencias durante la construcción.
  2. Código donde se usa init () pero podría haberse evitado.

En mi experiencia personal, he visto solo algunas instancias de (1) pero muchas más instancias de (2). En consecuencia, generalmente asumo que init () es un olor a código, pero este no es siempre el caso. A veces no puedes evitarlo.

He descubierto que usar el Patrón de construcción a menudo puede ayudar a eliminar la necesidad / deseo de tener un init ().

Ivan
fuente
1
Si una superclase o marco no permite que un tipo obtenga las dependencias que necesita a través del constructor, ¿cómo lo init()resolvería agregar un método? El init()método necesitaría parámetros para aceptar dependencias, o tendría que instanciar las dependencias dentro del init()método, lo que también podría hacer con un constructor. ¿Podrías dar un ejemplo?
Vince Emigh
1
@VinceEmigh: init () a veces se puede usar para cargar un archivo de configuración desde una fuente externa, abrir una conexión de base de datos o algo por el estilo. El método DoFn.initialize () (del marco apache Crunch) se usa de esta manera. También se puede usar para cargar campos internos no serializables (DoFns debe ser serializable). Los dos problemas aquí son que (1) algo debe garantizar que se llame al método de inicialización y (2) el objeto necesita saber dónde va a obtener (o cómo va a construir) esos recursos.
Ivan
1

Un escenario típico cuando un método Init es útil es cuando tiene un archivo de configuración que desea cambiar y tener el cambio en cuenta sin reiniciar la aplicación. Esto, por supuesto, no significa que un método Init deba llamarse por separado de un constructor. Puede llamar a un método Init desde un constructor, y luego llamarlo más tarde cuando / si cambian los parámetros de configuración.

Para resumir: como para la mayoría de los dilemas, ya sea que se trate de un olor a código o no, depende de la situación y las circunstancias.

Vladimir Stokic
fuente
Si las actualizaciones de configuración, y esto requiere el objeto de restablecer / cambiarlo de estado basado en la configuración, ¿no cree que sería mejor tener el acto objeto como un observador hacia Config?
Vince Emigh
@Vince Emigh No necesariamente. Observer funcionaría si supiera el momento exacto en que cambia la configuración. Sin embargo, si los datos de configuración se guardan en un archivo que se puede cambiar fuera de una aplicación, entonces no existe un enfoque realmente elegante. Por ejemplo, si tengo un programa que analiza algunos archivos y transforma los datos en algún modelo interno, y un archivo de configuración separado contiene valores predeterminados para los datos faltantes, si cambio los valores predeterminados, los volveré a leer la próxima vez que lo ejecute el análisis Tener un método Init en mi aplicación sería bastante útil en ese caso.
Vladimir Stokic
Si el archivo de configuración se modifica externamente en tiempo de ejecución, no hay forma de volver a cargar esas variables sin algún tipo de notificación que informe a su aplicación que necesita llamar a init ( update/ reloadprobablemente sería más descriptivo para este tipo de comportamiento) para registrar realmente esos cambios . En ese caso, esa notificación haría que los valores de la configuración cambien internamente en su aplicación, lo que creo que podría observarse haciendo que su configuración sea observable, notificando a los observadores cuando se le dice a la configuración que cambie uno de sus valores. ¿O estoy malinterpretando tu ejemplo?
Vince Emigh
Una aplicación toma algunos archivos externos (exportados desde algún SIG u otro sistema), analiza esos archivos y los transforma en algún modelo interno que los sistemas de los que la aplicación forma parte. Durante ese proceso, se pueden encontrar algunas lagunas de datos y esas lagunas de datos se pueden llenar mediante el valor predeterminado. Esos valores predeterminados se pueden almacenar en un archivo de configuración que se puede leer al comienzo del proceso de transformación, que se invoca cada vez que hay nuevos archivos para transformar. Ahí es donde un método Init podría ser útil.
Vladimir Stokic
1

Depende de cómo los uses.

Uso ese patrón en lenguajes recolectados de basura como Java / C # cuando no quiero seguir reasignando un objeto en el montón (como cuando estoy haciendo un videojuego y necesito mantener el rendimiento alto, los recolectores de basura matan el rendimiento). Utilizo el constructor para realizar otras asignaciones de almacenamiento dinámico que necesita y initpara crear el estado útil básico justo antes de cada vez que quiera reutilizarlo. Esto está relacionado con el concepto de agrupaciones de objetos.

También es útil si tiene varios constructores que comparten un subconjunto común de instrucciones de inicialización, pero en ese caso initserán privados. De esa manera, puedo minimizar cada constructor tanto como sea posible, por lo que cada uno solo contiene sus instrucciones únicas y una sola llamada initpara hacer el resto.

En general, sin embargo, es un olor a código.

Cody
fuente
¿No sería un reset()método más descriptivo para su primera declaración? En cuanto al segundo (muchos constructores), tener muchos constructores es un olor a código. Asume que el objeto tiene múltiples propósitos / responsabilidades, lo que sugiere una violación de SRP. Un objeto debe tener una responsabilidad, y el constructor debe definir las dependencias requeridas para esa responsabilidad. Si tiene múltiples constructores debido a los valores opcionales, deben telescopios (que también es un olor a código, debería usar un constructor).
Vince Emigh
@VinceEmigh puedes usar init o reset, es solo un nombre después de todo. Init tiene más sentido en el contexto en el que tiendo a usarlo, ya que tiene poco sentido restablecer un objeto que nunca se configuró la primera vez que lo uso. En cuanto al problema del constructor, trato de evitar tener muchos constructores, pero ocasionalmente es útil. Mira la stringlista de constructores de cualquier idioma , toneladas de opciones. Para mí, por lo general, son quizás 3 constructores como máximo, pero el subconjunto común de instrucciones como inicialización tiene sentido cuando comparten cualquier código pero difieren de alguna manera.
Cody
Los nombres son extremadamente importantes. Describen el comportamiento que se realiza sin obligar al cliente a leer el contrato. Un mal nombre puede conducir a suposiciones falsas. En cuanto a los múltiples constructores en String, esto podría resolverse desacoplando la creación de cadenas. Al final, a Stringes a String, y su constructor solo debe aceptar lo que se necesita para que funcione según sea necesario. La mayoría de esos constructores están expuestos para fines de conversión, lo cual es un mal uso de los constructores. Los constructores no deben realizar la lógica, o corren el riesgo de una inicialización fallida, dejándolo con un objeto inútil.
Vince Emigh
El JDK está lleno de diseños horribles, podría enumerar unos 10 en la parte superior de mi cabeza. El diseño del software ha evolucionado desde que los aspectos centrales de muchos lenguajes fueron expuestos públicamente, y persisten debido a la posibilidad de descifrar el código si se rediseñara para los tiempos modernos.
Vince Emigh
1

init()Los métodos pueden tener bastante sentido cuando tiene objetos que necesitan recursos externos (como, por ejemplo, una conexión de red) que otros objetos utilizan simultáneamente. Es posible que no desee / necesite acaparar el recurso durante la vida útil del objeto. En tales situaciones, es posible que no desee asignar el recurso en el constructor cuando es probable que la asignación del recurso falle.

Especialmente en la programación incrustada, desea tener una huella de memoria determinista, por lo que es una práctica común (¿buena?) Llamar a sus constructores temprano, tal vez incluso estático, y solo inicializar más tarde cuando se cumplen ciertas condiciones.

Aparte de tales casos, creo que todo debería ir a un constructor.

tofro
fuente
Si el tipo requiere una conexión, debe usar DI. Si hay un problema al crear la conexión, no debe crear el objeto que lo requiere. Si empuja la creación de una conexión dentro de la clase, creará un objeto, ese objeto instanciará sus dependencias (la conexión). Si falla la creación de instancias de dependencias, terminas con un objeto que no puedes usar, desperdiciando recursos.
Vince Emigh
No necesariamente. Terminas con un objeto que temporalmente no puedes usar para todos los propósitos. En tales casos, el objeto podría actuar como una cola o proxy hasta que el recurso esté disponible. Condenar por completo los initmétodos es demasiado restrictivo, en mi humilde opinión.
tofro
0

Generalmente prefiero un constructor que reciba todos los argumentos necesarios para una instancia funcional. Eso deja en claro todas las dependencias de ese objeto.

Por otro lado, uso un marco de configuración simple que requiere un constructor público sin parámetros e interfaces para inyectar dependencias y valores de configuración. Una vez hecho esto, el marco de configuración llama al initmétodo del objeto: ahora que recibió todas las cosas que tengo para usted, realice los últimos pasos para prepararse para el trabajo. Pero tenga en cuenta: es el marco de configuración el que llama automáticamente al método init, por lo que no olvidará llamarlo.

Bernhard Hiller
fuente
0

No hay olor a código si el método init () está semánticamente incrustado en el ciclo de vida del estado del objeto.

Si necesita llamar a init () para poner el objeto en un estado consistente, es un olor a código.

Hay varias razones técnicas por las que existe una estructura de este tipo:

  1. gancho marco
  2. restablecer el objeto al estado inicial (evitar redundancia)
  3. posibilidad de anular durante las pruebas
oopexpert
fuente
1
Pero, ¿por qué un ingeniero de software lo integraría en el ciclo de vida? ¿Hay algún propósito que sea justificable (considerando que fomenta objetos parcialmente construidos) y no podría ser derribado por una alternativa más eficiente? Siento que incrustarlo en el ciclo de vida sería un olor a código, y que debería reemplazarse con un mejor momento de la creación de objetos (¿Por qué asignar memoria para un objeto si no planea usarlo en ese momento? ¿Por qué crear un objeto que está parcialmente construido cuando puedes esperar hasta que realmente necesites el objeto antes de crearlo?)
Vince Emigh
El punto es que el objeto debe ser utilizable ANTES de llamar al método init. Tal vez de otra manera que después de que se llamó Ver patrón de estado. En el sentido de la construcción de objetos parciales, es un olor a código.
oopexpert
-4

El nombre init a veces puede ser opaco. Tomemos un auto y un motor. Para arrancar el automóvil (solo encender, escuchar la radio) desea verificar que todos los sistemas estén listos para funcionar.

Así que construyes un motor, una puerta, una rueda, etc., tu pantalla muestra engine = off.

No es necesario comenzar a monitorear el motor, etc., ya que todos son caros. Luego, cuando gira la llave para encender, llama motor-> inicio. Comienza a ejecutar todos los procesos caros.

Ahora ves engine = on. Y comienza el proceso de ignición.

El automóvil no se encenderá sin el motor disponible.

Puede reemplazar el motor con su cálculo complejo. Como una celda de Excel. No todas las celdas deben estar activas con todos los controladores de eventos todo el tiempo. Cuando te enfocas en una celda, puedes iniciarla. Aumentando el rendimiento de esa manera.

Luc Franken
fuente