Técnicas para minimizar el número de argumentos de funciones.

13

En Clean Code, se escribe que "el número ideal de argumentos para una función es cero". Las razones por las cuales se explican y tienen sentido. Lo que busco es técnicas para refactorizar métodos con 4 o más argumentos para resolver este problema.

Una forma es extraer los argumentos en una nueva clase, pero eso seguramente conduciría a una explosión de clases. ¿Y es probable que esas clases terminen con nombres que violen algunas de las reglas de nomenclatura (que terminan en "Datos" o "Información", etc.)?

Otra técnica es hacer que las variables utilizadas por múltiples funciones sean una variable miembro privada para evitar pasarlas, pero eso expande el alcance de la variable, posiblemente de tal manera que esté abierta a funciones que realmente no la necesitan.

Simplemente buscando maneras de minimizar los argumentos de la función, habiendo aceptado las razones por las cuales es una buena idea hacerlo.

Neil Barnwell
fuente
21
Por cierto, no estoy de acuerdo con el código limpio en absoluto. Si el número de argumentos para una función es cero, eso implica que la función tiene efectos secundarios y probablemente cambia de estado en alguna parte. Si bien estoy de acuerdo en que menos de 4 argumentos pueden ser una buena regla general, prefiero tener una función con 8 argumentos que es estática y no tiene efectos secundarios que una función no estática con cero argumentos que cambia de estado y tiene efectos secundarios .
wasatz
44
" En Clean Code, está escrito que" el número ideal de argumentos para una función es cero ". " ¿En serio? Eso está muy mal! El número ideal de parámetros es uno, con un valor de retorno derivado determinísticamente de ese parámetro. En la práctica, el número de parámetros no importa mucho; lo que importa es que, siempre que sea posible, la función debe ser pura (es decir, deriva su valor de retorno solo de sus parámetros sin efectos secundarios).
David Arno
2
Bueno, el libro más adelante señala que los efectos secundarios no son deseables ...
Neil Barnwell
2
Posible duplicado de estrategias para ajuste de parámetros
mosquito

Respuestas:

16

Lo más importante para recordar es que son pautas, no reglas.

Hay casos en que un método simplemente debe tomar un argumento. Piense en el +método para los números, por ejemplo. O el addmétodo para una colección.

De hecho, incluso se podría argumentar que lo que significa agregar dos números depende del contexto, por ejemplo, en ℤ 3 + 3 == 6, pero en ℤ | 5 3 + 3 == 2 , por lo que realmente el operador de suma debería ser un método en un objeto de contexto que toma dos argumentos en lugar de un método en números que toma un argumento.

Del mismo modo, un método para comparar dos objetos debe ser un método de un objeto tomando al otro como argumento, o un método del contexto, tomando dos objetos como argumentos, por lo que simplemente no tiene sentido tener un método de comparación con menos de un argumento

Dicho esto, hay un par de cosas que se pueden hacer para reducir la cantidad de argumentos para un método:

  • Haga el método en sí más pequeño : tal vez, si el método necesita tantos argumentos, ¿está haciendo demasiado?
  • Una abstracción faltante : si los argumentos están estrechamente correlacionados, ¿tal vez van de la mano y hay una abstracción que te falta? (Ejemplo de libro de texto canónico: en lugar de dos coordenadas, pase unPoint objeto o, en lugar de pasar el nombre de usuario y el correo electrónico, pase un IdCardobjeto).
  • Estado del objeto : si el argumento es necesario por varios métodos, tal vez debería ser parte del estado del objeto. Si solo lo necesitan algunos de los métodos pero no otros, tal vez el objeto está haciendo demasiado y realmente debería ser dos objetos.

Una forma es extraer los argumentos en una nueva clase, pero eso seguramente conduciría a una explosión de clases.

Si su modelo de dominio tiene muchos tipos diferentes de cosas, entonces su código terminará con muchos tipos diferentes de objetos. No hay nada de malo en eso.

