Introducción y Fundamentos
El desarrollo de los algoritmos que permiten la construcción del software, normalmente se construyen a través de alguna técnica de diseño, basándose en planteos orientado generalmente a los objetivos que justifican su desarrollo. Dado que el primer paso es construir el algoritmo exitosamente, en los primeros movimientos, es problable que no se piense en optimizar los algoritmos.
Quizá le suene poco profesional, pero siempre en un principio cuando se desarrolla un algoritmo, puede que se recurra a la imaginación e improvisación. Desgraciadamente, muchos desarrolladores se detienen en este punto. Lo normal sería diseñar los algoritmos, luego someterlos a testing, métricas de ingeniería para determinar la mantenibilidad, costes, etc. Como último paso, el refinamiento para mejorar su performance y también su legibilidad, sin olvidar su debida documentación final.
El Refactory proporciona muchas ventajas. Por empezar, la optimización y refinamiento del código y, por el otro lado, la documentación de forma rápida, apropiada y libre de ambigüedades.
Deseo hacer una puntualización importante. Las técnicas de Refactoreo pueden aplicarse a cualquier tipo de lenguaje de programación. Solo ha de conocerse bien su sintaxis según el lenguaje en el que trabaje y sus mecanismos de desarrollo más apropiados. De esta forma, a través de Refactory, podrá mejorar sus códigos.
Técnicas de Refactoreo
Un Refactoreo Básico solo se trata de optimizar la legibilidad del código y en algunas oportunidades mejorar substancialmente el rendimiento del mismo. Muchos desarrolladores creen que escribir todo en una sola línea es una técnica válida de Refactoreo. La respuesta es No. Por ejemplo:
Button2.BackColor = Color.Violet : Button2.ForeColor = Color.White : ...
Este trozo de código no encierra estrategias de Refactoreo, de hecho, lo invitaría a que evite este tipo de codificaciones por varias razones. La primera y la de mayor peso es la organización de su código y por otro lado evitar potenciales errores de diversa índole. Como siempre le digo a mis alumnos, "Que Ud., tenga un Automóvil y lo utilice para llevar un mueble de su cocina a la casa de su tía, no significa que su Automóvil se trate de una Pick-Up o 4x4". Aquí ocurre exactamente lo mismo. Que funcione no significa que sea apropiado.
Refactoreo Sencillo
Para ver un caso de refactoreo muy sencillo, analicemos el siguiente código que está hecho en Visual Basic .NET Insisto nuevamente, esta técnica podría aplicarla en otros lenguajes. Tan solo deberá encontrar la sintaxis apropiada para cada estructura de refactoreo. Como verá, no resulta para nada complicado.
Button2.BackColor = Color.Violet
Button2.ForeColor = Color.White
Button2.Text = "Mi Botón"
Button2.TextAlign = ContentAlignment.MiddleCenter
Button2.Width = 80
Button2.Height = 30
Button2.ForeColor = Color.White
Button2.Text = "Mi Botón"
Button2.TextAlign = ContentAlignment.MiddleCenter
Button2.Width = 80
Button2.Height = 30
Si observa detenidamente vera que el objeto Button2 se repite en cada línea para cada propiedad asignada. Pues bien, una sencilla forma de Refactorizar este código, simplemente, bastará con organizar el código utilizando el estamento With...End. Véase el siguiente resultado de un Refactoreo extremadamente sencillo.
With Button2
.BackColor = Color.Violet
.ForeColor = Color.White
.Text = "Mi Botón"
.TextAlign = ContentAlignment.MiddleCenter
.Width = 80
.Height = 30
End With
Ahora bien, siguiendo de cerca el Refactoreo recientemente hecho, aún podemos seguir mejorando nuestro código. Si observa las propiedades finales Width y Heigth, como sabrá, ambas responden a la clase Size. Bien, por tanto, podemos utilizar la clase Size para pasarle mediante una instancia constructor los valores de Width y Height en una sóla línea. Aquí sí no solo ahorramos líneas de código y legibilidad, sino que también, mejoramos el rendimiento del código dado que lo estamos orientando fuertemente a objetos. Veamos como quedaría esta nueva modificación.
With Button2
.BackColor = Color.Violet
.ForeColor = Color.White
.Text = "Mi Botón"
.TextAlign = ContentAlignment.MiddleCenter
.Size = New Point(80, 30)
End With
.BackColor = Color.Violet
.ForeColor = Color.White
.Text = "Mi Botón"
.TextAlign = ContentAlignment.MiddleCenter
.Size = New Point(80, 30)
End With
Refactoreo de Distribución - Divide y Vencerás
Como la famosa cita "Divide y Vencerás" del genial Fiorentino Niccolò di Bernardo dei Machiavelli, o más conocido por Maquiavelo, en el mundo de la programación esta técnica resulta extremadamente útil. En las técnicas de Refactoreo, también resultan apropiadas. La división o la técnica de atomización para ser más precisos, permite separar grandes trozos de código en pequeñas porciones más o menos manipulables. Esta facilidad permite a los desarrolladores hacer más legible el código. Además de ello, permite operar estrategicamente en las áreas de interés.
Cuando se realiza mantención o corrección del código, el desarrollador se enfoca en el área apropiada haciendo foco en dicho sector y realizando una suerte de abstracción del resto del conjunto de códigos que componen el gran proceso. Si en lugar de contar con áreas separadas se tiene lo contrario, es decir, todo en una sola gran área, se corren ciertos riesgos. Es más, resulta más marañada la tarea y la legibilidad del código. Esto se puede evitar y la mejor técnica es la de atomizar los grandes procesos en procesos más pequeños. A continuación el código para la gestión de la Calculadora de Plazo Fijo.
Public Class Form1
Private Sub controlButtons(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles Button1.Click, Button2.Click
Dim inversion As Decimal = 0.0
Dim tna As Decimal = 0
Dim plazo As Integer = 0
Dim residual As Decimal = 0.0
Dim capitalFinal As Decimal = 0.0
If sender.Equals(Button1) Then
Try
inversion = TextBox1.Text
tna = TextBox2.Text
plazo = TextBox3.Text
residual = (inversion * plazo * (tna / 12)) / (365 * 100)
capitalFinal = inversion + residual
TextBox4.Text = residual
TextBox5.Text = capitalFinal
Catch ex As Exception
MsgBox("Valor numérico no válido")
TextBox1.Text = 0
TextBox2.Text = 8.5
TextBox3.Text = 30
TextBox4.Text = 0
TextBox5.Text = 0
End Try
End If
If sender.Equals(Button2) Then
TextBox1.Text = 0
TextBox2.Text = 8.5
TextBox3.Text = 30
TextBox4.Text = 0
TextBox5.Text = 0
End If
End Sub
Handles Button1.Click, Button2.Click
Dim inversion As Decimal = 0.0
Dim tna As Decimal = 0
Dim plazo As Integer = 0
Dim residual As Decimal = 0.0
Dim capitalFinal As Decimal = 0.0
If sender.Equals(Button1) Then
Try
inversion = TextBox1.Text
tna = TextBox2.Text
plazo = TextBox3.Text
residual = (inversion * plazo * (tna / 12)) / (365 * 100)
capitalFinal = inversion + residual
TextBox4.Text = residual
TextBox5.Text = capitalFinal
Catch ex As Exception
MsgBox("Valor numérico no válido")
TextBox1.Text = 0
TextBox2.Text = 8.5
TextBox3.Text = 30
TextBox4.Text = 0
TextBox5.Text = 0
End Try
End If
If sender.Equals(Button2) Then
TextBox1.Text = 0
TextBox2.Text = 8.5
TextBox3.Text = 30
TextBox4.Text = 0
TextBox5.Text = 0
End If
End Sub
End Class
Para evitar recargar la lectura de este Blog, me permito volcar directamente el Refactoreo de este código e ir explicando cada uno de los cambios realizados de modo que queda más clara esta estrategia. El código quedaría de la siguiente forma.
Public Class Form1
Private inversion As Decimal = 0.0
Private tna As Decimal = 0
Private plazo As Integer = 0
Private residual As Decimal = 0.0
Private capitalFinal As Decimal = 0.0
Private Sub controlCalcular()
inversion = TextBox1.Text
tna = TextBox2.Text
plazo = TextBox3.Text
residual = (inversion * plazo * (tna / 12)) / (365 * 100)
capitalFinal = inversion + residual
TextBox4.Text = residual
TextBox5.Text = capitalFinal
End Sub
Private Sub controlLimpiarValores()
TextBox1.Text = 0
TextBox2.Text = 8.5
TextBox3.Text = 30
TextBox4.Text = 0
TextBox5.Text = 0
End Sub
Private Sub controlButtons(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles Button1.Click, Button2.Click
If sender.Equals(Button1) Then
Try
controlCalcular()
Catch ex As Exception
MsgBox("Valor numérico no válido")
controlLimpiarValores()
End Try
End If
If sender.Equals(Button2) Then
controlLimpiarValores()
End If
End Sub
End Class
Private inversion As Decimal = 0.0
Private tna As Decimal = 0
Private plazo As Integer = 0
Private residual As Decimal = 0.0
Private capitalFinal As Decimal = 0.0
Private Sub controlCalcular()
inversion = TextBox1.Text
tna = TextBox2.Text
plazo = TextBox3.Text
residual = (inversion * plazo * (tna / 12)) / (365 * 100)
capitalFinal = inversion + residual
TextBox4.Text = residual
TextBox5.Text = capitalFinal
End Sub
Private Sub controlLimpiarValores()
TextBox1.Text = 0
TextBox2.Text = 8.5
TextBox3.Text = 30
TextBox4.Text = 0
TextBox5.Text = 0
End Sub
Private Sub controlButtons(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles Button1.Click, Button2.Click
If sender.Equals(Button1) Then
Try
controlCalcular()
Catch ex As Exception
MsgBox("Valor numérico no válido")
controlLimpiarValores()
End Try
End If
If sender.Equals(Button2) Then
controlLimpiarValores()
End If
End Sub
End Class
Empezaremos analizando las variables. Si bien, el uso en este caso de las variables solo se realizan dentro del evento único clic de ambos botones, es mejor declararlas por fuera. Una razón es la de "separar" parte del código que originalmente estaba dentro de dicho evento y evitar recargarlo demasiado. Además, las variables al declararse fuera, podrían ser reutilizadas por otros procedimientos o funciones. He aquí una de las ventajas de reutilizanción de código que evita la "duplicación" de código que es otro punto negativo de la programación. La duplicación resulta en excesiva, recargada, exhausativa e innecesaria.
Deseo hacer una puntualización. Esta estrategia de distribuir las variables no es la única y ni la absoluta. Ud., puede poner en práctica otras técnicas y, por lo tanto, lo invito a pensar en ello. Seguro que encontrará más de una solución para este caso. Aquí planteo una de las tantas posibilidades.
Bien continuando con nuestro acometido, quizá lo más interesante ocurre con los dos procedimientos que se han agregado en esta última actualización. Como notará, parte del código que estaba originalmente en el primer código, ha sido extraido y ensamblado en otros dos procesos aparte. Un proceso ejerce el cálculo para la calculadora de plazo fijo o financiera y, el otro procedimiento, se encarga de limpiar y resetear a los valores iniciales dicha calculadora.
El procedimiento encargado de limpiar y resetear la calculadora llamado controlLimpiarValores(), le ha permitido al código original ahorrarse de unas cuantas líneas de código. Además, volviendo al código original, ¿qué ocurriría si Ud., debería actualizar algún dato? Por ejemplo, que la taza TNA sea 7% y que el Plazo sea de 12 días. Ud., debía actualizar estos valores en cada una de las líneas escritas relacionadas, lo cual y sin duda alguna, será más trabajo y con mayores riesgos a comenter errores u omisiones. Esto podría tener como resultado final severas consecuencias.
En cambio, al agrupar en un proceso todas estas líneas, no solamente que ha ahorrado código y líneas escritas, sino que a su vez, ha facilitado la mantenibilidad. Si tiene que hacer algún cambio o actualización, bastará entonces operar directamente dentro de dicho proceso. Esto es a lo que me refiero hacer foco a un proceso y abstraerse del resto del código. Esta situación foco y abstracción, resultaría algo compleja de entender dentro del gran proceso en el código original.
El procedimiento que realiza el cálculo financiero llamado controlCalcular(). Aquí también podemos facilitar la mantenibilidad. Si observa de cerca el procedimiento, notará que cualquier cambio que desee realizar en esta sección, resulta en más legible, fácil de comprender y extrictamente dinámica.
Refinando Aún Más Nuestro Código
Analizando en detalles de forma más profunda, podemos refinar aún más nuestro código. Siguiendo el lineamiento de "atomización" y por otro lado "optimización", encontraremos que al procedimiento que realiza el cálculo financiero llamado controlCalcular() podemos optimizarlo aún más. Para ello, deseo que observe esta última actualización. Aquí vuelvo a volcar todo el código nuevamente con la actualización.
Public Class Form1
Private inversion As Decimal = 0.0
Private tna As Decimal = 0
Private plazo As Integer = 0
Private residual As Decimal = 0.0
Private capitalFinal As Decimal = 0.0
Private inversion As Decimal = 0.0
Private tna As Decimal = 0
Private plazo As Integer = 0
Private residual As Decimal = 0.0
Private capitalFinal As Decimal = 0.0
Private Shared Function controlFormula(ByVal setInversion As Decimal, _
ByVal setPlazo As Integer, _
ByVal setTNA As Decimal) As Decimal
Return (setInversion * setPlazo * (setTNA / 12)) / (365 * 100)
End Sub
Return (setInversion * setPlazo * (setTNA / 12)) / (365 * 100)
End Sub
Private Sub controlCalcular()
inversion = TextBox1.Text
tna = TextBox2.Text
plazo = TextBox3.Text
residual = controlFormula(inversion, plazo, tna)
capitalFinal = inversion + residual
TextBox4.Text = residual
TextBox5.Text = capitalFinal
End Sub
Private Sub controlLimpiarValores()
TextBox1.Text = 0
TextBox2.Text = 8.5
TextBox3.Text = 30
TextBox4.Text = 0
TextBox5.Text = 0
End Sub
Private Sub controlButtons(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles Button1.Click, Button2.Click
If sender.Equals(Button1) Then
Try
controlCalcular()
Catch ex As Exception
MsgBox("Valor numérico no válido")
controlLimpiarValores()
End Try
End If
If sender.Equals(Button2) Then
controlLimpiarValores()
End If
End Sub
End Class
inversion = TextBox1.Text
tna = TextBox2.Text
plazo = TextBox3.Text
residual = controlFormula(inversion, plazo, tna)
capitalFinal = inversion + residual
TextBox4.Text = residual
TextBox5.Text = capitalFinal
End Sub
Private Sub controlLimpiarValores()
TextBox1.Text = 0
TextBox2.Text = 8.5
TextBox3.Text = 30
TextBox4.Text = 0
TextBox5.Text = 0
End Sub
Private Sub controlButtons(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles Button1.Click, Button2.Click
If sender.Equals(Button1) Then
Try
controlCalcular()
Catch ex As Exception
MsgBox("Valor numérico no válido")
controlLimpiarValores()
End Try
End If
If sender.Equals(Button2) Then
controlLimpiarValores()
End If
End Sub
End Class
La última modificación he Refactoreado el procedimiento controlCalcular(). Para ello extraje la sección donde figura la fórmula que permite hallar la inversión del capital en función al tiempo y el interés nominal anual TNA. Estos parámetros pueden variar entre países. En la República de Argentina, es regida por el Banco Central de la República de Argentina BCRA. Para cálculos de interés, véase reglas de Intereses (Wikipedia).
Volviendo a nuestro acometido, como decía, he creado una nueva función para controlar el cálculo agrupando una línea de dicha sección en una función homogenea la que he llamado con el nombre de controlFormula(parámetros...). La función simplemente se encarga de realizar el cálculo y transferir el valor resultado a una variable, para que luego, sea calculada como resultado final.
Es probable que Ud., esté pensando ¿cuál es el beneficio de hacer esto? La respuesta es "rendimiento". Voy a justificarlo para que lo entienda mejor. Por empezar, las funciones resultan ser más seguras y rápidas. Han sido creadas para realizar diversos procesos exhaustivos que los procedimientos de por si no soportan. Uno de los procesos que exigen de una performance adecuada son las recursiones. En efecto, las funciones soportan perfectamente estas exigencias y son adecuadas para procesos donde los cálculos y las recursiones son frecuentes en sistemas que demandan dichos procesos de forma masiva.
Por ejemplo, imagínese una librería la cual es utilizada por muchos sistemas a la vez. Imagine ahora que esta función de cálculo es utilizada en un sitio dinámico Web. No hace falta pensar demasiado que esta función estaría sometida a un nivel de exigencia bastante importante. Es más, hasta en una Intranet Corporativa, donde existe un sistema de contabilidad que requiere el uso de sus servicios, también estaría en parte sometida a peticiones de diversos clientes en dicha red. En consecuencia, las funciones son adecuadas para estas situaciones. Mejor aún, si esta función es construida y compilada en una librería aparte, la cual, se pueda acceder a ella a través de clases y, de esta forma, reutilizarla en las soluciones diversas Desktop, Front-End, Servicios para el Servidor, etc.
Otro concepto importante de la separación del código y, más precisamente, en el uso de funciones separadas para procesos más rápidos o ágiles, es la posibilidad de utilizar Thread "Hilos o Hebras" para facilitar la programación paralela. Esto mejoría la performance aún más, aunque es un tema muy complejo para analizar aquí en este apartado, debido a que hay que tener conocimientos sólidos de Hebras o Hilos. No obstante, aquí tiene otra posibilidad más para aplicar Refactory para su mejoramiento y optimización de su código.
Métricas Para Este Ejemplo
A continuación muestro una serie de tablas que muestran las métricas de este ejemplo. Pese a que es un desarrollo muy pequeño y sencillo, es probable que algunos valores no muestren grandes rasgos característicos de rendimiento, pero al menos, nos darán una idea de los avances que se producen entre estos y las consecuencias que tendrían en la práctica sin duda alguna.
Tabla de Ingeniería para el Modelo Original
Mantenibilidad
|
67
|
Ciclo de Complejidad
|
45
|
Profundidad de Herencia
|
7
|
Acoplamiento de Clases
|
28
|
Líneas de Código
|
185
|
Tabla de Ingeniería del Último Refactoreo
Mantenibilidad
|
69
|
Ciclo de Complejidad
|
48
|
Profundidad de Herencia
|
7
|
Acoplamiento de Clases
|
28
|
Líneas de Código
|
183
|
Como podrá apreciar en las tablas, existe una marcada diferencia entre el factor de Mantenibilidad, Ciclo de Complejidad y la cantidad de Líneas de Código de toda la aplicación. Explico brevemente cada uno de ellos para entender el resultado y cerrar esta primera parte del Blog.
El valor de Mantenibilidad, según Microsoft, debe ser lo más alto posible y oscila entre el número 20 y 100. Por tanto, valores menores a este la mantenibilidad resulta crítica. En nuestro caso, hemos subido de 67 a 69, por tanto, esto nos demuestra que la última actualización ha resultado mejor para su mantención.
El Ciclo de Complejidad hace referencia al diseño y distribución de los algoritmos. Esta lectura podría preocuparlo, pero en realidad es sencillo de entender. Por empezar, la complejidad puede incidir en materia de pruebas dado que resulta en más compleja o requiere de más análisis, Sin embargo, por otro lado, contamos con baja mantenibilidad. Por ende, es un índice que atañe a la complejidad analítica y no al grado de mantención. Para nuestra aplicación, indica un valor interesante y poco influente. Eso si, podría resultar crítico e interesante para otros análisis, pero sencillamente, escaparían de este tema que he planteado en este Blog.
La Profundidad de Herencia atañe al volumen de descendencia de las clases intervinientes en un proyecto o aplicación. No es este el caso. Aquí no hay motivo para analizar el Deeping "profundidad" y, por tanto, no hablaré de ello. Lo mismo es para Acomplamiento que son temas que escapan de este apartado. Sin embargo, es probable que los cite en la Parte II de Refactory porque si son muy importantes, en especial, este último. El factor de acoplamiento es clave en el diseño de Software e Ingeniería.
Por último, tenemos el número de líneas que en esta oportunidad he logrado un ahorro de dos líneas para todo el proyecto. Esto que parece un logro pobre, no lo es desde el punto de vista de costes y performance, como citaré más adeleante en otros apartados. Aquí en esta pequeña aplicación, no podemos notar grandes diferencias como señale, pero solo piense en un gran proyecto. La diferencia podría ser más que importante.
Otro dato que quiero señalar. El ahorro de líneas no siempre puede lograrse en un ciento por ciento. Recuérdese que es un parámetro al que se tiende lograr y, en consecuencia, de no hacerlo no implica que el desarrollo sea crítico.
Continuará en otra sección, Parte II.
Muchas Gracias por su Atención.
Job Systems Solutions
No hay comentarios:
Publicar un comentario