Aquí hay un problema de programación / lenguaje en el que me gustaría escuchar sus pensamientos.
Hemos desarrollado convenciones que la mayoría de los programadores (deberían) seguir que no son parte de la sintaxis de los lenguajes pero sirven para hacer que el código sea más legible. Por supuesto, estos son siempre un tema de debate, pero hay al menos algunos conceptos básicos que la mayoría de los programadores encuentran agradables. Nombra tus variables apropiadamente, nombra en general, hace que tus líneas no sean exageradamente largas, evitando funciones largas, encapsulaciones, esas cosas.
Sin embargo, hay un problema que todavía no he encontrado a nadie comentando y que podría ser el más grande del grupo. Es el problema de que los argumentos sean anónimos cuando se llama a una función.
Las funciones provienen de las matemáticas donde f (x) tiene un significado claro porque una función tiene una definición mucho más rigurosa que la que suele tener en la programación. Las funciones puras en matemáticas pueden hacer mucho menos de lo que pueden hacer en programación y son una herramienta mucho más elegante, generalmente solo toman un argumento (que generalmente es un número) y siempre devuelven un valor (también generalmente un número). Si una función toma múltiples argumentos, casi siempre son solo dimensiones adicionales del dominio de la función. En otras palabras, un argumento no es más importante que los otros. Están ordenados explícitamente, claro, pero aparte de eso, no tienen un orden semántico.
Sin embargo, en programación, tenemos más funciones que definen la libertad, y en este caso diría que no es algo bueno. Una situación común, tienes una función definida como esta
func DrawRectangleClipped (rectToDraw, fillColor, clippingRect) {}
Mirando la definición, si la función está escrita correctamente, está perfectamente claro qué es qué. Al llamar a la función, incluso podría tener algo de magia de finalización de código / intellisense en su IDE / editor que le dirá cuál debería ser el siguiente argumento. Pero espera. Si necesito eso cuando estoy escribiendo la llamada, ¿no hay algo que nos falta aquí? La persona que lee el código no tiene el beneficio de un IDE y, a menos que salte a la definición, no tiene idea de cuál de los dos rectángulos pasados como argumentos se utiliza para qué.
El problema va más allá que eso. Si nuestros argumentos provienen de alguna variable local, puede haber situaciones en las que ni siquiera sabemos cuál es el segundo argumento, ya que solo vemos el nombre de la variable. Tome por ejemplo esta línea de código
DrawRectangleClipped(deserializedArray[0], deserializedArray[1], deserializedArray[2])
Esto se alivia en diversos grados en diferentes idiomas, pero incluso en lenguajes estrictamente tipados e incluso si nombra sus variables con sensatez, ni siquiera menciona el tipo que es la variable cuando la pasa a la función.
Como suele ocurrir con la programación, hay muchas posibles soluciones a este problema. Muchos ya están implementados en idiomas populares. Parámetros con nombre en C #, por ejemplo. Sin embargo, todo lo que sé tiene inconvenientes significativos. Nombrar cada parámetro en cada llamada de función no puede conducir a un código legible. Casi parece que tal vez estamos superando las posibilidades que nos brinda la programación de texto sin formato. Nos hemos movido de SOLO texto en casi todas las áreas, pero todavía codificamos lo mismo. ¿Se necesita más información para que se muestre en el código? Agrega más texto. De todos modos, esto se está volviendo un poco tangencial, así que me detendré aquí.
Una respuesta que obtuve al segundo fragmento de código es que probablemente primero desempaquetarás la matriz en algunas variables con nombre y luego las usarás, pero el nombre de la variable puede significar muchas cosas y la forma en que se llama no necesariamente te dice la forma en que se supone que debe hacerlo. ser interpretado en el contexto de la función llamada. En el ámbito local, es posible que tenga dos rectángulos llamados leftRectangle y rightRectangle porque eso es lo que representan semánticamente, pero no necesita extenderse a lo que representan cuando se asigna a una función.
De hecho, si sus variables se nombran en el contexto de la función llamada, entonces está introduciendo menos información de la que potencialmente podría con esa llamada de función y, en algún nivel, si conduce a un código peor. Si tiene un procedimiento que da como resultado un rectángulo que almacena en rectForClipping y luego otro procedimiento que proporciona rectForDrawing, entonces la llamada real a DrawRectangleClipped es solo una ceremonia. Una línea que no significa nada nuevo y está ahí solo para que la computadora sepa exactamente lo que quieres, aunque ya lo hayas explicado con tu nombre. Esto no es algo bueno.
Realmente me encantaría escuchar nuevas perspectivas sobre esto. Estoy seguro de que no soy el primero en considerar esto un problema, entonces, ¿cómo se resuelve?
Respuestas:
Estoy de acuerdo en que la forma en que las funciones se usan a menudo puede ser una parte confusa de escribir código, y especialmente leer código.
La respuesta a este problema depende en parte del idioma. Como mencionó, C # ha nombrado parámetros. La solución de Objective-C a este problema involucra nombres de métodos más descriptivos. Por ejemplo,
stringByReplacingOccurrencesOfString:withString:
es un método con parámetros claros.En Groovy, algunas funciones toman mapas, lo que permite una sintaxis como la siguiente:
En general, puede resolver este problema limitando el número de parámetros que pasa a una función. Creo que 2-3 es un buen límite. Si parece que una función necesita más parámetros, me hace repensar el diseño. Pero, esto puede ser más difícil de responder en general. A veces intentas hacer demasiado en una función. A veces tiene sentido considerar una clase para almacenar sus parámetros. Además, en la práctica, a menudo encuentro que las funciones que toman una gran cantidad de parámetros normalmente tienen muchas de ellas como opcionales.
Incluso en un lenguaje como Objective-C tiene sentido limitar el número de parámetros. Una razón es que muchos parámetros son opcionales. Para ver un ejemplo, vea rangeOfString: y sus variaciones en NSString .
Un patrón que uso a menudo en Java es usar una clase de estilo fluido como parámetro. Por ejemplo:
Esto utiliza una clase como parámetro, y con una clase de estilo fluido, hace que el código sea fácil de leer.
El fragmento de Java anterior también ayuda cuando el orden de los parámetros puede no ser tan obvio. Normalmente asumimos con coordenadas que X viene antes que Y. Y normalmente veo la altura antes que el ancho como una convención, pero eso aún no está muy claro (
something.draw(5, 20)
).También he visto algunas funciones como,
drawWithHeightAndWidth(5, 20)
pero incluso estas no pueden tomar demasiados parámetros, o comenzaría a perder legibilidad.fuente
Dimension(int width, int height)
yGridLayout(int rows, int cols)
(el número de filas es la altura, lo que significa queGridLayout
tiene la altura primero yDimension
el ancho).array_filter($input, $callback)
versusarray_map($callback, $input)
,strpos($haystack, $needle)
versusarray_search($needle, $haystack)
Principalmente se resuelve con un buen nombre de funciones, parámetros y argumentos. Sin embargo, ya exploró eso y descubrió que tenía deficiencias. La mayoría de esas deficiencias se mitigan manteniendo funciones pequeñas, con un pequeño número de parámetros, tanto en el contexto de llamada como en el contexto de llamada. Su ejemplo particular es problemático porque la función que está llamando está tratando de hacer varias cosas a la vez: especificar un rectángulo base, especificar una región de recorte, dibujarla y llenarla con un color específico.
Esto es como intentar escribir una oración usando solo los adjetivos. Ponga más verbos (llamadas a funciones) allí, cree un sujeto (objeto) para su oración, y es más fácil de leer:
Incluso si
clipRect
ycolor
tienen nombres terribles (y no debería), todavía se puede discernir sus tipos a partir del contexto.Su ejemplo deserializado es problemático porque el contexto de llamada está tratando de hacer demasiado a la vez: deserializar y dibujar algo. Debe asignar nombres que tengan sentido y separen claramente las dos responsabilidades. Como mínimo, algo como esto:
Muchos problemas de legibilidad son causados por tratar de ser demasiado conciso, omitiendo las etapas intermedias que los humanos requieren para discernir el contexto y la semántica.
fuente
En la práctica, se resuelve con un mejor diseño. Es excepcionalmente raro que las funciones bien escritas tomen más de 2 entradas, y cuando ocurre, es raro que esas muchas entradas no puedan agregarse en algún paquete cohesivo. Esto hace que sea bastante fácil dividir funciones o agregar parámetros para que no haga que una función haga demasiado. Una tiene dos entradas, se vuelve fácil de nombrar y mucho más claro acerca de qué entrada es cuál.
Mi lenguaje de juguete tenía el concepto de frases para lidiar con esto, y otros lenguajes de programación centrados en el lenguaje más natural han tenido otros enfoques para abordarlo, pero todos tienden a tener otras desventajas. Además, incluso las frases son poco más que una buena sintaxis para hacer que las funciones tengan mejores nombres. Siempre será difícil crear un buen nombre de función cuando se necesitan muchas entradas.
fuente
En Javascript (o ECMAScript ), por ejemplo, muchos programadores se acostumbraron a
Y como práctica de programación, pasó de los programadores a sus bibliotecas y de allí a otros programadores a quienes les gustó y lo usaron y escribieron algunas bibliotecas más, etc.
Ejemplo
En lugar de llamar
Me gusta esto:
, que es un estilo válido y correcto, se llama
Me gusta esto:
, que es válido y correcto y agradable con respecto a su pregunta.
Por supuesto, tiene que haber condiciones adecuadas para esto: en Javascript, esto es mucho más viable que en, por ejemplo, C. En JavaScript, esto incluso dio origen a la notación estructural ahora ampliamente utilizada que se hizo popular como una contraparte más ligera de XML. Se llama JSON (puede que ya hayas oído hablar de él).
fuente
params
(a menudo que contienen argumentos opcionales y, a menudo, opcional) como, por ejemplo, esta función . Esto hace que las funciones con muchos argumentos sean bastante fáciles de entender (en mi ejemplo hay 2 argumentos obligatorios y 6 opciones).Debería usar el objetivo C, entonces, aquí hay una definición de función:
Y aquí se usa:
Creo que ruby tiene construcciones similares y puedes simularlas en otros idiomas con listas de valores-clave.
Para funciones complejas en Java, me gusta definir variables ficticias en la redacción de las funciones. Para su ejemplo izquierda-derecha:
Parece más codificación, pero puede, por ejemplo, usar leftRectangle y luego refactorizar el código más adelante con "Extract local variable" si cree que no será comprensible para un futuro mantenedor del código, que podría ser o no ser usted.
fuente
Mi enfoque es crear variables locales temporales, pero no solo llamarlas
LeftRectange
yRightRectangle
. Más bien, uso nombres algo más largos para transmitir más significado. A menudo trato de diferenciar los nombres tanto como sea posible, por ejemplo, no llamarlos a ambossomething_rectangle
, si su rol no es muy simétrico.Ejemplo (C ++):
e incluso podría escribir una función o plantilla de envoltura de una línea:
(ignore los signos de unión si no conoce C ++).
Si la función hace varias cosas raras sin una buena descripción, ¡entonces probablemente sea necesario refactorizarla!
fuente