¿Y es probable que esas clases terminen con nombres que violen algunas de las reglas de nomenclatura (que terminan en "Datos" o "Información", etc.)?

Si no puede encontrar un nombre propio, quizás haya agrupado demasiados argumentos o muy pocos. Entonces, o tienes solo un fragmento de una clase o tienes más de una clase.

Otra técnica es hacer que las variables utilizadas por múltiples funciones sean una variable miembro privada para evitar pasarlas, pero eso expande el alcance de la variable, posiblemente de tal manera que esté abierta a funciones que realmente no la necesitan.

Si tiene un grupo de métodos, todos los cuales operan con los mismos argumentos, y otro grupo de métodos que no lo hacen, tal vez pertenezcan a diferentes clases.

Tenga en cuenta con qué frecuencia usé la palabra "tal vez"? Es por eso que son pautas, no reglas. ¡Quizás tu método con 4 parámetros está perfectamente bien!

Jörg W Mittag
fuente
77
@ BrunoSchäpper: Claro: (1) "Reduzca el método en sí mismo: tal vez, si el método necesita tantos argumentos, ¿está haciendo demasiado? ". El número de parámetros es una pobre prueba de esto. Los parámetros opcionales / booleanos y muchas líneas de código son indicadores sólidos de que un método está haciendo demasiado. Muchos params son, en el mejor de los casos, débiles. (2) " Estado del objeto: si el argumento es necesario por varios métodos, tal vez debería ser parte del estado del objeto ". No, no y tres veces, no. Minimizar el estado del objeto; No funcionan los parámetros. Si es posible, pase un valor a todos los métodos a través de parámetros para evitar el estado del objeto.
David Arno
El ejemplo que diste para agregar es totalmente incorrecto. La addfunción para números naturales y la addfunción para el anillo de números enteros mod n son dos acciones de funciones diferentes en dos tipos diferentes. Tampoco entiendo lo que quieres decir con "contexto".
cabeza de jardín
Gracias @DavidArno. 1) acordado, no es un indicador fuerte en sí mismo. Pero uno bueno, no obstante. A menudo veo algunos métodos, con algunos objetos pasados ​​alrededor. No hay estado de objeto. Eso es bueno, pero 2) una mejor opción en mi humilde opinión está refactorizando esos métodos, mover el estado implícito a una nueva clase, que toma todos estos parámetros como argumentos explícitos. Terminas con un método público de argumento cero y muchos métodos internos de argumento cero a uno. El estado no es público, global o incluso se mantiene vivo por mucho tiempo, pero el código es mucho más limpio.
Bruno Schäpper
6

Tenga en cuenta que los argumentos cero no implican efectos secundarios, porque su objeto es un argumento implícito. Mire cuántos métodos de aridad cero tiene la lista inmutable de Scala , por ejemplo.

Una técnica útil que llamo la técnica de "enfoque de lente". Cuando enfoca la lente de una cámara, es más fácil ver el verdadero punto de enfoque si lo lleva demasiado lejos, luego lo retrocede al punto correcto. Lo mismo se aplica a la refactorización de software.

Especialmente si está utilizando el control de versiones distribuido, los cambios de software son fáciles de experimentar, vea si le gusta cómo se ven y retroceda si no lo hace, pero por alguna razón la gente a menudo parece reacia a hacerlo.

En el contexto de su pregunta actual, eso significa escribir las versiones de argumento cero o uno, con varias funciones separadas primero, luego es relativamente fácil ver qué funciones deben combinarse para facilitar la lectura.

Tenga en cuenta que el autor también es un gran defensor del desarrollo impulsado por pruebas, que tiende a producir funciones de baja aridad al principio porque comienza con sus casos de prueba triviales.

Karl Bielefeldt
fuente
1
Al igual que su analogía de "enfoque de lente": especialmente cuando se refactoriza, es importante usar la lente gran angular en lugar de la de primer plano. Y mirar el número de parámetros es simplemente demasiado de cerca
desde el
0

