Código de limpieza: funciones con pocos parámetros [cerrado]

47

Leí los primeros capítulos de Clean Code de Robert C. Martin, y me parece que es bastante bueno, pero tengo una duda, en una parte se menciona que es bueno ( cognitivamente) que las funciones deberían tener la menor cantidad de parámetros posible, incluso sugiere que 3 o más parámetros son demasiado para una función (que me parece muy exagerada e idealista), así que comencé a preguntarme ...

Tanto las prácticas de uso de variables globales como la de pasar muchos argumentos sobre las funciones serían malas prácticas de programación, pero el uso de variables globales puede reducir considerablemente el número de parámetros en las funciones ...

Así que quería escuchar lo que piensas al respecto, ¿vale la pena usar variables globales para reducir el número de parámetros de las funciones o no? ¿En qué casos sería?

Lo que creo es que depende de varios factores:

  • Tamaño del código fuente.
  • Número de parámetros en promedio de las funciones.
  • Número de funciones.
  • Frecuencia en la que se utilizan las mismas variables.

En mi opinión, si el tamaño del código fuente es relativamente pequeño (como menos de 600 líneas de código), hay muchas funciones, las mismas variables se pasan como parámetros y las funciones tienen muchos parámetros, entonces valdría la pena usar variables globales , pero me gustaría saber ...

  • ¿Compartes mi opinión?
  • ¿Qué piensa de otros casos en que el código fuente es más grande, etc.?

P.S . Vi esta publicación , los títulos son muy similar, pero él no pregunta qué quiero saber.

    
pregunta OiciTrap 13.07.2017 - 05:27

15 respuestas

110

No comparto tu opinión. En mi opinión, usar variables globales es una práctica peor que más parámetros, independientemente de las cualidades que describió. Mi razonamiento es que más parámetros pueden hacer que un método sea más difícil de entender, pero las variables globales pueden causar muchos problemas para el código, entre los que se incluyen poca capacidad de prueba, errores de concurrencia y un acoplamiento estrecho. No importa cuántos parámetros tenga una función, no tendrá inherentemente los mismos problemas que las variables globales.

  

... las mismas variables se pasan como parámetros

puede ser un olor a diseño. Si tiene los mismos parámetros que se están pasando a la mayoría de las funciones en su sistema, puede haber una preocupación transversal que deba manejarse al introducir un nuevo componente. No creo que pasar la misma variable a muchas funciones sea un razonamiento sólido para introducir variables globales.

En una edición de su pregunta, indicó que la introducción de variables globales podría mejorar la legibilidad del código. Estoy en desacuerdo. El uso de variables globales está oculto en el código de implementación, mientras que los parámetros de función se declaran en la firma. Las funciones deberían ser idealmente puras. Solo deben operar con sus parámetros y no deben tener efectos secundarios. Si tiene una función pura, puede razonar acerca de la función mirando solo una función. Si su función no es pura, debe considerar el estado de otros componentes, y se vuelve mucho más difícil de razonar.

    
respondido por el Samuel 13.07.2017 - 05:47
65

Debes evitar las variables globales como la plaga .

No pondría un límite estricto a la cantidad de argumentos (como 3 o 4), pero sí desea mantenerlos al mínimo, si es posible.

Use struct s (u objetos en C ++) para agrupar variables en una sola entidad y pasar eso (por referencia) a las funciones. Por lo general, una función obtiene una estructura u objeto (con algunas cosas diferentes) que se le pasa junto con un par de otros parámetros que le indican a la función que haga algo al struct .

Para obtener un código modular, limpio y con buen olor, intente seguir el principio de responsabilidad única . Haga eso con sus estructuras (u objetos), las funciones y los archivos de origen. Si lo hace, el número natural de parámetros pasados a una función será obvio.

    
respondido por el robert bristow-johnson 13.07.2017 - 07:31
55

