Colaboraciones en el Guille

Sobrecarga vs. Parámetros Opcionales.

¿Cual es el método más adecuado?

Fecha: 19/Dic/2005 (18/12/05)
Autor: Raúl Carrillo Garrido aka metsuke (rcarrillo@metsuke.com)

En mis anteriores artículos, El paradigma SAV ('Simply A Video') y El paradigma SAV (2ª Parte), defendía el uso de los parámetros opcionales siempre que fuera posible, sin embargo, tras leer algunos artículos de Francesco Balena y otros autores, comencé a plantearme si estaba enfocando la cuestión del modo correcto. Por ello decidí realizar un pequeño análisis para salir de dudas ... y he aquí el resultado.


¿Cual es el método más adecuado?

- Bueno, esa es la pregunta a la que pretendemos dar respuesta.

Sin tener en cuenta nada más que la cuestión de cantidad de código y mantenibilidad del mismo, el planteamiento inicial por mi parte es que se deben usar parámetros opcionales siempre que sustituyan eficientemente una serie de sobrecargas por dos motivos:

  1. Cantidad de Código menor.- El código necesario para construir una funcionalidad equivalente a un conjunto de sobrecargas es sensiblemente menor.
  2. Mantenibilidad del Código Superior.- El código generado con parámetros opcionales resulta más legible y por tanto la posibilidad de cometer errores en su mantenimiento es sensiblemente menor que su homologo basado en sobrecargas.

Como muestra de lo expuesto tomemos el ejemplo de las llamadas de encriptacion que vimos en anteriores artículos:

 (...) 
Public Function CryptSymmetric(ByVal pString As String) As String
return CryptSymmetric(pString,CryptedStringFormat.Base64)
End Function

Public Function CryptSymmetric(ByVal pString As String, pFormat As CryptedStringFormat) As String
return CryptSymmetric(pString, pFormat, SymmetricEncryptionMethod.Rijndael)
End Function

Public Function CryptSymmetric(ByVal pString As String, pEncryptionMethod As SymmetricEncryptionMethod) As String
return CryptSymmetric(pString, CryptedStringFormat.Base64 ,pEncryptionMethod)
End Function

Public Function CryptSymmetric(ByVal pString As String, pFormat As CryptedStringFormat, pEncryptionMethod As SymmetricEncryptionMethod) As String
[realizar operación]
End Function
(...)
 (...) 
Public Function CryptSymmetric( ByVal pString As String _
, Optional ByVal pFormat As CryptedStringFormat = CryptedStringFormat.Base64 _
, Optional ByVal pEncryptionMethod As SymmetricEncryptionMethod = SymmetricEncryptionMethod.Rijndael _
) As String
[realizar operación]
(...)
End Function
(...)

Teniendo en cuenta que he obviado los controles try...catch para centrarme en la funcionalidad, resulta obvio que en este caso, dos parámetros opcionales en un sólo método han encapsulado adecuadamente la misma funcionalidad que especificaban las cuatro sobrecargas anteriores. Como podemos ver el ahorro de tiempo y código es considerable.

Una vez planteado el punto de partida, vamos a poner a prueba diferentes afirmaciones existentes en diferentes artículos de la red a favor y en contra de los dos métodos, para tratar de establecer, de forma objetiva, qué método es mejor, sin embargo tal y como siempre apunta Jorge Cortell en sus conferencias "No quiero que me creáis, comprobadlo por vosotros mismos" (para ello en el fichero zip que os podréis descargar al final del artículo han sido incluidos todos los proyectos utilizados para las pruebas).

Afirmación 1: MSIL crea las sobrecargas así que no suponen diferencia alguna.