Un enfoque que simplemente (e ingenuamente, o incluso debería decir a ciegas ) solo apunta a reducir el número de argumentos a las funciones, probablemente no sea correcto. No hay absolutamente nada de malo en que las funciones tengan una gran cantidad de argumentos. Si la lógica los requiere, bueno, son necesarios ... Una larga lista de parámetros no me preocupa en absoluto, siempre que esté correctamente formateada y comentada para facilitar su lectura.

En el caso de que todos o un subconjunto de argumentos pertenezcan a una entidad lógica única y se transmitan comúnmente en grupos a lo largo de su programa, quizás tenga sentido agruparlos en algún contenedor, generalmente una estructura u otro objeto. Los ejemplos típicos pueden ser algún tipo de mensaje o tipo de datos de evento .

Puede exagerar fácilmente ese enfoque: una vez que encuentre que empacar y desempacar cosas hacia y desde dichos contenedores de transporte genera más sobrecarga que mejora la legibilidad, probablemente haya ido demasiado lejos.

OTOH, las listas de parámetros grandes pueden ser una señal de que su programa puede no estar estructurado adecuadamente; tal vez la función que requiere una cantidad tan grande de parámetros solo está tratando de hacer demasiado y debería dividirse en varias funciones más pequeñas. Prefiero comenzar aquí que preocuparme por # de parámetros.

tofro
fuente
55
Reducir ciegamente el número de argumentos es incorrecto, por supuesto. Pero no estoy de acuerdo con "No hay absolutamente nada de malo en que las funciones tengan una gran cantidad de argumentos". . En mi opinión, cuando tocas una función con gran número de argumentos, en el 99,9% de todos los casos hay una manera de mejorar la estructura del código de una manera deliberada que (también) reduce el número de argumentos de la función.
Doc Brown
@DocBrown Es por eso que existe este último párrafo y la recomendación de comenzar allí ... Y otro: Probablemente nunca haya intentado programar contra una API de MS Windows;)
tofro
"tal vez la función que requiere una cantidad tan grande de parámetros solo está tratando de hacer demasiado y debería dividirse en varias funciones más pequeñas". Estoy totalmente de acuerdo, aunque en la práctica, ¿no acabas con otra función más arriba en la pila que llama a esas varias funciones más pequeñas? A continuación, podría refactorizarlos en un objeto, pero ese objeto tendrá un ctor. Podrías usar un constructor, bla, bla, bla. El punto es que es una regresión infinita: en algún lugar, hay una serie de valores necesarios para que el software haga su trabajo, y de alguna manera tienen que llegar a esas funciones.
Neil Barnwell
1
@NeilBarnwell En el caso ideal (vale la pena refactorizar), es posible que pueda dividir una función que necesita 10 argumentos en tres que necesitan 3-4 argumentos cada uno. En el caso no tan ideal, terminas con tres funciones que necesitan 10 argumentos cada una (mejor déjalo en paz, entonces). Con respecto a su argumento de pila superior: De acuerdo, puede ser el caso, pero no necesariamente, los argumentos provienen de alguna parte, y esa recuperación también podría estar en algún lugar dentro de la pila y solo necesita colocarse en el lugar adecuado allí - Kilometraje tiende a variar
tofro
La lógica del software nunca requiere más de cuatro parámetros. Solo un compilador podría hacerlo.
theDoctor
0

No hay una forma mágica: debe dominar el dominio del problema para descubrir la arquitectura correcta. Esa es la única forma de refactorizar: dominar el dominio del problema. Más de cuatro parámetros es solo una apuesta segura de que su arquitectura actual es defectuosa e incorrecta.

Las únicas excepciones son los compiladores (metaprogramas) y las simulaciones, donde el límite es teóricamente 8, pero probablemente solo 5.

theDoctor
fuente