Estamos hablando de carga cognitiva, no de sintaxis. Entonces la pregunta es ... ¿Qué es un parámetro en este contexto?

Un parámetro es un valor que afecta el comportamiento de la función. Cuantos más parámetros, más combinaciones posibles de valores obtengas, más difícil será el razonamiento sobre la función.

En ese sentido, las variables globales que utiliza la función son parámetros . Son parámetros que no aparecen en su firma, que tienen problemas de orden de construcción, control de acceso y remanencia.

A menos que dicho parámetro sea lo que se denomina preocupación transversal , es un estado de todo el programa que todo usa pero nada altera (por ejemplo, un objeto de registro), no debe reemplazar los parámetros de la función con variables globales. Todavía serían parámetros, pero más desagradables.

    
respondido por el Quentin 13.07.2017 - 10:31
34

En mi humilde opinión su pregunta se basa en un malentendido. En "Código limpio", Bob Martin no sugiere reemplazar los parámetros de función repetidos por los globales, eso sería un consejo realmente horrible. Sugiere reemplazarlos por variables miembro privadas de la clase de la función. Y también propone clases pequeñas y cohesivas (generalmente más pequeñas que las 600 líneas de código que mencionó), por lo que estas variables miembro definitivamente no son globales.

Entonces, cuando tengas la opinión en un contexto con menos de 600 líneas "valdría la pena usar variables globales" , entonces compartes perfectamente la opinión del tío Bob. Por supuesto, es discutible si "3 parámetros al máximo" es el número ideal, y si esta regla a veces conduce a demasiadas variables miembro incluso en clases pequeñas. En mi humilde opinión, esto es un compromiso, no hay una regla estricta sobre dónde dibujar la línea.

    
respondido por el Doc Brown 13.07.2017 - 07:41
33

Tener muchos parámetros se considera indeseable, pero convertirlos en campos o variables globales es mucho peor porque no resuelve el problema real, pero presenta nuevos problemas.

Tener muchos parámetros no es en sí mismo el problema, pero es un síntoma que puede tener un problema. Considera este método:

Graphics.PaintRectangle(left, top, length, height, red, green, blue, transparency);

Tener 7 parámetros es una señal de advertencia definida. El problema subyacente es que estos parámetros no son independientes sino que pertenecen a grupos. left y top pertenecen juntos como una estructura Position , length y height como una estructura Size , y red , blue y green como una estructura Color . ¿Y tal vez Color y la transparencia pertenecen a una estructura Brush ? ¿Quizás Position y Size pertenecen juntos en una estructura Rectangle , en cuyo caso incluso podemos considerar convertirlo en un método Paint en el objeto Rectangle ? Entonces podemos terminar con:

Rectangle.Paint(brush);

Misión cumplida! Pero lo importante es que en realidad hemos mejorado el diseño general, y la reducción en el número de parámetros es una consecuencia de esto. Si solo reducimos el número de parámetros sin abordar los problemas subyacentes, podríamos hacer algo como esto:

Graphics.left = something;
Graphics.top = something;
Graphics.length = something;
...etc
Graphics.PaintRectangle();

Aquí hemos logrado la misma reducción en el número de parámetros, pero en realidad hemos empeorado el diseño .

Conclusión: para cualquier consejo de programación y reglas de oro es realmente importante entender el razonamiento subyacente.

    
respondido por el JacquesB 13.07.2017 - 13:38
7
  

¿vale la pena usar variables globales para reducir el número de parámetros de las funciones o no?

No

  

Leí los primeros capítulos de este libro

¿Leíste el resto del libro?

Un global es solo un parámetro oculto. Causan un dolor diferente. Pero sigue siendo dolor. Deja de pensar en maneras de sortear esta regla. Piensa en cómo seguirlo.

¿Qué es un parámetro?

Son cosas. En una caja bien etiquetada. ¿Por qué importa cuántas cajas tienes cuando puedes poner cualquier cosa en ellas?

