¿La mejor práctica para pasar muchos argumentos al método?

95

Ocasionalmente, tenemos que escribir métodos que reciben muchos argumentos, por ejemplo:

public void doSomething(Object objA , Object objectB ,Date date1 ,Date date2 ,String str1 ,String str2 )
{
}

Cuando me encuentro con este tipo de problema, a menudo encapsulo argumentos en un mapa.

Map<Object,Object> params = new HashMap<Object,Object>();
params.put("objA",ObjA) ;

......

public void doSomething(Map<Object,Object> params)
{
 // extracting params 
 Object objA = (Object)params.get("objA");
 ......
 }

Esta no es una buena práctica, encapsular parámetros en un mapa es una pérdida total de eficiencia. Lo bueno es que la firma limpia es fácil de agregar otros parámetros con la menor cantidad de modificaciones. ¿Cuál es la mejor práctica para este tipo de problema?

Aserrador
fuente

Respuestas:

140

En Effective Java , Capítulo 7 (Métodos), Ítem 40 (Diseñe las firmas del método con cuidado), Bloch escribe:

Hay tres técnicas para acortar listas de parámetros demasiado largas:

  • dividir el método en varios métodos, cada uno de los cuales requiere solo un subconjunto de los parámetros
  • crear clases auxiliares para contener un grupo de parámetros (normalmente clases de miembros estáticos)
  • Adapte el patrón Builder desde la construcción del objeto hasta la invocación del método.

Para más detalles, te animo a que compres el libro, realmente vale la pena.

JRL
fuente
¿Qué son los "parámetros demasiado largos"? ¿Cuándo podemos decir que un método tiene demasiados parámetros? ¿Existe un número específico o un rango?
Red M
2
@RedM Siempre he considerado que algo más de 3 o 4 parámetros es "demasiado largo"
jtate
2
@jtate, ¿es una elección personal o estás siguiendo un documento oficial?
Red M
2
@RedM preferencia personal :)
jtate
2
Con la tercera edición de Effective Java, este es el capítulo 8 (métodos), elemento 51
GarethOwen
71

Usar un mapa con claves de cadena mágicas es una mala idea. Pierde la comprobación del tiempo de compilación y no está claro cuáles son los parámetros necesarios. Necesitaría escribir documentación muy completa para compensarlo. ¿Recordará en unas semanas cuáles son esas cadenas sin mirar el código? ¿Y si cometiste un error tipográfico? ¿Utiliza el tipo incorrecto? No lo sabrá hasta que ejecute el código.

En su lugar, use un modelo. Cree una clase que será un contenedor para todos esos parámetros. De esa forma mantendrá la seguridad de tipos de Java. También puede pasar ese objeto a otros métodos, ponerlo en colecciones, etc.

Por supuesto, si el conjunto de parámetros no se usa en otro lugar o no se transmite, un modelo dedicado puede ser excesivo. Hay que lograr un equilibrio, así que use el sentido común.

tom
fuente
24

Si tiene muchos parámetros opcionales, puede crear una API fluida: reemplace el método único con la cadena de métodos

exportWithParams().datesBetween(date1,date2)
                  .format("xml")
                  .columns("id","name","phone")
                  .table("angry_robots")
                  .invoke();

Con la importación estática puede crear API internas fluidas:

... .datesBetween(from(date1).to(date2)) ...
Decir ah.
fuente
3
¿Qué pasa si todos los parámetros son obligatorios, no opcionales?
emeraldhieu
1
También puede tener parámetros predeterminados de esta manera. Además, el patrón del constructor está relacionado con interfaces fluidas. Esta debería ser realmente la respuesta, creo. Además de dividir un constructor largo en métodos de inicialización más pequeños que son opcionales.
Ehtesh Choudhury
13

Se llama "Introducir objeto de parámetro". Si se encuentra pasando la misma lista de parámetros en varios lugares, simplemente cree una clase que los contenga todos.

XXXParameter param = new XXXParameter(objA, objB, date1, date2, str1, str2);
// ...
doSomething(param);

Incluso si no se encuentra pasando la misma lista de parámetros con tanta frecuencia, esa fácil refactorización seguirá mejorando la legibilidad de su código, lo cual siempre es bueno. Si miras tu código 3 meses después, será más fácil comprender cuándo necesitas corregir un error o agregar una función.

Es una filosofía general, por supuesto, y como no ha proporcionado ningún detalle, tampoco puedo darle un consejo más detallado. :-)

dimitarvp
fuente
¿Será un problema la recolección de basura?
Rupinderjeet
1
No si hace que el objeto de parámetro tenga un alcance local en la función de llamada y no lo mute. Lo más probable es que se recopile y su memoria se reutilice con bastante rapidez en tales circunstancias.
dimitarvp
Imo, también debe tener XXXParameter param = new XXXParameter();disponible y luego usar XXXParameter.setObjA(objA); etc ...
satibel
10