Aquel que haya leído solo la especificación ECMA-334 (C#) puede haber inferido - incorrectamente - que la implementación de opcionales de Vb.Net sería a través de sobrecargas, pero esto no es así, como veremos fácilmente si analizamos la documentación publica oficial sobre C# y CLI, observaremos que la especificación ECMA-334 (C#) no habla sobre parámetros opcionales y que sin embargo el Standard ECMA-335: Common Language Infrastructure (CLI) - pagina 200 - establece la implementación de parámetros opcionales mediante el indicador [opt], el valor por defecto del parámetro se establece usando la sintaxis .param (dentro del código MSIL).

Por tanto no es cierto que el compilador convierta los opcionales en sobrecargas, el MSIL contiene instrucciones especificas que implementan los parámetros opcionales, por tanto son una implementación especifica.

Afirmación 2: No permite la total compatibilidad entre lenguajes.

Cierto. Aun cuando personalmente sigo convencido de que los parámetros opcionales podrían ser una importante mejora, la realidad actual es que solo VB.Net los soporta, lo que los convierte en un problema a la hora de implementar clases que sean usadas desde otros lenguajes. Lo que nos ahorramos programando la clase original, lo desperdiciamos multiplicado por 10 si tratamos de usar esa clase desde C# o J#, pudiéndonos generar verdaderos quebraderos de cabeza.

Afirmación 3: Parámetro opcional queda compilado en el ejecutable que lo llama.

Esta afirmación, de ser cierta, supondría un grave problema ya que en .NET (a diferencia de versiones anteriores de Visual Basic, en que esto se hacía en tiempo de ejecución) el valor de los parámetros opcionales se decidiría en tiempo de compilación, de forma que , si se usa una librería que incluye parámetros opcionales en un método y el autor de dicha librería altera alguno de los parámetros por defecto, deberemos recompilar cualquier aplicación que haga uso de ella ya que las 'firmas' de dichos métodos no estarían coordinados, y esto no ocurriría si se usaran sobrecargas ...

Comprobémoslo con un ejemplo.

Para comprobar este mito, crearemos un proyecto de librería con dos funciones, una con sobrecarga y la otra con parámetro opcional. Usando esta librería, llamaremos a las funciones desde otro proyecto de consola y comprobaremos el resultado.

Código de la primera prueba.
Ambos muestran el valor correctamente.
Código de la segunda prueba.
En el caso de opcionales el valor es incorrecto.

Efectivamente la afirmación es 100% cierta como se puede comprobar (usando el proyecto [001 - Relink Test]), al recompilar la dll y sobrescribir manualmente la dll asociada al ejecutable, observamos claramente que sólo cambia correctamente el valor generado con sobrecargas, en el caso de los parámetros opcionales, dotNet había decidido el valor a proporcionar en tiempo de compilación, lo que obligaría a recompilar el ejecutable nuevamente. Esta necesidad de recopilación resulta inaceptable para la mayoría de escenarios de producción, lo que por si solo echaría por tierra el uso de opcionales, sin embargo proseguiremos con el resto de pruebas para obtener una visión más completa de esta cuestión.

Afirmación 4: Con parámetros opcionales, la aplicación resultante es mas lenta y consume mas memoria.

Esta afirmación se basa en la creencia de que estos parámetros, por haber sido implementados solamente para cubrir la migración de usuarios desde Visual Basic 6, no es todo lo veloz y optimizada que debería, provocando problemas en este aspecto.

Lo que hemos descubierto hasta ahora apoya esta teoría, pero estoy seguro de que los parámetros opcionales actuales son mucho mas rápidos en ejecución que sus equivalentes en sobrecarga, simplemente por una cuestión de volumen de código, sensiblemente mayor en sobrecargas. Si la conversión a MSIL se produjera en forma de sobrecargas (como esperábamos a priori), la velocidad seria la misma.

Para comprobar este punto, vamos a realizar comprobaciones en el tamaño del ejecutable generado, consumo de memoria en ejecución y velocidad (usando NTime), de modo que obtengamos datos mas o menos objetivos al respecto.

Comprobación 1: Tamaño ejecutable generado en forma de dll

Para ello generamos dos dlls que contienen la misma funcionalidad, en un caso implementado con sobrecargas y en el otro con parámetros opcionales (ver proyecto [002 - Dll Size]). Repetimos cada bloque de código 10 veces en cada librería para provocar una diferencia perceptible y he aquí los resultados:

Haciendo un calculo estimado, si con 10 sobrecargas la diferencia es de 512 Bytes, con 10000 la diferencia seria de 512000 bytes = 500 Kb , diferencia notable, y mas teniendo en cuenta que no hemos incluido código de depuración, pero que solo tendrá importancia en proyectos donde el espacio en disco ocupado por el programa sea crítico, como ocurre en dispositivos móviles. Para el resto de escenarios, esta diferencia no tiene porqué ser un factor determinante en la decisión a tomar.

Comprobación 2: Tamaño y consumo de memoria ejecutable generado en forma de exe.

Para realizar esta comprobación, creamos dos ejecutables y en cada uno de ellos realizamos la misma funcionalidad diez veces (igual que en la comprobación 1) empleando las dos técnicas (ver [003 - Exe Size and Mem]), y comprobamos el tamaño de los ejecutables generados y el consumo de memoria en ejecución empleando el administrador de tareas. Los resultados fueron los siguientes:

Como podemos observar, en el caso de ejecutables el espacio en disco es idéntico, y en el consumo de memoria tan solo 4 Kb de diferencia. Aquí entiendo que ocurre lo mismo que con el espacio en disco de las dll, si tomamos como referencia 10000 sobrecargas, la diferencia de consumo de memoria teóricamente seria de 4 Megas, sin embargo, al tomar en consideración los entornos habituales en que se manejan este tipo de lenguajes de alto nivel, y exceptuando el caso de dispositivos móviles, esta diferencia de consumo no debería ser un factor critico.

Comprobación 3: Estudio de Velocidad usando NTime.

Para realizar esta comprobación hemos creado una serie de pruebas de rendimiento basadas en NTime y hemos medido el numero de ejecuciones por segundo de cada uno de los métodos en estudio (ver [004 - NTime Measurements]). La tabla de resultados es la siguiente:

Medicción Sobrecarga/sec Opcionales/sec
1 114220 103444
2 76394 120554
3 112764 119260
4 96339 98745
5 116346 117550
6 113934 107261
7 72658 91869
8 71880 120481
9 110754 118329
10 114823 103071
11 112095 104144
12 117192 117302
13 114246 118764
14 116171 117619
15 120105 119317
Total 1579921 1677710

En el ejemplo sobre 3 ejecuciones con cinco repeticiones cada uno, 4-6% mas rápido opcional que sobrecarga, lo cual no es un valor que justifique por si solo el uso de uno u otro método, salvo en entornos donde la velocidad y consumo de procesador sea crítica, nuevamente me refiero a dispositivos móviles.

Algunas Reflexiones.

Compatibilidad entre lenguajes: Para que fueran realmente útiles, además de una correcta implementación en el compilador, estos deberían ser soportados por C# y J#. Mientras esto no sea así entiendo que debemos atenernos al mínimo común, y no deberíamos utilizarlos.

El hecho de que el CLI soporte esta funcionalidad como normal, a priori parece revelar no que los parámetros opcionales sean obsoletos sino que C# es incompleto, y que la única razón por la que no habría parámetros opcionales en C# es por política. En ese caso a C# le faltaría esta funcionalidad que si incorporaría VB Net... pero nada mas lejos de la realidad, esta implementación parece haber sido realizada deprisa y corriendo con objeto de compatibilizar el código de VB6 y que no habría intención de darle continuidad. Esto desde mi punto de vista es un error pero es lo que parece haber tras esto, dada la nefasta implementación de opcionales realizada.

Por mucho que se niegue, los parámetros opcionales serian un avance muy interesante en la simplicidad del código, si de verdad el msil generado consistiera en las sobrecargas esperadas - o incluso en algo nativo de MSIL - y no en esa implementación chapucera que ahora realiza el compilador. Esta mejora no sustituiría todos los casos de sobrecarga, pero si eliminaría mucho código innecesario que no hace sino complicar el mantenimiento y la depuración.

Aun con todo esta diferencia de consumo potencial da que pensar... si tan solo hubieran implementado el código de los parámetros opcionales sin los problemas del formato de compilación actual... quizás serían una alternativa factible para mejorar sustancialmente el rendimiento, pero tal y como está planteado en la actualidad solo es recomendable para optimizaciones puntuales.

Conclusiones.

Desde mi punto de vista, tomando en consideración la forma de implementación por parte del compilador, he de cambiar mi planteamiento y recomendar que no se use esta funcionalidad ya que el beneficio de la optimización obtenida con su uso no compensa los problemas que podría causar , sobre todo en cuanto a costes de depuración de los errores lógicos que provocara en aplicaciones grandes, dada la nefasta implementación actual.

Si en un futuro se establece su uso en todos los lenguajes y se realiza una compilación de los parámetros opcionales correcta, podría llegar a ser una importante mejora, pero en el momento actual, y muy a mi pesar, no lo es.

¿Cual es el mejor método?

Desde mi punto de vista y tras analizar la cuestión: las sobrecargas.

Agradecimientos.

Algunas Página de interés.

Artículos

Especificaciones


Fichero con el código de ejemplo: metsuke_sobrecargavsoptional.zip - 140 KB


Creative Commons License


ir al índice principal del Guille