Es el costo de envío y manejo.

Move(1, 2, 3, 4)

Dime que puedes leer eso. Vamos, inténtalo.

Move(PointA, PointB)

Por eso.

Ese truco se llama introduce el objeto de parámetro .

Y sí, solo es un truco si todo lo que estás haciendo es contar parámetros. Lo que deberías estar contando es IDEAS! ¡Abstracciones! ¿En qué me estás haciendo pensar al mismo tiempo? Mantenlo simple.

Ahora este es el mismo conteo:

Move(xyx, y)

¡OW! ¡Esto es horrible! ¿Qué salió mal aquí?

No es suficiente limitar el número de ideas. Deben ser ideas claras. ¿Qué diablos es un xyx?

Ahora esto sigue siendo débil. ¿Cuál es una forma más poderosa de pensar sobre esto?

  

Las funciones deben ser pequeñas. No más pequeño que eso.

     

Tío Bob

Move(PointB)

¿Por qué hacer que la función haga más de lo que realmente necesita hacer? El principio de responsabilidad única no es sólo para las clases. En serio, vale la pena cambiar una arquitectura completa solo para evitar que una función se convierta en una pesadilla sobrecargada con 10 parámetros a veces relacionados, algunos de los cuales no pueden usarse con otros.

He visto un código donde el número más común de líneas en una función era 1. En serio. No estoy diciendo que tengas que escribir así, pero no me digas que una forma global es la ÚNICA manera de cumplir con esta regla. Deja de intentar salir de refactorizar esa función correctamente. Sabes que puedes. Puede descomponerse en algunas funciones. En realidad podría convertirse en unos pocos objetos. Demonios, incluso podrías dividir una parte de ella en una aplicación completamente diferente.

El libro no te dice que cuentes tus parámetros. Te está diciendo que prestes atención al dolor que estás causando. Cualquier cosa que corrija el dolor soluciona el problema. Solo tenga en cuenta que simplemente está cambiando un dolor por otro.

    
respondido por el candied_orange 14.07.2017 - 04:44
4

Nunca usaría variables globales para reducir parámetros. La razón es que las variables globales pueden ser alteradas por cualquier función / comando, por lo tanto, la entrada de la función no es confiable y es propensa a valores que están fuera del alcance de lo que la función puede manejar. ¿Qué sucede si la variable se cambió durante la ejecución de la función y la mitad de la función tenía valores diferentes a la otra mitad?

Por otro lado, pasar parámetros, restringe el alcance de la variable solo a su propia función, de modo que solo la función puede modificar un parámetro una vez que se llama.

Si necesita pasar variables globales en lugar de un parámetro, es preferible rediseñar el código.

Solo mis dos centavos.

    
respondido por el Dimos 13.07.2017 - 07:04
2

Estoy con el tío Bob en esto y estoy de acuerdo en que más de 3 parámetros es algo que se debe evitar (rara vez uso más de 3 parámetros en una función). Tener muchos parámetros en una sola función crea un problema de mantenimiento mayor y es probablemente un olor que su función está haciendo demasiado / tiene demasiadas responsabilidades.

Si está utilizando más de 3 en un método en un lenguaje OO, entonces debería considerar si los parámetros no están relacionados entre sí de alguna manera y, por lo tanto, ¿realmente debería pasar un objeto?

Además, si crea más funciones (más pequeñas), también notará que las funciones tienden a tener 3 parámetros o menos. La función / método de extracción es tu amigo :-).

¡No use variables globales como una forma de evitar tener más parámetros! ¡Eso es cambiar una mala práctica por una aún peor!

    
respondido por el bytedev 13.07.2017 - 12:01
1

Una alternativa válida a muchos parámetros de función es introducir un objeto de parámetro . Esto es útil si tiene un método compuesto que pasa (casi) todos sus parámetros a un montón de otros métodos.