Primero, intentaría refactorizar el método. Si usa tantos parámetros, puede que sea demasiado largo. Desglosarlo mejoraría el código y reduciría potencialmente la cantidad de parámetros para cada método. También es posible que pueda refactorizar toda la operación en su propia clase. En segundo lugar, buscaría otras instancias en las que esté usando el mismo (o superconjunto) de la misma lista de parámetros. Si tiene varias instancias, es probable que indique que estas propiedades van juntas. En ese caso, cree una clase para contener los parámetros y utilícela. Por último, evaluaría si la cantidad de parámetros hace que valga la pena crear un objeto de mapa para mejorar la legibilidad del código. Creo que esta es una llamada personal: hay problemas en cada sentido con esta solución y dónde está el punto de compensación puede diferir. Para seis parámetros probablemente no lo haría. Para 10 probablemente lo haría (si ninguno de los otros métodos funcionara primero).

tvanfosson
fuente
8

Esto suele ser un problema al construir objetos.

En ese caso, use el patrón de objetos del constructor , funciona bien si tiene una gran lista de parámetros y no siempre los necesita todos.

También puede adaptarlo a la invocación de métodos.

También aumenta mucho la legibilidad.

public class BigObject
{
  // public getters
  // private setters

  public static class Buider
  {
     private A f1;
     private B f2;
     private C f3;
     private D f4;
     private E f5;

     public Buider setField1(A f1) { this.f1 = f1; return this; }
     public Buider setField2(B f2) { this.f2 = f2; return this; }
     public Buider setField3(C f3) { this.f3 = f3; return this; }
     public Buider setField4(D f4) { this.f4 = f4; return this; }
     public Buider setField5(E f5) { this.f5 = f5; return this; }

    public BigObject build()
    {
      BigObject result = new BigObject();
      result.setField1(f1);
      result.setField2(f2);
      result.setField3(f3);
      result.setField4(f4);
      result.setField5(f5);
      return result;
    }
  }
}

// Usage:
BigObject boo = new BigObject.Builder()
  .setField1(/* whatever */)
  .setField2(/* whatever */)
  .setField3(/* whatever */)
  .setField4(/* whatever */)
  .setField5(/* whatever */)
  .build();

También puede poner la lógica de verificación en los métodos Builder set .. () y build ().

Bohdan
fuente
¿Qué recomendaría si muchos de sus campos lo son final? Esto es lo principal que me impide escribir funciones de ayuda. Supongo que puedo hacer que los campos sean privados y asegurarme de no modificarlos incorrectamente en el código de esa clase, pero espero algo más elegante.
ragerdl
7

Hay un patrón llamado objeto de parámetro .

La idea es usar un objeto en lugar de todos los parámetros. Ahora, incluso si necesita agregar parámetros más tarde, solo necesita agregarlos al objeto. La interfaz del método sigue siendo la misma.

Padmarag
fuente
5

Podría crear una clase para almacenar esos datos. Sin embargo, debe ser lo suficientemente significativo, pero mucho mejor que usar un mapa (Dios mío).

Johannes Rudolph
fuente
No creo que sea tan necesario crear una clase para contener un parámetro de método.
Sawyer
Solo crearía la clase si hubiera varias instancias de pasar los mismos parámetros. Esto indicaría que los parámetros están relacionados y probablemente pertenezcan juntos de todos modos. Si está creando una clase para un solo método, la cura probablemente sea peor que la enfermedad.
tvanfosson
Sí, puede mover parámetros relacionados a un objeto de valor o DTO. ¿Son algunos de los múltiples parámetros opcionales, es decir, el método principal está sobrecargado con estos parámetros adicionales? En tales casos, creo que es aceptable.
JoseK
Eso es lo que quise decir al decir que debe ser lo suficientemente significativo.
Johannes Rudolph
4

Code Complete * sugiere un par de cosas:

  • "Limite el número de parámetros de una rutina a aproximadamente siete. Siete es un número mágico para la comprensión de las personas" (p. 108).
  • "Coloque los parámetros en el orden de entrada-modificación-salida ... Si varias rutinas usan parámetros similares, coloque los parámetros similares en un orden consistente" (p. 105).
  • Ponga las variables de estado o error al final.
  • Como mencionó tvanfosson , pase solo las partes de variables estructuradas (objetos) que necesita la rutina. Dicho esto, si está utilizando la mayor parte de la variable estructurada en la función, simplemente pase la estructura completa, pero tenga en cuenta que esto promueve el acoplamiento hasta cierto punto.

* Primera edición, sé que debería actualizar. Además, es probable que algunos de estos consejos hayan cambiado desde que se escribió la segunda edición cuando OOP comenzaba a ser más popular.

Justin Johnson
fuente
2

Una buena práctica sería refactorizar. ¿Qué pasa con estos objetos que significa que deben pasarse a este método? ¿Deberían encapsularse en un solo objeto?

GaryF
fuente
Sí, deberían . Por ejemplo, un formulario de búsqueda grande tiene muchas limitaciones y necesidades de paginación no relacionadas. necesita pasar currentPageNumber, searchCriteria, pageSize ...
Sawyer
2

