Pregunta

¿Cómo solucionar el siguiente código de malo que pasa demasiados parámetros alrededor?

void helper1(int p1, int p3, int p5, int p7, int p9, int p10) {
  // ...
}

void helper2(int p1, int p2, int p3, int p5, int p6, int p7, int p9, int p10) {
  // ...
}

void foo(int p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8, 
         int p9, int p10) {
  helper1(p1, p3, p5, p7, p9, p10);
  helper2(p1, p2, p3, p5, p6, p7, p9, p10);
}

Veo dos enfoques diferentes:

Enfoque 1: Poner todas las funciones en una clase

class Foo {
  private:
    int p1, p2, p3, p4, p5, p6, p7, p8, p9, p10;
    void helper1() {}
    void helper2() {}
  public:
    void foo() {
      helper1();
      helper2();
    }
    // add constructor
};

Enfoque 2: Sólo tiene que pasar parámetros como clase

struct FooOptions {
    int p1, p2, p3, p4, p5, p6, p7, p8, p9, p10;
};

void helper1(const FooOptions& opt) {
  // ...
}

void helper2(const FooOptions& opt) {
  // ...
}

void foo(const FooOptions& opt) {
  helper1(opt);
  helper2(opt);
}

¿Cuáles son las ventajas y desventajas de los enfoques?

Una ventaja del enfoque 1 es que - si usted hace las funciones helper virtuales - que puede subclase y les sobrecargue, añadiendo flexibilidad. Pero entonces, en mi caso (fuera de la mini ejemplo de juguete que di) ayudantes de este tipo son a menudo templated, por lo que no pueden ser todos modos virtual.

Una ventaja del enfoque 2 es que las funciones de ayuda fácilmente se pueden llamar desde otras funciones, también.

( Esta pregunta está relacionado, pero no se discute estas dos alternativas.)

¿Fue útil?

Solución

Respuesta corta:

Feliz Año Nuevo! Yo evitaría la opción # 1 y sólo ir con la opción # 2, si los parámetros se pueden separar en grupos claras y lógicas que tienen sentido fuera de su función.

Respuesta larga

he visto muchos ejemplos de funciones que usted describió sus compañeros de trabajo. Estoy de acuerdo con usted en el hecho de que es un mal olor código. Sin embargo, la agrupación de parámetros en una clase sólo por lo que no tiene que pasar parámetros y decidir arbitrariamente en lugar de agruparlos basan en las funciones de ayuda puede dar lugar a más malos olores. Usted tiene que preguntarse si lo que mejora la legibilidad y la comprensión de otras que vienen después.

calcTime(int p1, int p2, int p3, int p4, int p5, int p6) {
    dist = calcDistance( p1, p2, p3 );
    speed = calcSpeed( p4, p5, p6 );
    return speed == 0 : 0 ? dist/speed; }

No se puede agrupar las cosas para ser más comprensible porque hay una clara distinción entre los parámetros. Entonces sugeriría enfoque # 2.

Por otra parte, el código en el que me han dado a menudo se ve así:

calcTime(int p1, int p2, int p3, int p4, int p5, int p6) {
    height = height( p1, p2, p3, p6 );
    type = getType( p1, p4, p5, p6 );
    if( type == 4 ) {
        return 2.345; //some magic number
    }
    value = calcValue( p2, p3, type ); //what a nice variable name...
    a = resetA( p3, height, value );
    return a * value; }

, que te deja con la sensación de que estos parámetros no son exactamente amable a romper en algo significativo clase se refiere. En su lugar estaría mejor servido cosas voltear alrededor como

calcTime(Type type, int height, int value, int p2, int p3)

y luego llamándola

calcTime(
    getType( p1, p4, p5, p6 ),
    height( p1, p2, p3, p6 ),
    p3,
    p4 );

que puede enviar escalofríos por su espina dorsal como esa pequeña voz dentro de su cabeza grita "seco, seco, seco!" ¿Cuál es más fácil de leer y por lo tanto fácil de mantener?

La opción # 1 es un no-go en mi cabeza ya que hay una muy buena posibilidad de que alguien se olvide de un conjunto de los parámetros. Esto podría muy fácilmente dar lugar a una difícil de detectar error que pasa las pruebas de unidades simples. Tu caso es distinto.

Otros consejos

Reescribí las clases y estructuras para que pudieran ser analizados como sigue:

class Point {
      private:
        int x, y
        void moveUp() {}
        void moveRight() {}
      public:
        void moveUpAndRight() {
          moveUp();
          moveRight();
        }
        // add constructor
    };

struct Point {
  int x, y;
};

void moveUp {}
void moveRight {}
void moveUpAndRight {
  moveUp {}
  moveRight {}
}

El uso de clases le permite actuar sobre el objeto. Código que utiliza el objeto de punto utilizará una interfaz estándar para mover el punto alrededor. Si la coordenada cambios en el sistema y la clase punto se modifica para detectar el sistema de coordenadas que se encuentra, esto permitirá que la misma interfaz para utilizar en múltiples sistemas (no romper nada) de coordenadas. En este ejemplo, tiene sentido ir con el enfoque 1.