En casos simples, este es un DTO simple que no tiene más que los parámetros antiguos como propiedades.

    
respondido por el Timothy Truckle 13.07.2017 - 09:56
1

Usar variables globales siempre parece una forma fácil de codificar (especialmente en un programa pequeño), pero hará que su código sea difícil de extender.

Sí, puede reducir el número de parámetros en una función utilizando una matriz para vincular los parámetros en una entidad.

function <functionname>(var1,var2,var3,var4.....var(n)){}

La función anterior se editará y se cambiará a [usando una matriz asociativa] -

data=array(var1->var1,
           var2->var2
           var3->var3..
           .....
           ); // data is an associative array

function <functionname>(data)

Estoy de acuerdo con la respuesta de robert bristow-johnson : incluso puede usar una estructura para vincular datos en una sola entidad.

    
respondido por el Narender Parmar 13.07.2017 - 08:14
1

Tomando un ejemplo de PHP 4, observe la firma de la función para mktime() :

  • int mktime ([ int $hour = date("H") [, int $minute = date("i") [, int $second = date("s") [, int $month = date("n") [, int $day = date("j") [, int $year = date("Y") [, int $is_dst = -1 ]]]]]]] )

¿No te parece confuso? La función se llama "ganar tiempo", pero toma los parámetros de día, mes y año, así como tres parámetros de tiempo. ¿Qué tan fácil es recordar en qué orden entran? ¿Qué pasa si ves mktime(1, 2, 3, 4, 5, 2246); ? ¿Puedes entender eso sin tener que referirte a nada más? ¿ 2246 se interpreta como un tiempo de 24 horas "22:46"? ¿Qué significan los otros parámetros? Sería mejor como un objeto.

Moviéndose a PHP 5, ahora hay un objeto DateTime. Entre sus métodos se encuentran dos llamados setDate() y setTime() . Sus firmas son las siguientes:

  • public DateTime setDate ( int $year , int $month , int $day )
  • public DateTime setTime ( int $hour , int $minute [, int $second = 0 ] )

Aún debes recordar que el orden de los parámetros va de mayor a menor, pero es una gran mejora. Tenga en cuenta que no hay un solo método que le permita configurar los seis parámetros a la vez. Tienes que hacer dos llamadas separadas para hacer esto.

Lo que el tío Bob está hablando es evitar tener una estructura plana. Los parámetros relacionados deben agruparse en un objeto, y si tiene más de tres parámetros, es muy probable que tenga un pseudo objeto allí, lo que le brinda la oportunidad de crear un objeto adecuado para una mayor separación. Aunque PHP no tiene una clase separada Date y Time , podría considerar que DateTime realmente contiene un objeto Date y un objeto Time .

Es posible que tengas la siguiente estructura:

<?php
$my_date = new Date;
$my_date->setDay(5);
$my_date->setMonth(4);
$my_date->setYear(2246);

$my_time = new Time;
$my_time->setHour(1);
$my_time->setMinute(2);
$my_time->setSecond(3);

$my_date_time = new DateTime;
$my_date_time->setTime($my_time);
$my_date_time->setDate($my_date);
?>

¿Es obligatorio establecer dos o tres parámetros cada vez? Si desea cambiar solo la hora o solo el día, ahora es fácil hacerlo. Sí, el objeto debe validarse para asegurarse de que cada parámetro funcione con los otros, pero ese fue el caso antes de todos modos.

Lo más importante es, ¿es más fácil de entender y, por lo tanto, mantener? El bloque de código en la parte inferior es más grande que una sola función mktime() , pero diría que es mucho más fácil de entender; incluso un no programador no tendría muchos problemas para resolver lo que hace. El objetivo no es siempre un código más corto o un código más inteligente, sino un código más mantenible.

¡Oh, y no uses globals!

    
respondido por el CJ Dennis 14.07.2017 - 09:19
1

