Estaba pensando en lo fácil que sería leer código si, al llamar a una función, pudiera escribir:
doFunction(param1=something, param2=somethingElse);
No puedo pensar en ningún inconveniente y haría que el código fuera mucho más legible. Sé que podría pasar una matriz como único argumento y tener las claves de la matriz como nombres de parámetros, sin embargo, eso aún no sería tan legible.
¿Hay una desventaja de esto que me estoy perdiendo? Si no, ¿por qué muchos idiomas no permiten esto?
Respuestas:
Especificar nombres de parámetros no siempre hace que el código sea más legible: como ejemplo, ¿preferiría leer
min(first=0, second=1)
omin(0, 1)
?Si acepta el argumento del párrafo anterior, es bastante obvio que especificar nombres de parámetros no debería ser obligatorio. ¿Por qué no todos los idiomas hacen que la especificación de nombres de parámetros sea una opción válida?
Puedo pensar en al menos dos razones:
Tenga en cuenta que no conozco ningún lenguaje que implemente parámetros con nombre sin implementar también parámetros opcionales (que requieren parámetros con nombre), por lo que debe tener cuidado de sobreestimar sus beneficios de legibilidad, que se pueden obtener de manera más consistente con la definición de Interfaces fluidas .
fuente
Los parámetros con nombre hacen que el código sea más fácil de leer, más difícil de escribir
Cuando leo un fragmento de código, los parámetros con nombre pueden introducir un contexto que hace que el código sea más fácil de entender. Considere, por ejemplo, este constructor:
Color(1, 102, 205, 170)
. ¿Qué demonios significa eso? De hecho,Color(alpha: 1, red: 102, green: 205, blue: 170)
sería mucho más fácil de leer. Pero, por desgracia, el compilador dice "no" , quiereColor(a: 1, r: 102, g: 205, b: 170)
. Al escribir código usando parámetros con nombre, pasa una cantidad innecesaria de tiempo buscando los nombres exactos; es más fácil olvidar los nombres exactos de algunos parámetros que olvidar su orden.Esto una vez me mordió al usar una
DateTime
API que tenía dos clases de hermanos para puntos y duraciones con interfaces casi idénticas. Si bienDateTime->new(...)
aceptó unsecond => 30
argumento, elDateTime::Duration->new(...)
deseadoseconds => 30
, y similar para otras unidades. Sí, tiene mucho sentido, pero esto me mostró que los parámetros nombrados son fáciles de usar.Los malos nombres ni siquiera hacen que sea más fácil de leer
Otro ejemplo de cómo los parámetros nombrados pueden ser malos es probablemente el lenguaje R. Este fragmento de código crea un diagrama de datos:
Se ven dos argumentos posicionales para el X y Y las filas de datos, y luego una lista de parámetros con nombre. Hay muchas más opciones con valores predeterminados, y solo se enumeran aquellos cuyos valores predeterminados quería cambiar o especificar explícitamente. Una vez que ignoramos que este código usa números mágicos y podría beneficiarse del uso de enumeraciones (¡si R tuviera alguno!), El problema es que muchos de estos nombres de parámetros son bastante indescifrables.
pch
es en realidad el carácter de la trama, el glifo que se dibujará para cada punto de datos.17
es un círculo vacío, o algo así.lty
es el tipo de línea Aquí1
hay una línea continua.bty
es el tipo de caja Configurarlo para"n"
evitar que se dibuje un cuadro alrededor de la trama.ann
controla la apariencia de las anotaciones de eje.Para alguien que no sabe lo que significa cada abreviatura, estas opciones son bastante confusas. Esto también revela por qué R usa estas etiquetas: no como código autodocumentado, sino (siendo un lenguaje de tipo dinámico) como claves para asignar los valores a sus variables correctas.
Propiedades de parámetros y firmas
Las firmas de funciones pueden tener las siguientes propiedades:
Diferentes idiomas aterrizan en diferentes coordenadas de este sistema. En C, los argumentos están ordenados, sin nombre, siempre son obligatorios y pueden ser varargs. En Java, la situación es similar, excepto que las firmas pueden sobrecargarse. En el Objetivo C, las firmas se ordenan, nombran, requieren y no se pueden sobrecargar porque es solo azúcar sintáctico alrededor de C.
Los idiomas escritos dinámicamente con varargs (interfaces de línea de comandos, Perl, ...) pueden emular parámetros con nombre opcionales. Los idiomas con sobrecarga de tamaño de firma tienen algo así como parámetros opcionales posicionales.
Cómo no implementar parámetros con nombre
Cuando pensamos en parámetros con nombre, generalmente asumimos parámetros con nombre, opcionales y sin ordenar. Implementar estos es difícil.
Los parámetros opcionales pueden tener valores predeterminados. Estos deben ser especificados por la función llamada y no deben compilarse en el código de llamada. De lo contrario, los valores predeterminados no se pueden actualizar sin volver a compilar todo el código dependiente.
Ahora, una pregunta importante es cómo se pasan los argumentos a la función. Con parámetros ordenados, los argumentos se pueden pasar en un registro o en su orden inherente en la pila. Cuando excluimos los registros por un momento, el problema es cómo colocar argumentos opcionales desordenados en la pila.
Para eso, necesitamos un cierto orden sobre los argumentos opcionales. ¿Qué pasa si se cambia el código de declaración? Como el orden es irrelevante, un reordenamiento en la declaración de función no debería cambiar la posición de los valores en la pila. También debemos considerar si es posible agregar un nuevo parámetro opcional. Desde la perspectiva de los usuarios, esto parece ser así, porque el código que no utilizó ese parámetro anteriormente debería funcionar con el nuevo parámetro. Por lo tanto, esto excluye pedidos como el uso del orden en la declaración o el uso del orden alfabético.
Considere esto también a la luz del subtipo y el Principio de sustitución de Liskov: en la salida compilada, las mismas instrucciones deberían poder invocar el método en un subtipo con posiblemente nuevos parámetros con nombre y en un supertipo.
Posibles implementaciones
Si no podemos tener un orden definitivo, entonces necesitamos una estructura de datos desordenada.
La implementación más simple es simplemente pasar el nombre de los parámetros junto con los valores. Así se emulan los parámetros nombrados en Perl o con las herramientas de línea de comandos. Esto resuelve todos los problemas de extensión mencionados anteriormente, pero puede ser una gran pérdida de espacio, no una opción en el código de rendimiento crítico. Además, procesar estos parámetros con nombre ahora es mucho más complicado que simplemente extraer valores de una pila.
En realidad, los requisitos de espacio se pueden reducir mediante la agrupación de cadenas, que puede reducir las comparaciones de cadenas posteriores a las comparaciones de puntero (excepto cuando no se puede garantizar que las cadenas estáticas se agrupen realmente, en cuyo caso las dos cadenas tendrán que compararse en detalle).
En cambio, también podríamos pasar una estructura de datos inteligente que funcione como un diccionario de argumentos con nombre. Esto es barato en el lado de la persona que llama, porque el conjunto de claves se conoce estáticamente en la ubicación de la llamada. Esto permitiría crear una función hash perfecta o precalcular un trie. La persona que llama aún tendrá que probar la existencia de todos los posibles nombres de parámetros, lo cual es algo costoso. Python usa algo como esto.
Entonces es demasiado caro en la mayoría de los casos
Si una función con parámetros con nombre debe ser extensible correctamente, no se puede suponer un orden definitivo. Entonces solo hay dos soluciones:
Otras trampas
Los nombres de variables en una declaración de función generalmente tienen algún significado interno y no son parte de la interfaz, incluso si muchas herramientas de documentación todavía los mostrarán. En muchos casos, desearía diferentes nombres para una variable interna y el argumento con nombre correspondiente. Los idiomas que no permiten elegir los nombres visibles externamente de un parámetro con nombre no ganan mucho si el nombre de la variable no se utiliza teniendo en cuenta el contexto de la llamada.
Un problema con las emulaciones de argumentos con nombre es la falta de comprobación estática en el lado de la persona que llama. Esto es especialmente fácil de olvidar al pasar un diccionario de argumentos (mirándote, Python). Esto es importante porque pasando un diccionario es una solución común, por ejemplo en JavaScript:
foo({bar: "baz", qux: 42})
. Aquí, ni los tipos de valores ni la existencia o ausencia de ciertos nombres se pueden verificar estáticamente.Emulación de parámetros con nombre (en lenguajes estáticamente escritos)
Simplemente usar cadenas como claves, y cualquier objeto como valor no es muy útil en presencia de un verificador de tipo estático. Sin embargo, los argumentos con nombre se pueden emular con estructuras o literales de objeto:
fuente
it is easier to forget the exact names of some parameters than it is to forget their order
" ... esa es una afirmación interesante.Por la misma razón que la notación húngara ya no se usa ampliamente; Intellisense (o su equivalente moral en IDE que no sean de Microsoft). La mayoría de los IDE modernos le dirán todo lo que necesita saber sobre un parámetro simplemente pasando el cursor sobre la referencia del parámetro.
Dicho esto, varios lenguajes admiten parámetros con nombre, incluidos C # y Delphi. En C #, es opcional, por lo que no tiene que usarlo si no lo desea, y hay otras formas de declarar miembros específicamente, como la inicialización de objetos.
Los parámetros con nombre son principalmente útiles cuando solo desea especificar un subconjunto de parámetros opcionales, y no todos. En C #, esto es muy útil porque ya no necesita un montón de métodos sobrecargados para proporcionar al programador esta flexibilidad.
fuente
sin(radians=2)
, no necesitas "Intellisense".Bash ciertamente admite este estilo de programación, y tanto Perl como Ruby admiten la aprobación de listas de parámetros de nombre / valor (como lo haría cualquier idioma con soporte nativo de hash / mapa). Nada le impide elegir definir una estructura / registro o hash / mapa que adjunte nombres a los parámetros.
Para los idiomas que incluyen almacenes hash / map / keyvalue de forma nativa, puede obtener la funcionalidad que desea, simplemente adoptando el modismo para pasar hashes de clave / valor a sus funciones / métodos. Una vez que lo haya probado en un proyecto, tendrá una mejor idea de si ha ganado algo, ya sea por el aumento de la productividad derivado de la facilidad de uso o la mejora de la calidad a través de la reducción de defectos.
Tenga en cuenta que los programadores experimentados se han acostumbrado a pasar parámetros por orden / ranura. También puede encontrar que este idioma solo es valioso cuando tiene más de unos pocos argumentos (digamos> 5/6). Y dado que una gran cantidad de argumentos a menudo son indicativos de métodos (demasiado) complejos, es posible que este modismo solo sea beneficioso para los métodos más complejos.
fuente
Creo que es la misma razón por la cual c # es más popular que VB.net. Si bien VB.NET es más "legible", por ejemplo, escribe "end if" en lugar de un corchete de cierre, simplemente termina siendo más cosas que rellenan el código y eventualmente lo hacen más difícil de entender.
Resulta que lo que hace que el código sea más fácil de entender es la concisión. Cuanto menos mejor. Los nombres de los parámetros de función son normalmente bastante obvios de todos modos, y realmente no van tan lejos para ayudarlo a comprender el código.
fuente
Los parámetros con nombre son una solución a un problema en la refactorización del código fuente y no pretenden hacer que el código fuente sea más legible . Los parámetros con nombre se utilizan para ayudar al compilador / analizador a resolver los valores predeterminados para las llamadas a funciones. Si desea que el código sea más legible, agregue comentarios significativos.
Los lenguajes que son difíciles de refactorizar a menudo admiten parámetros con nombre, porque
foo(1)
se romperán cuando se firmen losfoo()
cambios, perofoo(name:1)
es menos probable que se rompan, lo que requiere menos esfuerzo del programador para realizar cambiosfoo
.¿Qué haces cuando tienes que introducir un nuevo parámetro a la siguiente función y hay cientos o miles de líneas de código que llaman a esa función?
La mayoría de los programadores harán lo siguiente.
Ahora, no se requiere refactorización y la función puede ejecutarse en modo heredado cuando
gender
es nulo.Especificar
gender
un valor ahora está HARDCODED en la solicitudage
. Como este ejemplo:El programador miró la definición de la función, vio que
age
tiene un valor predeterminado0
y usó ese valor.Ahora el valor predeterminado para
age
en la definición de función es completamente inútil.Los parámetros con nombre le permiten evitar este problema.
Se pueden agregar nuevos parámetros
foo
y no tiene que preocuparse por el orden en que se agregaron, y puede cambiar los valores predeterminados sabiendo que el valor predeterminado que establece es realmente su verdadero valor predeterminado.fuente