Usar un mapa es una forma sencilla de limpiar la firma de la llamada, pero luego tiene otro problema. Debe mirar dentro del cuerpo del método para ver qué espera el método en ese mapa, cuáles son los nombres de las claves o qué tipos tienen los valores.

Una forma más limpia sería agrupar todos los parámetros en un objeto bean, pero eso aún no soluciona el problema por completo.

Lo que tienes aquí es un problema de diseño. Con más de 7 parámetros para un método comenzarás a tener problemas para recordar qué representan y qué orden tienen. A partir de aquí, obtendrá muchos errores simplemente llamando al método en el orden de parámetros incorrecto.

Necesita un mejor diseño de la aplicación, no una buena práctica para enviar muchos parámetros.


fuente
1

Cree una clase de frijol, establezca todos los parámetros (método setter) y pase este objeto de frijol al método.

Tony
fuente
1
  • Mire su código y vea por qué se pasan todos esos parámetros. A veces es posible refactorizar el método en sí.

  • Usar un mapa deja su método vulnerable. ¿Qué pasa si alguien que usa su método escribe mal el nombre de un parámetro o publica una cadena donde su método espera un UDT?

  • Definir un objeto de transferencia . Le proporcionará verificación de tipo como mínimo; incluso puede ser posible que realice alguna validación en el punto de uso en lugar de dentro de su método.

Todos
fuente
0

Si está pasando demasiados parámetros, intente refactorizar el método. Tal vez está haciendo muchas cosas que se supone que no debe hacer. Si ese no es el caso, intente sustituir los parámetros con una sola clase. De esta manera, puede encapsular todo en una instancia de una sola clase y pasar la instancia y no los parámetros.

azamsharp
fuente
0

Yo diría que siga con la forma en que lo hizo antes. La cantidad de parámetros en su ejemplo no es mucha, pero las alternativas son mucho más horribles.

  1. Mapa: está el aspecto de la eficiencia que mencionaste, pero el problema más grande aquí es:

    • Las personas que llaman no saben qué enviarle sin hacer referencia a otra
      cosa ... ¿Tiene javadocs que indique exactamente qué claves y
      valores se utilizan? Si lo hace (lo cual es genial), entonces tener muchos parámetros tampoco es un problema.
    • Se vuelve muy difícil aceptar diferentes tipos de argumentos. Puede restringir los parámetros de entrada a un solo tipo o usar Map <String, Object> y convertir todos los valores. Ambas opciones son horribles la mayor parte del tiempo.
  2. Objetos de envoltura: esto solo mueve el problema ya que necesita llenar el objeto de envoltura en primer lugar, en lugar de directamente a su método, será al constructor del objeto de parámetro. Determinar si mover el problema es apropiado o no depende de la reutilización de dicho objeto. Por ejemplo:

No lo usaría: solo se usaría una vez en la primera llamada, por lo que ¿mucho código adicional para tratar con 1 línea ...?

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    SomeObject i = obj2.callAnotherMethod(a, b, c, h);
    FinalResult j = obj3.callAFinalMethod(c, e, f, h, i);
}

Puede usarlo: aquí, puede hacer un poco más. Primero, puede factorizar los parámetros para 3 llamadas a métodos. también puede realizar otras 2 líneas en sí mismo ... por lo que se convierte en una variable de estado en cierto sentido ...

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    e = h.resultOfSomeTransformation();
    SomeObject i = obj2.callAnotherMethod(a, b, c, d, e, f, g);
    f = i.somethingElse();
    FinalResult j = obj3.callAFinalMethod(a, b, c, d, e, f, g, h, i);
}
  1. Patrón de constructor: este es un anti-patrón en mi opinión. El mecanismo de manejo de errores más deseable es detectar antes, no más tarde; pero con el patrón del constructor, las llamadas con parámetros obligatorios faltantes (el programador no pensó en incluirlos) se mueven del tiempo de compilación al tiempo de ejecución. Por supuesto, si el programador coloca intencionalmente null o algo así en la ranura, será tiempo de ejecución, pero aún detectar algunos errores antes es una ventaja mucho mayor para atender a los programadores que se niegan a mirar los nombres de los parámetros del método al que están llamando. Lo encuentro solo apropiado cuando se trata de una gran cantidad de parámetros opcionales , e incluso entonces, el beneficio es marginal en el mejor de los casos. Estoy muy en contra del "patrón" del constructor.

La otra cosa que la gente se olvida de considerar es el papel del IDE en todo esto. Cuando los métodos tienen parámetros, los IDE generan la mayor parte del código por usted, y las líneas rojas le recuerdan lo que necesita suministrar / configurar. Cuando usa la opción 3 ... pierde esto por completo. Ahora depende del programador hacerlo bien, y no hay señales durante la codificación y el tiempo de compilación ... el programador debe probarlo para averiguarlo.

Además, las opciones 2 y 3, si se adoptan de forma generalizada innecesariamente, tienen implicaciones negativas a largo plazo en términos de mantenimiento debido a la gran cantidad de código duplicado que genera. Cuanto más código hay, más hay que mantener, más tiempo y dinero se gasta en mantenerlo.

Juan
fuente