Muchas buenas respuestas aquí, sin embargo, la mayoría no aborda el tema. ¿Por qué estas reglas de oro? Se trata de alcance, se trata de dependencias y se trata de modelado adecuado.

Globals for argument es peor porque solo parece que lo hiciste más simple pero en realidad solo ocultaste la complejidad. Ya no lo ves en el prototipo, pero aún tienes que ser consciente de ello (lo cual es difícil porque ya no está ahí para que lo veas), y entender la lógica de la función no te ayudará porque puede Sé otra lógica oculta que interfiere detrás de tu espalda. Su alcance se extendió por todos lados e introdujo una dependencia en lo que sea, porque lo que sea ahora puede alterar su variable. No es bueno.

Lo principal que hay que mantener para una función es que puedes entender lo que hace mirando el prototipo y la llamada. Así que el nombre debe ser claro e inequívoco. Pero también, cuantos más argumentos haya, más difícil será comprender lo que hace. Amplía el alcance en tu cabeza, demasiadas cosas están sucediendo, por eso quieres limitar el número. Sin embargo, importa el tipo de argumentos con los que estés lidiando, algunos son peores que otros. Un booleano opcional adicional que permite un procesamiento que no distingue entre mayúsculas y minúsculas no hace que la función sea más difícil de entender, por lo que no querrá hacer una gran cosa al respecto. Como nota al margen, los enums hacen mejores argumentos que los booleanos porque el significado de un enum es obvio en la convocatoria.

El problema típico no es que escriba una nueva función con una cantidad enorme de argumentos, comenzará con solo unos pocos cuando todavía no se da cuenta de lo complejo que es realmente el problema que está resolviendo. A medida que su programa evoluciona, las listas de argumentos tienden a alargarse gradualmente. Una vez que un modelo se ha fijado en su mente, desea mantenerlo porque es una referencia segura que usted conoce. Pero en retrospectiva, el modelo puede no ser tan bueno. Te perdiste un paso o demasiado en la lógica y no pudiste reconocer una o dos entidades. "Está bien ... podría comenzar de nuevo y pasar uno o dos días reformulando mi código o ... podría agregar este argumento para que pueda hacer lo correcto después de todo y terminar con él. Por ahora. Para este escenario . Para quitar este error de mi plato para que pueda mover la nota adhesiva a "".

Cuanto más seguido vaya con la segunda solución, más costoso será el mantenimiento y el refactor será más difícil y poco atractivo.

No hay una solución de placa de caldera para reducir el número de argumentos en una función existente. Agruparlos en compuestos no es realmente hacer las cosas más fáciles, es solo otra forma de borrar la complejidad debajo de la alfombra. Hacerlo correctamente implica volver a mirar toda la pila de llamadas y reconocer lo que falta o se ha hecho mal.

    
respondido por el Martin Maat 16.07.2017 - 10:06
0

Varias veces he encontrado que agrupar muchos parámetros que se enviaron juntos mejoró las cosas.

  • Te obliga a dar un buen nombre a esta agrupación de Conceptos que se utilizan juntos. Si usas esos parámetros juntos, podría ser que tengan una relación (o que no debas usarlos juntos).

  • Una vez con el objeto, generalmente encuentro que se puede mover alguna funcionalidad que este objeto aparentemente estúpido. Por lo general, es fácil de probar y con gran cohesión y bajo acoplamiento hace que la cosa sea aún mejor.

  • La próxima vez que necesite agregar un nuevo parámetro, tengo un lugar agradable para incluirlo sin cambiar la firma de muchos métodos.

Puede que no funcione el 100% de las veces, pero pregúntese si esta lista de parámetros debe agruparse en un objeto. Y por favor. No utilice clases sin tipo como Tuple para evitar crear el objeto. Está utilizando programación orientada a objetos, no le importa crear más objetos si los necesita.

    
respondido por el Borjab 14.07.2017 - 10:48
0

