sintaxis refactorizacion programar programacion principiantes para lenguaje introduccion como comandos codigos codigo refactoring

refactoring - programar - refactorizacion de codigo



¿Cuál es la mejor manera de refactorizar un método que tiene demasiados(6+) parámetros? (23)

Ocasionalmente encuentro métodos con una cantidad incómoda de parámetros. La mayoría de las veces, parecen ser constructores. Parece que debería haber una mejor manera, pero no puedo ver qué es.

return new Shniz(foo, bar, baz, quux, fred, wilma, barney, dino, donkey)

He pensado en utilizar structs 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?


¿Qué tal si no lo configuras todo de una vez en los constructores pero lo haces a través de propiedades / setters ? He visto algunas clases de .NET que utilizan este enfoque, como la clase de Process :

Process p = new Process(); p.StartInfo.UseShellExecute = false; p.StartInfo.CreateNoWindow = true; p.StartInfo.RedirectStandardOutput = true; p.StartInfo.RedirectStandardError = true; p.StartInfo.FileName = "cmd"; p.StartInfo.Arguments = "/c dir"; p.Start();


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, normalmente creo un objeto ShnizParams para pasar, como usted describe.


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 clase incorrecta: en ese caso, la estructura / clase auxiliar sugerida anteriormente suele ser un buen enfoque, pero también tiende a terminar con montones de estructuras que son solo bolsas de propiedades y no hagas nada útil. El constructor de 8 argumentos también podría indicar que su clase es demasiado genérica / demasiado multiusos, 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 ej. Shniz.NewBaz (foo, bar) podría llamar realmente al constructor real que pasa los parámetros correctos.


Cuando un clas tiene un constructor que toma demasiados argumentos, generalmente es una señal de que tiene demasiadas responsabilidades. Probablemente se puede dividir en clases separadas que cooperan para dar las mismas funcionalidades.

En caso de que realmente necesite tantos argumentos para un constructor, el patrón del Constructor puede ayudarlo. El objetivo es pasar 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 :