Si los datos relacionados a la agrupación acaba de ser accedidos por motivos de organización ...

struct Dwarves {
  int Doc, Grumpy, Happy, Sleepy, Bashful, Sneezy, Dopey;
}

void killDwarves(Dwarves& dwarves) {}
void buryDwarves(Dwarves& dwarves) {}

void disposeOfDwarves(Dwarves& dwarves) {
  killDwarves(dwarves);
  buryDwarves(dwarves);
}

Entonces tiene sentido ir con Enfoque 2.

Esta es una opción de diseño orientado a objetos por lo que hay varias soluciones correctas y de lo que se nos da es difícil dar una respuesta definitiva.

Si p1, p2, etc ... son en realidad las opciones (banderas, etc ...), y no tienen ninguna relación entre sí, entonces me gustaría ir con la segunda opción.

Si algunos de ellos tienden a ser siempre juntos, entonces tal vez hay algunas clases de espera aparezca aquí, y me gustaría ir con la primera opción, pero probablemente no en una clase grande (es probable que en realidad tienen varias clases para crear - - es difícil de decir sin los nombres de los parámetros reales).

class Something {
   int p1, p3, p5 ...;
   void helper1a();
};

class SomethingElse {
   int p7, p9, p10 ...;
   void helper1b();
};

class Options {
   int p2, p4, ...
};

void foo(Something &s, SomethingElse & else, Options &options) {
  helper2(options);
  s.helper1a();
  else.helper1b();
}

Opción # 2.

Sin embargo, si usted piensa seriamente en el dominio, usted probablemente encontrará que hay una agrupación significativa de los valores de los parámetros y que se puede asignar a una entidad en su dominio.

A continuación, puede crear instancias de este objeto, pasarlo a su programa.

Por lo general esto podría algo así como un objeto Aplicación, o un objeto Session, etc.

El enfoque de ponerlos en una clase no "siente" correcta. Parece como un intento de adaptar el código antiguo en el aspecto de algo que no lo es. Con el tiempo, podría ser posible avanzar hacia un estilo "orientado a objetos" de código, pero parece más probable que terminaría siendo una mala mezcla de dos paradigmas. Pero eso se basa principalmente en mis pensamientos de lo que sucedería con algo de código no-OO con los que trabajo.

Así que entre esas dos opciones, yo creo que la opción de estructura es un poco mejor. Se deja abierta la flexibilidad para otra función que sea capaz de empaquetar los parámetros en una estructura y llamar a una de las funciones de ayuda. Sin embargo, este método me parece que tiene un problema con el aspecto autodocumentado. Si se hace necesario añadir una llamada a Helper1 de alguna otra ubicación, no está tan claro qué parámetros se deben pasar a ella.

Así, mientras que se siente como un número de votos negativos que vienen a mi manera, creo que la mejor forma de hacerlo es no cambiarla. El hecho de que un editor de buena le mostrará la lista de parámetros cuando se escribe en una nueva llamada a una de las funciones hace que sea muy sencillo de hacer las cosas bien. Y el costo a menos que sea en una zona de alto tráfico no es tan grande. Y con entornos de 64 bits es incluso menos porque más de los parámetros (es decir que 4?) Se envían típicamente en registros.

Aquí es una pregunta relacionada hablando de esto con una gran cantidad de respondedores de pesaje en.

No sé. Realmente depende de lo que los parámetros son . No hay mágicas "parámetros de la función make 20 desaparecen" patrón de diseño se puede invocar. Lo mejor que puede hacer es perfeccionar por su código. Dependiendo de lo que representan los parámetros, puede ser que sea significativo para el grupo algunos de ellos en una clase de parámetros, o poner todo en una clase monolítica, o dividir todo el asunto en varias clases. Una opción que el trabajo puede es alguna forma de currificación, pasando los objetos un poco a la vez, cada vez produciendo un nuevo objeto en el que la siguiente llamada puede ser invocado, pasando el siguiente lote de parámetros. Esto es especialmente tiene sentido si algunos parámetros permanecen a menudo la misma a través de las llamadas.

Una variante más simple de esto podría ser para envolver todo en una clase, donde se pasan algunos parámetros en el constructor (los que normalmente permanecen igual en toda una tanda de llamadas), y otros, los que deben ser suministrados por llamada, ya que está en constante cambio, se pasan en la llamada a la función (probablemente un operator() sobrecargada).

Sin conocer el real significa de su código, es imposible decir cómo puede ser mejor rediseñado.

No utilice clases como el almacenamiento de datos puros y no utilice campos para pasar datos a los métodos. Por lo general, si un método requiere demasiados parámetros que no demasiado, por lo que debe extraer la funcionalidad de ella en métodos separados. Tal vez usted podría ofrecer un ejemplo más concreto de sus métodos para que podamos discutir desde una perspectiva más grande.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top