Con el debido respeto, estoy bastante seguro de que se ha perdido completamente el punto de tener una pequeña cantidad de parámetros en una función.

La idea es que el cerebro solo puede contener tanta "información activa" a la vez, y si tienes los parámetros n en una función, tienes n más piezas de "información activa" que deben estar en su cerebro para comprender de manera fácil y precisa lo que está haciendo la pieza de código (Steve McConnell, en Código Completo (un libro mucho mejor, OMI), dice algo similar sobre 7 variables en un método cuerpo: rara vez alcanzamos eso, pero más y estás perdiendo la capacidad de mantener todo derecho en tu cabeza).

El punto de un bajo número de variables es poder mantener los requisitos cognitivos de trabajar con este código pequeño, para que pueda trabajar en él (o leerlo) de manera más efectiva. Una causa secundaria de esto es que el código bien factorizado tenderá a tener cada vez menos parámetros (por ejemplo, el código pobremente factorizado tiende a agrupar un montón de cosas en un lío).

Al pasar objetos en lugar de valores, quizás obtenga un nivel de abstracción para su cerebro porque ahora debe darse cuenta de que sí, tengo un SearchContext con el que trabajar aquí en lugar de pensar en el 15 propiedades que podrían estar dentro de ese contexto de búsqueda.

Al intentar usar las variables globales, has desaparecido completamente en la dirección equivocada. Ahora, no solo no ha resuelto los problemas de tener demasiados parámetros para una función, ¡ha tomado ese problema y lo ha puesto en un problema mucho mejor que ahora tiene que llevar en su mente! / em>

Ahora, en lugar de trabajar solo en el nivel de función, también debes considerar el alcance global de tu proyecto (¡qué terrible! Me estremezco ...). Ni siquiera tienes toda tu información frente a ti en la función (mierda, ¿cuál es el nombre de la variable global?) (Espero que esto no haya cambiado desde que llamé a esta función).

Las variables de alcance global son una de las peores cosas que se pueden ver en un proyecto (grande o pequeño). Denotan una discapacidad para la gestión adecuada del alcance, que es una habilidad muy fundamental para la programación. Ellos. Son. Malvado.

Al mover los parámetros fuera de tu función y colocarlos en globales, te has disparado a ti mismo en el suelo (o la pierna o la cara). Y Dios no quiera que alguna vez decidas que hey, puedo reutilizar este global para otra cosa ... mi mente tiembla ante el pensamiento.

Todo lo ideal es mantener las cosas fáciles de administrar, y las variables globales NO lo van a hacer. Me atrevería a decir que son una de las peores cosas que puedes hacer para ir en la dirección opuesta.

    
respondido por el jleach 16.07.2017 - 14:35
-1

He encontrado un enfoque altamente efectivo (en JavaScript) para minimizar la fricción entre las interfaces: Utilice una interfaz uniforme para todos los módulos , específicamente: funciones de param solo.

Cuando necesite varios parámetros: use un solo objeto / hash o matriz.

Ten paciencia conmigo, te prometo que no estoy haciendo trolls ...

Antes de decir "¿para qué sirve una única función param?" o "¿Hay alguna diferencia entre las funciones 1 Array y multi arg?"

Bueno, sí. Tal vez sea visualmente sutil, pero la diferencia es muchas veces: exploro los muchos beneficios aquí

Aparentemente, algunas personas piensan que 3 es el número correcto de argumentos. Algunos piensan que es 2. Bueno, eso aún plantea la pregunta: "¿qué parámetro va en arg [0]?" En lugar de elegir la opción que restringe la interfaz con una declaración más rígida.

Supongo que defiendo una posición más radical: no confíes en los argumentos posicionales. Simplemente siento que es frágil y conduce a discusiones sobre la posición de los malditos argumentos. Salta y avanza hacia la inevitable lucha por los nombres de funciones y variables.

respondido por el Dan Levy 13.07.2017 - 13:33

Lea otras preguntas en las etiquetas