public class Toto { private final String state0; private final String state1; private final String state2; private final String state3; public Toto(String arg0, String arg1, String arg2, String arg3) { this.state0 = arg0; this.state1 = arg1; this.state2 = arg2; this.state3 = arg3; } public static class TotoBuilder { private String arg0; private String arg1; private String arg2; private String arg3; public TotoBuilder addArg0(String arg) { this.arg0 = arg; return this; } public TotoBuilder addArg1(String arg) { this.arg1 = arg; return this; } public TotoBuilder addArg2(String arg) { this.arg2 = arg; return this; } public TotoBuilder addArg3(String arg) { this.arg3 = arg; return this; } public Toto newInstance() { // maybe add some validation ... return new Toto(this.arg0, this.arg1, this.arg2, this.arg3); } } public static void main(String[] args) { Toto toto = new TotoBuilder() .addArg0("0") .addArg1("1") .addArg2("2") .addArg3("3") .newInstance(); } }


Cuando veo largas listas de parámetros, mi primera pregunta es si esta función u objeto está haciendo demasiado. Considerar:

EverythingInTheWorld earth=new EverythingInTheWorld(firstCustomerId, lastCustomerId, orderNumber, productCode, lastFileUpdateDate, employeeOfTheMonthWinnerForLastMarch, yearMyHometownWasIncorporated, greatGrandmothersBloodType, planetName, planetSize, percentWater, ... etc ...);

Por supuesto, este ejemplo es deliberadamente ridículo, pero he visto muchos programas reales con ejemplos apenas menos ridículos, donde una clase se usa para contener muchas cosas apenas relacionadas o no relacionadas, aparentemente solo porque el mismo programa de llamadas necesita ambas o porque el programador pasó a pensar en ambos al mismo tiempo. A veces, la solución fácil es simplemente dividir la clase en varias piezas, cada una de las cuales hace lo suyo.

Un poco más complicado es cuando una clase realmente necesita lidiar con varias cosas lógicas, como el pedido de un cliente y la información general sobre el cliente. En estos casos, prepare una clase para el cliente y una clase para el pedido, y permita que hablen entre ellos según sea necesario. Entonces, en lugar de:

Order order=new Order(customerName, customerAddress, customerCity, customerState, customerZip, orderNumber, orderType, orderDate, deliveryDate);

Nosotros podríamos tener:

Customer customer=new Customer(customerName, customerAddress, customerCity, customerState, customerZip); Order order=new Order(customer, orderNumber, orderType, orderDate, deliveryDate);

Si bien, por supuesto, prefiero las funciones que toman solo 1 o 2 o 3 parámetros, a veces tenemos que aceptar que, de manera realista, esta función requiere mucho, y que el número de sí mismo no crea complejidad. Por ejemplo:

Employee employee=new Employee(employeeId, firstName, lastName, socialSecurityNumber, address, city, state, zip);

Sí, son muchos campos, pero probablemente todo lo que hagamos con ellos es guardarlos en un registro de base de datos o lanzarlos en una pantalla o algo así. No hay mucho procesamiento aquí.

Cuando mis listas de parámetros se alargan, prefiero si puedo asignar a los campos diferentes tipos de datos. Como cuando veo una función como:

void updateCustomer(String type, String status, int lastOrderNumber, int pastDue, int deliveryCode, int birthYear, int addressCode, boolean newCustomer, boolean taxExempt, boolean creditWatch, boolean foo, boolean bar);

Y luego lo veo llamado con:

updateCustomer("A", "M", 42, 3, 1492, 1969, -7, true, false, false, true, false);

Me preocupo. Al mirar 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 lo aceptaría. Prefiero tener una firma donde todas estas cosas son enumeraciones, por lo que una llamada pasa en cosas como Type.ACTIVE en lugar de "A" y CreditWatch.NO en lugar de "falso", etc.


Depende de qué tipo de argumentos tengas, pero si son muchos valores / opciones booleanos, ¿podrías usar un Flag Enum?


En general, me inclino por el enfoque de las estructuras; presumiblemente, la mayoría de estos parámetros están relacionados de alguna manera y representan el estado de algún elemento relevante para su método.

Si el conjunto de parámetros no se puede convertir en un objeto significativo, eso es probablemente una señal de que Shniz está haciendo demasiado, y la refactorización debería implicar dividir el método en preocupaciones separadas.


Esto se cita del libro de Fowler y Beck: "Refactorización"

Lista larga de parámetros

En nuestros primeros días de programación, nos enseñaron a pasar como parámetros todo lo que necesita una rutina. Esto era comprensible porque la alternativa eran los datos globales, y los datos globales son malvados y usualmente dolorosos. Los objetos cambian esta situación porque si no tiene algo que necesita, siempre puede pedirle a otro objeto que lo obtenga. Por lo tanto, con los objetos no se pasa todo lo que el método necesita; en cambio, pasas lo suficiente para que el método pueda llegar a todo lo que necesita. Mucho de lo que necesita un método está disponible en la clase de host del método. En los programas orientados a objetos, las listas de parámetros tienden a ser mucho más pequeñas que en los programas tradicionales. Esto es bueno porque las largas listas de parámetros son difíciles de entender, porque se vuelven inconsistentes y difíciles de usar, y porque las está cambiando para siempre, ya que necesita más datos. La mayoría de los cambios se eliminan al pasar objetos porque es mucho más probable que necesite hacer solo un par de solicitudes para obtener una nueva información. Use Reemplazar parámetro con Método cuando puede obtener los datos en un parámetro haciendo una solicitud de un objeto que ya conoce. Este objeto podría ser un campo o podría ser otro parámetro. Utilice Conservar todo el objeto para tomar un montón de datos recopilados de un objeto y reemplazarlo con el objeto mismo. Si tiene varios elementos de datos sin objeto lógico, use Introducir objeto de parámetro. Hay una excepción importante para hacer estos cambios. Esto es cuando explícitamente no desea crear una dependencia desde el objeto llamado al objeto más grande. En esos casos, es deseable desempaquetar los datos y enviarlos como parámetros, pero preste atención al dolor involucrado. Si la lista de parámetros es demasiado larga o cambia con demasiada frecuencia, debe reconsiderar su estructura de dependencia.


Estoy de acuerdo con el enfoque de mover los parámetros a un objeto de parámetro (struct). En lugar de simplemente pegarlos todos en un solo objeto, revise si otras funciones usan grupos similares de parámetros. Un objeto paramater es más valioso si se usa con múltiples funciones en las que se espera que ese conjunto de parámetros cambie consistentemente a través de esas funciones. Es posible que solo coloque algunos de los parámetros en el nuevo objeto de parámetro.


La mejor manera sería encontrar formas de agrupar los argumentos. Esto supone, y realmente solo funciona si, terminarías con múltiples "agrupaciones" de argumentos.

Por ejemplo, si está pasando la especificación de un rectángulo, puede pasar x, y, ancho y alto o simplemente podría pasar un objeto rectangular que contenga x, y, ancho y alto.

Busque cosas como esta cuando refactorice para limpiarlo de alguna manera. Si los argumentos realmente no se pueden combinar, comience a ver si tiene una violación del Principio de Responsabilidad Individual.


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:

driver.connect(host, user, pass)

Podrías usar

config = new Configuration() config.setHost(host) config.setUser(user) config.setPass(pass) driver.connect(config)

YMMV


Los argumentos con nombre son una buena opción (suponiendo que el lenguaje los respalde) para eliminar la ambigüedad de listas de parámetros largas (¡o incluso breves!) Y permitir (en el caso de constructores) que las propiedades de la clase sean inmutables sin imponer un requisito para permitir su existencia en un estado parcialmente construido.

La otra opción que buscaría al hacer este tipo de refactor serían los 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 los parámetros para x, y, height y width podría factorizar xey en un objeto Point, lo que le permite pasar tres parámetros al constructor del Rectángulo. 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.


No ha proporcionado suficiente información para garantizar una buena respuesta. Una larga lista de parámetros no es inherentemente mala.

Shniz (foo, bar, baz, quux, fred, wilma, barney, dino, burro)

podría ser interpretado como:

void Shniz(int foo, int bar, int baz, int quux, int fred, int wilma, int barney, int dino, int donkey) { ...

En este caso, es mucho mejor crear una clase para encapsular los parámetros porque le da sentido a los diferentes parámetros de una manera que el compilador puede verificar, así como también visualmente facilitando la lectura del código. También hace que sea más fácil de leer y refactorizar más tarde.

// old way Shniz(1,2,3,2,3,2,1,2); Shniz(1,2,2,3,3,2,1,2); //versus ShnizParam p = new ShnizParam { Foo = 1, Bar = 2, Baz = 3 }; Shniz(p);

Alternativamente si tuvieras:

void Shniz(Foo foo, Bar bar, Baz baz, Quux quux, Fred fred, Wilma wilma, Barney barney, Dino dino, Donkey donkey) { ...

Este es un caso muy diferente porque todos los objetos son diferentes (y no es probable que sean confusos). 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 una anulación de método (el mismo nombre de método, pero diferentes firmas de método?) Todos estos tipos de detalles importan cuál es la mejor respuesta.

* Un bolso de propiedad también puede ser útil, pero no específicamente mejor dado que no se proporcionan antecedentes.

Como puede ver, hay más de 1 respuesta correcta a esta pregunta. Elige tu opción.


No quiero sonar como un crack, pero también debes asegurarte de que los datos que estás pasando realmente deban pasarse: Pasar cosas a un constructor (o método para ese asunto) huele un poco a poco énfasis en el comportamiento de un objeto.

No me malinterpreten: los métodos y constructores tendrán muchos parámetros a veces. Pero cuando se encuentran, intente considerar encapsular datos con comportamiento en su 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 propiedades (read: any) o getters / setters.


Puede cambiar la complejidad de las líneas de código fuente. Si el método en sí mismo hace demasiado (navaja suiza) trate de reducir a la mitad sus tareas creando otro método. Si el método es simple, solo necesita demasiados parámetros, entonces los llamados objetos de parámetros son el camino a seguir.


Puede intentar agrupar su parámetro en múltiples estructuras / clases significativas (si es posible).


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 tener métodos para los opcionales, devolviendo el constructor, para usarlo así:

return new Shniz.Builder(foo, bar).baz(baz).quux(quux).build();

Los detalles de esto se describen en Effective Java, 2nd Ed., P. 11. Para los argumentos del método, el mismo libro (página 189) describe tres enfoques para acortar las listas de parámetros:

  • Divide el método en múltiples métodos que toman menos argumentos
  • Cree clases de miembro de ayudante estáticas para representar grupos de parámetros, es decir, pase un DinoDonkey lugar de un dino y un donkey
  • Si los parámetros son opcionales, se puede adoptar el constructor anterior para los métodos, definir un objeto para todos los parámetros, establecer los necesarios y luego llamar a algún método de ejecución en él

Si se trata de un constructor, especialmente si hay varias variantes sobrecargadas, debe mirar el patrón del Constructor:

Foo foo = new Foo() .configBar(anything) .configBaz(something, somethingElse) // and so on

Si se trata de un método normal, debe pensar en las relaciones entre los valores que se pasan, y quizás crear un Transfer Object.


Si su idioma lo admite, use parámetros nombrados y haga tantos como sea posible (con valores predeterminados razonables).


Si tiene tantos parámetros, es probable que el método esté haciendo demasiado, por lo tanto, 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 la instancia.

Prefiere clases / métodos pequeños en general. Recuerde el principio de responsabilidad única.


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 podrían quizás asignarse después de la construcción.

¿De dónde vienen los valores? Quizás algunos valores son verdaderamente externos cuando, como otros, realmente provienen de alguna configuración o datos globales que mantiene la biblioteca.

En este caso, puede ocultar el constructor del uso externo y proporcionar una función Crear para él. La función de creación toma los valores verdaderamente externos y construye el objeto, luego utiliza accesos solo disponibles para 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 verdaderamente de naturaleza externa.


Usaría el constructor predeterminado y los definidores de propiedades. C # 3.0 tiene una buena sintaxis para hacer esto automágicamente.

return new Shniz { Foo = foo, Bar = bar, Baz = baz, Quuz = quux, Fred = fred, Wilma = wilma, Barney = barney, Dino = dino, Donkey = donkey };

La mejora del código viene en la simplificación del constructor y no tener que soportar múltiples métodos para admitir varias combinaciones. La sintaxis de "llamada" todavía es un poco "prolijo", pero en realidad no es peor que llamar manualmente a los configuradores de propiedades.


Voy a suponer que te refieres a C # . Algunas de estas cosas se aplican a otros idiomas, también.

Tienes varias opciones:

cambiar de constructor a definidores 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 del Inicializador de Objetos hace que se vea bien. También es fácil de implementar, ya que solo puede usar propiedades generadas automáticamente y omitir la escritura de los constructores.

class C { public string S { get; set; } public int I { get; set; } } new C { S = "hi", I = 3 };

Sin embargo, pierde inmutabilidad y pierde la capacidad de garantizar que los valores requeridos se establecen antes de usar el objeto en tiempo de compilación.

Patrón de constructor

Piensa en la relación entre string y StringBuilder . Puedes obtener esto para tus propias clases. Me gusta implementarlo como una clase anidada, por lo que la clase C tiene la clase relacionada C.Builder . También me gusta una interfaz fluida en el constructor. Hecho bien, puedes obtener una sintaxis como esta:

C c = new C.Builder() .SetX(4) // SetX is the fluent equivalent to a property setter .SetY("hello") .ToC(); // ToC is the builder pattern analog to ToString() // Modify without breaking immutability c = c.ToBuilder().SetX(2).ToC(); // Still useful to have a traditional ctor: c = new C(1, "..."); // And object initializer syntax is still available: c = new C.Builder { X = 4, Y = "boing" }.ToC();

Tengo un script de PowerShell que me permite generar el código del generador para hacer todo esto, donde la entrada se ve así:

class C { field I X field string Y }

Entonces puedo generar en tiempo de compilación. partial clases partial me permiten extender tanto la clase principal como el constructor sin modificar el código generado.

La refactorización "Presentar objeto parámetro" . Ver el Catálogo de Refactorización . La idea es tomar algunos de los parámetros que está pasando y colocarlos en un nuevo tipo, y luego pasar una instancia de ese tipo en su lugar. Si haces esto sin pensar, terminarás donde comenzaste:

new C(a, b, c, d);

se convierte

new C(new D(a, b, c, d));

Sin embargo, este enfoque tiene el mayor potencial para tener un impacto positivo en su código. Por lo tanto, continúe siguiendo estos pasos:

  1. Busque subconjuntos de parámetros que tengan sentido juntos. Solo agrupar sin pensar todos los parámetros de una función no te ayuda mucho; el objetivo es tener agrupaciones que tengan sentido. Sabrá que lo hizo bien cuando el nombre del nuevo tipo es obvio.

  2. Busque otros lugares donde se usen estos valores 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 tenga sentido en todos esos lugares.

  3. Busque la funcionalidad que está en el código existente, pero pertenece al nuevo tipo.

Por ejemplo, tal vez ve un código que se ve así:

bool SpeedIsAcceptable(int minSpeed, int maxSpeed, int currentSpeed) { return currentSpeed >= minSpeed & currentSpeed < maxSpeed; }

Puede tomar los parámetros minSpeed y maxSpeed y ponerlos en un nuevo tipo:

class SpeedRange { public int Min; public int Max; } bool SpeedIsAcceptable(SpeedRange sr, int currentSpeed) { return currentSpeed >= sr.Min & currentSpeed < sr.Max; }

Esto es mejor, pero para aprovechar realmente el nuevo tipo, mueva las comparaciones al nuevo tipo:

class SpeedRange { public int Min; public int Max; bool Contains(int speed) { return speed >= min & speed < Max; } } bool SpeedIsAcceptable(SpeedRange sr, int currentSpeed) { return sr.Contains(currentSpeed); }

Y ahora estamos llegando a algún lado: la implementación de SpeedIsAcceptable() ahora dice lo que quiere decir, y tiene una clase útil y reutilizable. (El siguiente paso obvio es hacer que SpeedRange a Range<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 ha faltado en nuestro modelo.