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 tengan pocos parámetros. como sea posible, incluso sugiere que 3 o más parámetros son demasiado para una función (lo cual me parece muy exagerado e idealista), así que comencé a preguntarme ...
Las prácticas de usar variables globales y pasar muchos argumentos sobre las funciones serían malas prácticas de programación, pero el uso de variables globales puede reducir en gran medida el número de parámetros en las funciones ...
Entonces 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.
- Numero de funciones.
- Frecuencia en la que se usan 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 yo quisiera saber ...
- ¿Compartes mi opinión?
- ¿Qué opinas de otros casos en los que el código fuente es más grande, etc.?
PS . Vi esta publicación , los títulos son muy similares, pero él no me pregunta qué quiero saber.
postLetter(string country, string town, string postcode, string streetAddress, int appartmentNumber, string careOf)
es una versión malolientepostLetter(Address address)
. Continúa leyendo el libro, ojalá diga algo así.Respuestas:
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, incluida una capacidad de prueba deficiente, 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.
Se puede ser un olor diseño. Si tiene los mismos parámetros que se pasan a la mayoría de las funciones en su sistema, puede haber una preocupación transversal que debería manejarse mediante la introducción de un nuevo componente. No creo que pasar la misma variable a muchas funciones sea un buen razonamiento 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 según sus parámetros y no deben tener efectos secundarios. Si tiene una función pura, puede razonar sobre la función mirando solo una función. Si su función no es pura, debe considerar el estado de otros componentes, y se hace mucho más difícil razonar.
fuente
Debe evitar variables globales como la peste .
No voy a poner un límite difícil de número de argumentos (como 3 o 4), pero que no desee mantener 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 le haga algostruct
.Para un código modular, limpio y de buen olor, intente atenerse al principio de responsabilidad única . Haga eso con sus estructuras (u objetos), las funciones y los archivos de origen. Si hace eso, el número natural de parámetros pasados a una función será obvio.
fuente
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 obtenga, 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 llama una preocupación transversal , es decir, 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 función con variables globales. Seguirían siendo parámetros, pero más desagradables.
fuente
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 funciones repetidas por 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 coherentes (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 tiene la opinión en un contexto con menos de 600 líneas "valdría la pena usar variables globales" , entonces comparte perfectamente la opinión del tío Bob. Por supuesto, es discutible si "3 parámetros como 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 una compensación, no hay una regla estricta donde dibujar la línea.
fuente
Tener muchos parámetros se considera indeseable, pero convertirlos en campos o variables globales es mucho peor porque no resuelve el problema real sino que introduce nuevos problemas.
Tener muchos parámetros no es en sí mismo el problema, pero es un síntoma de que podría tener un problema. Considere este método:
Tener 7 parámetros es una señal de advertencia definitiva. El problema subyacente es que estos parámetros no son independientes sino que pertenecen a grupos.
left
ytop
pertenecen juntos como unaPosition
estructura,length
yheight
comoSize
estructurared
,blue
ygreen
comoColor
estructura. ¿Y tal vez laColor
transparencia pertenece a unaBrush
estructura? ¿QuizásPosition
ySize
pertenece a unaRectangle
estructura, en cuyo caso podemos considerar convertirlo en unPaint
método en elRectangle
objeto? Entonces podemos terminar con:¡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:
Aquí hemos logrado la misma reducción en el número de parámetros, pero en realidad hemos empeorado el diseño .
En pocas palabras: para cualquier consejo de programación y reglas generales es muy importante comprender el razonamiento subyacente.
fuente
No
¿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 formas de evitar 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 dentro?
Es el costo de envío y manejo.
Dime que puedes leer eso. Adelante, inténtalo.
Es por eso.
Ese truco se llama introducir parámetro objeto .
Y sí, es solo un truco si todo lo que estás haciendo es contar parámetros. ¡Lo que debes contar es IDEAS! Abstracciones! ¿En qué me estás haciendo pensar a la vez? Mantenlo simple.
Ahora este es el mismo recuento:
¡AY! ¡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 todavía es débil. ¿Cuál es una forma más poderosa de pensar en esto?
¿Por qué hacer que la función haga más de lo que realmente necesita hacer? El principio de responsabilidad única no es solo 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 se pueden usar con otros.
He visto código donde el número más común de líneas en una función era 1. En serio. No digo que tenga que escribir de esa manera, pero sí, no me diga que una global es la ÚNICA forma en que puede cumplir con esta regla. Deja de intentar salir de la refactorización de esa función correctamente. Sabes que puedes. Puede dividirse en algunas funciones. En realidad, podría convertirse en unos pocos objetos. Demonios, incluso podrías dividir parte de él 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 solucione el dolor soluciona el problema. Solo tenga en cuenta cuando simplemente está intercambiando un dolor por otro.
fuente
Nunca usaría variables globales para reducir los parámetros. La razón es que cualquier función / comando puede alterar las variables globales, por lo que la entrada de la función no es confiable y propensa a valores que están fuera del alcance de lo que la función puede manejar. ¿Qué pasa si la variable se cambió durante la ejecución de la función y la mitad de la función tenía valores diferentes que la otra mitad?
Pasar parámetros, por otro lado, restringe el alcance de la variable a solo 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.
fuente
Estoy con el tío Bob en esto y estoy de acuerdo en que más de 3 params es algo que se debe evitar (muy rara vez uso más de 3 params en una función). Tener muchos parámetros en una sola función crea un mayor problema de mantenimiento y probablemente sea 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 debe considerar si los parámetros no están relacionados entre sí de alguna manera y, por lo tanto, ¿debería realmente 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 obtener más parámetros! ¡Eso está cambiando una mala práctica por una aún peor!
fuente
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.
fuente
Usar variables globales siempre parece una manera 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.
La función anterior se editará y cambiará a [usando una matriz asociativa] -
Estoy de acuerdo con la respuesta de robert bristow-johnson : incluso puedes usar una estructura para vincular datos en una sola entidad.
fuente
Tomando un ejemplo de PHP 4, mira 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 "hacer tiempo", pero toma 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 van? ¿Qué pasa si ves
mktime(1, 2, 3, 4, 5, 2246);
? ¿Puedes entender eso sin tener que referirte a nada más? ¿Se2246
interpreta como un tiempo de 24 horas "22:46"? ¿Qué significan los otros parámetros? Sería mejor como un objeto.Pasando a PHP 5, ahora hay un objeto DateTime. Entre sus métodos hay dos llamados
setDate()
ysetTime()
. 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 debe 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 único método que le permita establecer los seis parámetros a la vez. Tienes que hacer dos llamadas separadas para hacer esto.
De lo que habla el tío Bob es de 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 apropiado para una mayor separación. Aunque PHP no tiene una clase
Date
y unaTime
clase separadas , podría considerar queDateTime
realmente contiene unDate
objeto y unTime
objeto.Posiblemente podría tener la siguiente estructura:
¿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 necesita ser validado para asegurarse de que cada parámetro funcione con los demás, 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
mktime()
función, 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 siempre es un código más corto o un código más inteligente, sino un código más fácil de mantener.¡Ah, y no uses globals!
fuente
Muchas buenas respuestas aquí, sin embargo, la mayoría no aborda el punto. ¿Por qué estas reglas generales? Se trata de alcance, se trata de dependencias y se trata de un modelado adecuado.
Los argumentos globales para los argumentos son peores porque solo parece que lo hiciste más simple, pero de hecho solo escondiste la complejidad. Ya no lo ves en el prototipo, pero aún debes ser consciente de ello (lo cual es difícil ya que ya no puedes verlo) y entender la lógica de la función no te ayudará porque puede haber ser otra lógica oculta que interfiere a tus espaldas. Su alcance fue por todos lados e introdujo una dependencia de lo que sea porque lo que sea ahora puede alterar su variable. No está bien.
Lo principal que debe mantenerse para una función es que puede comprender lo que hace al mirar el prototipo y llamar. Entonces 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 su cabeza, hay demasiadas cosas sucediendo, es por eso que desea limitar el número. Sin embargo, es importante qué tipo de argumentos estás tratando, algunos son peores que otros. Un booleano opcional adicional que permite el procesamiento sin distinción 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 un gran problema al respecto. Como nota al margen, las enumeraciones hacen mejores argumentos que los booleanos porque el significado de una enumeración es obvio en la llamada.
El problema típico no es que escriba una nueva función con una enorme cantidad de argumentos, comenzará con solo unos pocos cuando aún no se dé cuenta de cuán complejo 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 acordado, desea conservarlo porque es una referencia segura que conoce. Pero en retrospectiva, el modelo puede no ser tan bueno. Perdiste un paso o también en la lógica y no reconociste una entidad o dos. "Está bien ... podría comenzar de nuevo y pasar uno o dos días refactorizando mi código o ... podría agregar este argumento para poder hacer lo correcto después de todo y terminar con esto. Por ahora. Para este escenario "Para quitar este error de mi plato y poder mover la nota adhesiva a".
Cuanto más seguido elija la segunda solución, más costoso será el mantenimiento adicional y más difícil y poco atractivo será un refactorizador.
No existe una solución de placa de caldera para reducir el número de argumentos en una función existente. Agruparlos en compuestos no facilita realmente las cosas, es solo otra forma de eliminar la complejidad debajo de la alfombra. Hacerlo bien implica mirar de nuevo toda la pila de llamadas y reconocer lo que falta o se ha hecho mal.
fuente
Varias veces descubrí 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 usan juntos. Si usa esos parámetros juntos, podría ser que tengan una relación (o que no debería usarlos en absoluto).
Una vez con el objeto, generalmente encuentro que se puede mover cierta funcionalidad que este objeto aparentemente estípido. Por lo general, es fácil de probar y con una gran cohesión y un bajo acoplamiento, lo que lo hace aún mejor.
La próxima vez que necesite agregar un nuevo parámetro, tengo un buen lugar para incluirlo sin cambiar la firma de muchos métodos.
Por lo tanto, 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 use 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.
fuente
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 tiene n parámetros en una función, tiene n más piezas de "información activa" que deben estar en su cerebro para que sea fácil y precisa Comprenda lo que está haciendo el fragmento de código (Steve McConnell, en Code Complete (un libro mucho mejor, IMO), dice algo similar sobre 7 variables en el cuerpo de un método: rara vez alcanzamos eso, pero más y estás perdiendo el habilidad para mantener todo recto en tu cabeza).
El objetivo de un bajo número de variables es poder mantener pequeños los requisitos cognitivos de trabajar con este código, 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, un código mal factorizado tiende a agrupar un montón de cosas en un desastre).
Al pasar objetos en lugar de valores, quizás obtenga un nivel de abstracción para su cerebro porque ahora necesita darse cuenta de que sí, tengo un SearchContext para trabajar aquí en lugar de pensar en las 15 propiedades que podrían estar dentro de ese contexto de búsqueda.
Al intentar usar variables globales, has ido 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 arrojado a un problema mucho, mucho mejor que ahora tiene que tener en mente!
Ahora, en lugar de trabajar solo en el nivel de función, también debe considerar el alcance global de su proyecto (¡Dios mío, qué terrible! Me estremezco ...). Ni siquiera tiene toda su información frente a usted en la función (mierda, ¿cuál es ese nombre de variable global?) (Espero que esto no haya cambiado por algo más desde que llamé a esta función).
Las variables de alcance global son una de las peores cosas que se ven 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. Mal.
Al mover los parámetros fuera de su función y ponerlos en globales, se ha disparado en el suelo (o la pierna o la cara). Y Dios no quiera que decidas que , hey, puedo reutilizar este global para otra cosa ... mi mente tiembla ante el pensamiento.
Todo el ideal es mantener las cosas fáciles de administrar, y las variables globales NO lo harán. Llegaría al extremo de decir que son una de las peores cosas que puedes hacer para ir en la dirección opuesta.
fuente
He encontrado un enfoque altamente efectivo (en JavaScript) para minimizar la fricción entre las interfaces: use una interfaz uniforme para todos los módulos , específicamente: funciones de parámetro único.
Cuando necesite múltiples parámetros: use un solo objeto / hash o matriz.
Ten paciencia, te prometo que no voy a trollear ...
Antes de decir "¿de qué sirve una función de parámetro único?" O "¿Hay alguna diferencia entre 1 matriz y funciones de múltiples argumentos?"
Bueno, sí. Quizás es visualmente sutil, pero la diferencia es múltiple: 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 todavía 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.
Creo que defiendo una posición más radical: no confíe en argumentos posicionales. Simplemente siento que es frágil y conduce a discusiones sobre la posición de los malditos argumentos. Sáltelo y avance a la inevitable lucha por los nombres de funciones y variables. 😉
Sin embargo, en serio, después de nombrar los ajustes, con suerte terminará con el siguiente código, que es algo autodocumentado, no sensible a la posición, y permite que los futuros cambios de parámetros se manejen dentro de la función:
fuente