De vez en cuando me encuentro con métodos con un número incómodo de parámetros. La mayoría de las veces, parecen ser constructores. Parece que debería haber una forma mejor, pero no puedo ver cuál es.
return new Shniz(foo, bar, baz, quux, fred, wilma, barney, dino, donkey)
He pensado en usar estructuras para representar la lista de parámetros, pero eso parece cambiar el problema de un lugar a otro y crear otro tipo en el proceso.
ShnizArgs args = new ShnizArgs(foo, bar, baz, quux, fred, wilma, barney, dino, donkey)
return new Shniz(args);
Entonces eso no parece una mejora. Entonces, ¿cuál es el mejor enfoque?
refactoring
recursivo
fuente
fuente
Respuestas:
La mejor manera sería encontrar formas de agrupar los argumentos. Esto supone, y realmente solo funciona si, terminará con múltiples "agrupaciones" de argumentos.
Por ejemplo, si está pasando la especificación para un rectángulo, puede pasar x, y, ancho y alto o simplemente puede pasar un objeto rectangular que contenga x, y, ancho y alto.
Busque cosas como esta al refactorizar para limpiarlo un poco. Si los argumentos realmente no se pueden combinar, comience a ver si tiene una violación del principio de responsabilidad única.
fuente
Voy a asumir que te refieres a C # . Algunas de estas cosas también se aplican a otros idiomas.
Tienes varias opciones:
cambiar de constructor a establecedores de propiedades . Esto puede hacer que el código sea más legible, porque es obvio para el lector qué valor corresponde a qué parámetros. La sintaxis de Object Initializer hace que esto se vea bien. También es fácil de implementar, ya que puede usar propiedades generadas automáticamente y omitir la escritura de los constructores.
Sin embargo, pierde inmutabilidad y pierde la capacidad de asegurarse de que se establezcan los valores requeridos antes de usar el objeto en tiempo de compilación.
Patrón de constructor .
Piense en la relación entre
string
yStringBuilder
. Puede obtener esto para sus propias clases. Me gusta implementarlo como una clase anidada, por lo que la claseC
tiene una clase relacionadaC.Builder
. También me gusta una interfaz fluida en el constructor. Si se hace bien, puede obtener una sintaxis como esta:Tengo un script de PowerShell que me permite generar el código del constructor para hacer todo esto, donde la entrada se ve así:
Entonces puedo generar en tiempo de compilación.
partial
las clases me permiten extender tanto la clase principal como el constructor sin modificar el código generado.Refactorización "Introducir objeto de parámetro" . Consulte el catálogo de refactorización . La idea es que tome algunos de los parámetros que está pasando y los ponga en un nuevo tipo, y luego pase una instancia de ese tipo. Si hace esto sin pensar, terminará de nuevo donde comenzó:
se convierte en
Sin embargo, este enfoque tiene el mayor potencial para tener un impacto positivo en su código. Entonces, continúe siguiendo estos pasos:
Busque subconjuntos de parámetros que tengan sentido juntos. El simple hecho de agrupar sin pensar todos los parámetros de una función no significa mucho; el objetivo es tener agrupaciones que tengan sentido. Sabrá que lo hizo bien cuando el nombre del nuevo tipo sea obvio.
Busque otros lugares donde estos valores se usen juntos y use el nuevo tipo allí también. Lo más probable es que, cuando haya encontrado un buen tipo nuevo para un conjunto de valores que ya usa en todas partes, ese nuevo tipo también tendrá sentido en todos esos lugares.
Busque la funcionalidad que está en el código existente, pero pertenece al nuevo tipo.
Por ejemplo, tal vez vea un código que se parece a:
Usted podría tomar las
minSpeed
y losmaxSpeed
parámetros y los puso en un nuevo tipo:Esto es mejor, pero para aprovechar realmente el nuevo tipo, mueva las comparaciones al nuevo tipo:
Y ahora estamos llegando a algo: la implementación de
SpeedIsAcceptable()
now dice lo que quieres decir, y tienes una clase útil y reutilizable. (El siguiente paso obvio es hacer queSpeedRange
en aRange<Speed>
.)Como puede ver, Introducir objeto de parámetro fue un buen comienzo, pero su valor real fue que nos ayudó a descubrir un tipo útil que faltaba en nuestro modelo.
fuente
Si es un constructor, especialmente si hay múltiples variantes sobrecargadas, debería mirar el patrón Builder:
Si es un método normal, debe pensar en las relaciones entre los valores que se pasan y quizás crear un objeto de transferencia.
fuente
La respuesta clásica a esto es usar una clase para encapsular algunos o todos los parámetros. En teoría, eso suena genial, pero yo soy el tipo de persona que crea clases para conceptos que tienen significado en el dominio, por lo que no siempre es fácil aplicar este consejo.
Por ejemplo, en lugar de:
Podrías usar
YMMV
fuente
Esto se cita del libro de Fowler y Beck: "Refactoring"
fuente
No quiero sonar como un sabio, pero también debe verificar para asegurarse de que los datos que está pasando realmente se transmitan: Pasar cosas a un constructor (o método para el caso) huele un poco a poco énfasis en el comportamiento de un objeto.
No me malinterpretes: los métodos y los constructores a veces tendrán muchos parámetros. Pero cuando se encuentre, intente considerar encapsular datos con comportamiento lugar.
Este tipo de olor (ya que estamos hablando de refactorización, esta horrible palabra parece apropiada ...) también podría detectarse para objetos que tienen muchas (léase: cualquiera) propiedades o getters / setters.
fuente
Cuando veo listas de parámetros largas, mi primera pregunta es si esta función u objeto está haciendo demasiado. Considerar:
Por supuesto, este ejemplo es deliberadamente ridículo, pero he visto muchos programas reales con ejemplos solo un poco menos ridículos, donde una clase se usa para contener muchas cosas apenas relacionadas o no relacionadas, aparentemente solo porque el mismo programa de llamada necesita ambos o porque el El programador pensó en ambos al mismo tiempo. A veces, la solución fácil es simplemente dividir la clase en varias partes, cada una de las cuales hace lo suyo.
Un poco más complicado es cuando una clase realmente necesita lidiar con múltiples cosas lógicas, como tanto el pedido de un cliente como la información general sobre el cliente. En estos casos, cree una clase para el cliente y una clase para el pedido, y permítales hablar entre ellos según sea necesario. Entonces en lugar de:
Nosotros podríamos tener:
Aunque, por supuesto, prefiero las funciones que toman solo 1, 2 o 3 parámetros, a veces tenemos que aceptar que, de manera realista, esta función requiere mucho y que el número en sí mismo no crea complejidad. Por ejemplo:
Sí, es un montón de campos, pero probablemente todo lo que vamos a hacer con ellos es guardarlos en un registro de base de datos o lanzarlos en una pantalla o algo así. Realmente no hay mucho procesamiento aquí.
Cuando mis listas de parámetros se hacen largas, prefiero si puedo dar a los campos diferentes tipos de datos. Como cuando veo una función como:
Y luego lo veo llamado con:
Me preocupo. Mirando la llamada, no está del todo claro qué significan todos estos números, códigos y banderas crípticos. Esto es solo pedir errores. Un programador puede confundirse fácilmente sobre el orden de los parámetros y accidentalmente cambiar dos, y si son del mismo tipo de datos, el compilador simplemente los aceptará. Preferiría tener una firma donde todas estas cosas sean enumeraciones, por lo que una llamada pasa en cosas como Type.ACTIVE en lugar de "A" y CreditWatch.NO en lugar de "false", etc.
fuente
Si algunos de los parámetros del constructor son opcionales, tiene sentido usar un constructor, que obtendría los parámetros requeridos en el constructor, y tendría métodos para los opcionales, devolviendo el constructor, para usarse así:
Los detalles de esto se describen en Effective Java, 2nd Ed., P. 11. Para los argumentos del método, el mismo libro (p. 189) describe tres enfoques para acortar las listas de parámetros:
DinoDonkey
lugar dedino
ydonkey
fuente
Usaría el constructor predeterminado y los establecedores de propiedades. C # 3.0 tiene una buena sintaxis para hacer esto automágicamente.
La mejora del código viene al simplificar el constructor y no tener que admitir múltiples métodos para admitir varias combinaciones. La sintaxis de "llamar" sigue siendo un poco "prolija", pero en realidad no es peor que llamar a los establecedores de propiedades manualmente.
fuente
No ha proporcionado suficiente información para garantizar una buena respuesta. Una lista larga de parámetros no es intrínsecamente mala.
podría interpretarse como:
En este caso, es mucho mejor crear una clase para encapsular los parámetros porque le da significado a los diferentes parámetros de una manera que el compilador puede verificar y también hace que el código sea más fácil de leer visualmente. También hace que sea más fácil de leer y refactorizar más adelante.
Alternativamente, si tuvieras:
Este es un caso muy diferente porque todos los objetos son diferentes (y no es probable que se confundan). Estuvo de acuerdo en que si todos los objetos son necesarios y todos son diferentes, tiene poco sentido crear una clase de parámetro.
Además, ¿algunos parámetros son opcionales? ¿Hay anulaciones de métodos (el mismo nombre de método, pero diferentes firmas de método)? Todos estos tipos de detalles son importantes en cuanto a cuál es la mejor respuesta.
* Una bolsa de propiedades también puede ser útil, pero no específicamente mejor dado que no se proporcionan antecedentes.
Como puede ver, hay más de una respuesta correcta a esta pregunta. Elige tu opción.
fuente
Puede intentar agrupar su parámetro en múltiples estructuras / clases significativas (si es posible).
fuente
En general, me inclinaría hacia el enfoque de estructuras; presumiblemente, la mayoría de estos parámetros están relacionados de alguna manera y representan el estado de algún elemento que es relevante para su método.
Si el conjunto de parámetros no se puede convertir en un objeto significativo, probablemente sea una señal de que se
Shniz
está haciendo demasiado, y la refactorización debería implicar dividir el método en preocupaciones separadas.fuente
Puede cambiar la complejidad por líneas de código fuente. Si el método en sí hace demasiado (cuchillo suizo), intente reducir a la mitad sus tareas creando otro método. Si el método es simple solo que necesita demasiados parámetros, entonces los llamados objetos de parámetro son el camino a seguir.
fuente
Si su idioma lo admite, utilice parámetros con nombre y haga tantos opcionales (con valores predeterminados razonables) como sea posible.
fuente
Creo que el método que describiste es el camino a seguir. Cuando encuentro un método con muchos parámetros y / o uno que probablemente necesite más en el futuro, generalmente creo un objeto ShnizParams para pasar, como usted describe.
fuente
¿Qué tal no configurarlo todo a la vez en los constructores sino hacerlo a través de propiedades / establecedores ? He visto algunas clases de .NET que utilizan este enfoque, como
Process
class:fuente
Estoy de acuerdo con el enfoque de mover los parámetros a un objeto de parámetro (estructura). Sin embargo, en lugar de simplemente pegarlos todos en un objeto, revise si otras funciones usan grupos similares de parámetros. Un objeto de parámetro es más valioso si se usa con múltiples funciones en las que espera que ese conjunto de parámetros cambie consistentemente en esas funciones. Es posible que solo coloque algunos de los parámetros en el nuevo objeto de parámetro.
fuente
Si tiene tantos parámetros, es probable que el método esté haciendo demasiado, así que aborde esto primero dividiendo el método en varios métodos más pequeños. Si aún tiene demasiados parámetros después de esto, intente agrupar los argumentos o convertir algunos de los parámetros en miembros de instancia.
Prefiera clases / métodos pequeños sobre grandes. Recuerde el principio de responsabilidad única.
fuente
Los argumentos con nombre son una buena opción (suponiendo un lenguaje que los admita) para eliminar la ambigüedad de listas de parámetros largas (¡o incluso cortas!) Y al mismo tiempo permitir (en el caso de los constructores) que las propiedades de la clase sean inmutables sin imponer un requisito para permitir que exista. en un estado parcialmente construido.
La otra opción que buscaría al hacer este tipo de refactorización serían grupos de parámetros relacionados que podrían manejarse mejor como un objeto independiente. Usando la clase Rectangle de una respuesta anterior como ejemplo, el constructor que toma parámetros para x, y, alto y ancho podría factorizar xey en un objeto Point, lo que le permite pasar tres parámetros al constructor de Rectangle. O vaya un poco más allá y conviértalo en dos parámetros (UpperLeftPoint, LowerRightPoint), pero eso sería una refactorización más radical.
fuente
Depende del tipo de argumentos que tenga, pero si son muchos valores / opciones booleanos, ¿tal vez podría usar un indicador de enumeración?
fuente
Creo que ese problema está profundamente ligado al dominio del problema que estás tratando de resolver con la clase.
En algunos casos, un constructor de 7 parámetros puede indicar una jerarquía de clases incorrecta: en ese caso, la estructura / clase auxiliar sugerida anteriormente suele ser un buen enfoque, pero también tiende a terminar con un montón de estructuras que son solo bolsas de propiedades y no hagas nada útil. El constructor de 8 argumentos también puede indicar que su clase es demasiado genérica / demasiado polivalente, por lo que necesita muchas opciones para ser realmente útil. En ese caso, puede refactorizar la clase o implementar constructores estáticos que oculten los constructores complejos reales: por ejemplo. Shniz.NewBaz (foo, bar) podría realmente llamar al constructor real pasando los parámetros correctos.
fuente
Una consideración es ¿cuál de los valores sería de solo lectura una vez que se crea el objeto?
Las propiedades de escritura pública tal vez podrían asignarse después de la construcción.
¿De dónde vienen en última instancia los valores? Quizás algunos valores son verdaderamente externos, mientras que otros son realmente de alguna configuración o datos globales que mantiene la biblioteca.
En este caso, podría ocultar el constructor del uso externo y proporcionarle una función Create. La función de creación toma los valores verdaderamente externos y construye el objeto, luego usa los accesos que solo están disponibles en la biblioteca para completar la creación del objeto.
Sería realmente extraño tener un objeto que requiera 7 o más parámetros para darle al objeto un estado completo y que todos sean realmente externos por naturaleza.
fuente
Cuando una clase tiene un constructor que acepta demasiados argumentos, suele ser una señal de que tiene demasiadas responsabilidades. Probablemente se pueda dividir en clases separadas que cooperen para brindar las mismas funcionalidades.
En caso de que realmente necesite tantos argumentos para un constructor, el patrón Builder puede ayudarlo. El objetivo es seguir pasando todos los argumentos al constructor, por lo que su estado se inicializa desde el principio y aún puede hacer que la clase sea inmutable si es necesario.
Vea abajo :
fuente