Refactoreo y Reutilización del Código
En el siguiente estudio vamos a ver cómo utilizar Refactoreo y la Reutilización de código para mejorar la construcción del software. En esta oportunidad, se trata de una simple aplicación útil en matemática geométrica, que permite calcular las áreas de algunas figuras geométricas.
Bien, a continuación, vuelco el código de construcción de esta aplicación. Más adelante de estos párrafos, iremos optimizando esta aplicación. Aplicaremos refactoreo y la reutilización de código para mejorar dicha aplicación.
Public Class Form2
Private Sub controlBotones(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles Button1.Click, Button2.Click, Button3.Click, Button3.Click, _
Button4.Click, Button5.Click, Button6.Click
Dim area As Double = 0.0
Dim base As Double = 0.0
Dim altura As Double = 0.0
Dim radio As Double = 0.0
If sender.Equals(Button1) Then
Try
base = TextBox1.Text
altura = TextBox2.Text
area = (base * altura) / 2
TextBox3.Text = area
Catch ex As Exception
TextBox1.Text = 0
TextBox2.Text = 0
TextBox3.Text = 0
End Try
End If
If sender.Equals(Button2) Then
Try
base = TextBox4.Text
area = base * base
TextBox5.Text = area
Catch ex As Exception
TextBox4.Text = 0
TextBox5.Text = 0
End Try
End If
If sender.Equals(Button3) Then
Try
base = TextBox8.Text
altura = TextBox7.Text
area = base * altura
TextBox6.Text = area
Catch ex As Exception
TextBox6.Text = 0
TextBox7.Text = 0
TextBox8.Text = 0
End Try
End If
If sender.Equals(Button4) Then
Try
radio = TextBox10.Text
area = Math.PI * radio * radio
TextBox9.Text = area
Catch ex As Exception
TextBox9.Text = 0
TextBox10.Text = 0
End Try
End If
If sender.Equals(Button5) Then
Try
radio = TextBox12.Text
area = 4 * Math.PI * radio * radio
TextBox11.Text = area
Catch ex As Exception
TextBox11.Text = 0
TextBox12.Text = 0
End Try
End If
If sender.Equals(Button6) Then
TextBox1.Text = 0
TextBox2.Text = 0
TextBox3.Text = 0
TextBox4.Text = 0
TextBox5.Text = 0
TextBox6.Text = 0
TextBox7.Text = 0
TextBox8.Text = 0
TextBox9.Text = 0
TextBox10.Text = 0
TextBox11.Text = 0
TextBox12.Text = 0
End If
End Sub
End Class
Antes de comenzar a plantear el refactoreo, primero hay que idealizar una estrategia de distribución, a los efectos de normalizar el proyecto. Una normalización permite estandarizar algunos aspectos de contacto con el software. De esta forma, cuando distribuimos parte del código refactoreado, los distintos trozos de código se coloquen donde se corresponda, respetando ciertos criterios primarios. Por ejemplo, todo aquello que atañe a la interfaz gráfica, conviene que esté dentro de la clase del formulario o, en todo caso, utilizar una clase de apoyo para el formulario. En nuestro caso, utilizaremos al propio formulario si los elementos optimizados hacen referencia a la interfaz gráfica. A continuación, vuelco el código por partes.
El refactoreo lo he planteado en dos áreas distintas. Un área, lo que concierne a la interfaz gráfica. En esa sección, he diseñado una Interfaz de modo de que implmente los métodos de limpieza para cada tipo de figura geométrica más un limpiador general de pantalla. A continuación, vuelco la interfaz.
Sub limpiarGeneral()
Sub limpiarTriangulo()
Sub limpiarCuadrado()
Sub limpiarRectangulo()
Sub limpiarCirculo()
Sub limpiarEsfera()
End Interface
En el siguiente código, vuelco el resto de las modificaciones que plantea el refactoreo y la reutilización del código sobre el formulario principal de la aplicación.
Public Class Form2
Implements IClear
Public Sub limpiarGeneral() Implements IClear.limpiarGeneral
limpiarTriangulo()
limpiarCuadrado()
limpiarRectangulo()
limpiarCirculo()
limpiarEsfera()
End Sub
Public Sub limpiarCirculo() Implements IClear.limpiarCirculo
TextBox9.Text = 0
TextBox10.Text = 0
End Sub
Public Sub limpiarCuadrado() Implements IClear.limpiarCuadrado
TextBox4.Text = 0
TextBox5.Text = 0
End Sub
Public Sub limpiarEsfera() Implements IClear.limpiarEsfera
TextBox11.Text = 0
TextBox12.Text = 0
End Sub
Public Sub limpiarRectangulo() Implements IClear.limpiarRectangulo
TextBox6.Text = 0
TextBox7.Text = 0
TextBox8.Text = 0
End Sub
Public Sub limpiarTriangulo() Implements IClear.limpiarTriangulo
TextBox1.Text = 0
TextBox2.Text = 0
TextBox3.Text = 0
End Sub
Private Sub controlBotones(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles Button1.Click, Button2.Click, Button3.Click, Button3.Click, _
Button4.Click, Button5.Click, Button6.Click
If sender.Equals(Button1) Then
Try
Dim triangulito = New Triangulo(TextBox1.Text, TextBox2.Text)
TextBox3.Text = triangulito.getGettingArea()
Catch ex As Exception
limpiarTriangulo()
End Try
End If
If sender.Equals(Button2) Then
Try
Dim cuadradito = New Cuadrado(TextBox4.Text)
TextBox5.Text = cuadradito.getGettingArea()
Catch ex As Exception
limpiarCuadrado()
End Try
End If
If sender.Equals(Button3) Then
Try
Dim rectangulito = New Rectangulo(TextBox8.Text, TextBox7.Text)
TextBox6.Text = rectangulito.getGettingArea()
Catch ex As Exception
limpiarRectangulo()
End Try
End If
If sender.Equals(Button4) Then
Try
Dim circulito = New Circulo(TextBox10.Text)
TextBox9.Text = circulito.getGettingArea()
Catch ex As Exception
limpiarCirculo()
End Try
End If
If sender.Equals(Button5) Then
Try
Dim esferita = New Esfera(TextBox12.Text)
TextBox11.Text = esferita.getGettingArea()
Catch ex As Exception
limpiarEsfera()
End Try
End If
If sender.Equals(Button6) Then
limpiarGeneral()
End If
End Sub
End Class
Ahora, veremos el resto de las clases e interfaz que he diseñado para soportar este proyecto. Al final, haré todos los comentarios al respecto. El siguiente pequeño código es la interfaz llamada IGeometria que implementará en todas las clases una función en común llamada getGettingArea() que es utilizada en cada uno de los cálculos geométricos.
Public Interface IGeometria
Function getGettingArea() As Double
End Interface
Ahora, la siguiente clase llamada MathBase, nos permite construir la constante PI que la he personalizado para justificar esta aplicación más todos los campos encapsulados. Obsérvese que he realizado la encapsulación de modo similar a como se hace en lenguajes tales como Java, C++ o C# respectivamente. Esto es para facilitar una posible migración de código hacia estos tipos de lenguajes mencionados.
Public Const miPI As Double = 3.1415926535897931
Private _area As Double
Private _base As Double
Private _altura As Double
Private _radio As Double
Public Sub setArea(ByVal value As Double)
_area = value
End Sub
Public Function getArea() As Double
Return _area
End Function
Public Sub setBase(ByVal value As Double)
_base = value
End Sub
Public Function getBase() As Double
Return _base
End Function
Public Sub setAltura(ByVal value As Double)
_altura = value
End Sub
Public Function getAltura() As Double
Return _altura
End Function
Public Sub setRadio(ByVal value As Double)
_radio = value
End Sub
Public Function getRadio() As Double
Return _radio
End Function
End Class
A continuación, cada una de las clases según el tipo de figura geométrica a la que se desea calcular particularmente.
Public Class Triangulo
Inherits MathBase
Implements IGeometria
Sub New()
'Vacío obligatorio
End Sub
Sub New(ByVal _base As Double, ByVal _altura As Double)
setBase(_base)
setAltura(_altura)
getGettingArea()
End Sub
Public Function getGettingArea() As Double Implements IGeometria.getGettingArea
Return (getBase() * getAltura()) / 2
End Function
End Class
Inherits MathBase
Implements IGeometria
Sub New()
'Vacío obligatorio
End Sub
Sub New(ByVal _base As Double, ByVal _altura As Double)
setBase(_base)
setAltura(_altura)
getGettingArea()
End Sub
Public Function getGettingArea() As Double Implements IGeometria.getGettingArea
Return (getBase() * getAltura()) / 2
End Function
End Class
Public Class Cuadrado
Inherits MathBase
Implements IGeometria
Sub New()
'Vacío obligatorio
End Sub
Sub New(ByVal _base As Double)
setBase(_base)
getGettingArea()
End Sub
Public Function getGettingArea() As Double Implements IGeometria.getGettingArea
Return getBase() * getBase()
End Function
End Class
Inherits MathBase
Implements IGeometria
Sub New()
'Vacío obligatorio
End Sub
Sub New(ByVal _base As Double)
setBase(_base)
getGettingArea()
End Sub
Public Function getGettingArea() As Double Implements IGeometria.getGettingArea
Return getBase() * getBase()
End Function
End Class
Public Class Rectangulo
Inherits MathBase
Implements IGeometria
Sub New()
'Vacío obligatorio
End Sub
Sub New(ByVal _base As Double, ByVal _altura As Double)
setBase(_base)
setAltura(_altura)
getGettingArea()
End Sub
Public Function getGettingArea() As Double Implements IGeometria.getGettingArea
Return getBase() * getAltura()
End Function
End Class
Inherits MathBase
Implements IGeometria
Sub New()
'Vacío obligatorio
End Sub
Sub New(ByVal _base As Double, ByVal _altura As Double)
setBase(_base)
setAltura(_altura)
getGettingArea()
End Sub
Public Function getGettingArea() As Double Implements IGeometria.getGettingArea
Return getBase() * getAltura()
End Function
End Class
Public Class Circulo
Inherits MathBase
Implements IGeometria
Sub New()
'Vacío obligatorio
End Sub
Sub New(ByVal _radio As Double)
setRadio(_radio)
getGettingArea()
End Sub
Public Function getGettingArea() As Double Implements IGeometria.getGettingArea
Return miPI * getRadio() * getRadio()
End Function
End Class
Inherits MathBase
Implements IGeometria
Sub New()
'Vacío obligatorio
End Sub
Sub New(ByVal _radio As Double)
setRadio(_radio)
getGettingArea()
End Sub
Public Function getGettingArea() As Double Implements IGeometria.getGettingArea
Return miPI * getRadio() * getRadio()
End Function
End Class
Inherits MathBase
Implements IGeometria
Sub New()
'Vacío obligatorio
End Sub
Sub New(ByVal _radio As Double)
setRadio(_radio)
getGettingArea()
End Sub
Public Function getGettingArea() As Double Implements IGeometria.getGettingArea
Return 4 * miPI * getRadio() * getRadio()
End Function
End Class
Seguramente estará tentado en incriminarme que esto no es un refactoreo y ni mucho menos una forma fácil de diseñar el software de esta aplicación. Quizá en parte le de la razón si lo analizamos desde el punto de vista complejidad. No obstante, quiero recordarle que la optimización del código y la mantenibilidad, son más fuertes y valen más que la complejidad a la hora de probar el rendimiento del programa. Es más, no lo digo yo por que se me ocurre, sino que si se dignara a leer todo el vademecum de recomendaciones que las normas ISO hacen sobre el diseño y construcción de software, seguramente me daría la razón. No repito como un loro estas normas, porque no tienen sentido práctico. En lugar de ello, prefiero realizar una práctica de modo de que esté al alcance de todos y, por su puesto, cada uno pueda sacar sus propias conclusiones.
Volviendo a nuestro acometido, verá que he dividido al programa en varias áreas específicas. Vamos a ir por partes de forma de poder comprender mejor cada uno de estos módulos.
Como señale antes, la interfaz llamada IGeometria la he utilizado para que me pueda facilitar la implementación de una función que resulta ser común en todos los cálculos operativos de área. Además de facilitar esta implementación, contribuye a delinear una normalización conveniente. La normalización me permite desarrollar el código bajo un patrón estable y legible. Otro aspecto interesante de las interfaces es que evitan potenciales errores de escalación del software. Es decir, con el uso de interfaces, se evita que se desvíen las normalizaciones, lo que podrían ocacionar diversos inconvenientes operativos y de escala. En otras palabras, permite limitar y poner un patrón de crecimiento del código. Para concluir, gracias a esta técnica que me ofrece la interfaz IGeometria, he facilitado el polimorfismo de forma satisfactoria.
La clase la que he llamado con el nombre de MathBase me proporciona los Setters & Getters o los campos de propiedades para manipular los valores de las variables para cada elemento de cálculo en cada uno de las operaciones. Luego, esta clase es heredada en cada una de las clases de las figuras geométricas.
Nótese que de no haber incorporado una clase como esta, tendría que haber duplicado el código en cada una de las clases de las figuras geométricas. Aquí hubiera tenido un serio problema de duplicación y, sin duda alguna, una cierta cantidad de líneas bastante considerables. Gracias a la clase MathBase, he ahorrado bastante código.
Por último, cada una de las clases que operan según la figura geométrica requerida, tiene su implementación de la interfaz IGeometria y heredan de la clase MathBase los Setters & Getters o propiedades de campos.
He optado el desarrollo de una clase por cada figura geométrica por una sencilla razón. Primero, de haber utilizado una sola clase, me vería obligado a crear funciones individuales por cada tipo de cálculo de figura geométrica. Por tanto, no pudiera utilizar interfaces y mucho menos encontrar una normalización adecuada si no se sacrifica sobreescribir o corregir la gran clase. Observe que el patrón de normalización no podía ser el adecuado bajo un escenario de esas características. Creo que no era lo correcto. Segundo, al diseñar clases totalmente individuales permiten escalar sin que esto altere el resto de las clases.
Supongamos que nos piden calcular otras cosas más. Por ejemplo, nos piden que calculemos Casquetes, Husos y Zona esféricas. De haber utilizado una sola clase, esta misma se llenaría de más funciones y tendería a la maraña y confusión. Además, desde el punto de vista matemático, estaría casi mal visto. Es más, desde el sentido común, también lo estaría. Por tanto, no es adecuado un tipo de organización semejante.
En cambio, como tenemos cada una de las clases individualizadas, bastará con tomar la clase Esfera y proceder a ampliarla según estas necesidades pertinentes. Puede construir una interfaz exclusiva para Esferas. Sin embargo, resulta demás dado que estas ampliaciones son exclusivas de la clase Esfera y, por tanto, no es necesario forzar una normalización estricta. Mal o bien, estas funciones estarían dentro de un conjunto de tipos de figuras geométricas que son adecuadas. A continuación, muestro un mapa de las clases y cómo han quedado. Al final, haremos un análisis de las métricas y otros estudios básicos de ingeniería.
Analizando Métricas y Rendimiento - Conclusiones
La hora a llegado y es tiempo de verificar el comportamiento de esta aplicación y su rendimiento final. A continuación, vuelco las dos tablas comparativas que nos darán el veredicto.
Tabla para el Código Original
Mantenibilidad
|
67
|
Ciclo de Complejidad
|
97
|
Profundidad de Herencia
|
7
|
Acoplamiento de Clases
|
26
|
Líneas de Código
|
420
|
Tabla para el Código Refactoreado
Mantenibilidad
|
87
|
Ciclo de Complejidad
|
134
|
Profundidad de Herencia
|
7
|
Acoplamiento de Clases
|
34
|
Líneas de Código
|
453
|
Los resultado son más que óptimos. Sin embargo, en algunos puntos del análisis, podemos encontrar algunas diferencias a favor y encontra. No cabe la menos duda que el valor de mantenibilidad es más que alto. Es excelente y por tanto este último desarrollo nos permite una mejor facilidad de mantenibilidad que el código original pese a que este mismo muestra una aparente código simple. En el último desarrollo, ha aumentado el grado de complejidad. Resulta evidente debido a que se han agregado muchas etapas al modelo como factoreo. La profundidad de herencia se ha mantenido exactamente igual. El acomplamiento de las clases ha aumentado y, como es lógico, es debido a la extensión realizada por el factoreo. Por último, han aumentado la cantidad de líneas.
Resulta una lástima que estos medidores de rendimiento no hagan énfasis al factor de escalabilidad. La escalabilidad es muy importante porque permite realizar un crecimiento exponencial de un software. Si la escala no es diseñada de forma adecuada o, al menos, no se tiene encuenta, los resultados podrían ser críticos. Por ejemplo, no podría imaginarme que escala puede tener el primer código. Comparando a ambos, el último refactoreo ofrece un mayor valor de escala que el modelo original. en consecuncia, el último refactoreo de código nos permite ampliar nuestra aplicación sin sufrir limitaciones o entuertos tal como, seguramente lo hará, el código original de esta aplicación.
Factor de Acoplamiento
El factor de acoplamiento puede afectar en determinados casos y en otros pueden resultar ser aceptables, tal es el caso de nuestra aplicación. Ingeniería recomienda valores de acoplamiento lo más bajo posible. El acoplamiento está basado en el proceso límite que permite separar módulos sistémicos de forma de hacerlos más manipulables, reutilizables y extensibles. La arquitectura del software evalúa mucho estos puntos valuativos dado que, en el caso de tecnologías que comparten entornos de sistemas en común, pueden resultar críticos. Por ejemplo, en un ambiente donde existen un gran compromiso en la convergencia de varios sistemas que comparten un medio.
Piense en el acoplamiento como en las etapas que hay de entre medio de varias secciones para comunicar un objeto con otro. Ambos objetos dependen de la fiabilidad entre estos. Si el canal que los une es crítico, en consecuencia directa, la comunicación entre ambos objetos resultará crítica también. Por ejemplo, una base de datos Oracle, una librería para manipular las conexiones, etc., en C# y una capa de negocio intermedia con una capa de usuario en Visual Basic .NET. Aquí hay tres acoplamientos bien claros. Cada uno ofrece una propuesta y el factor de acoplamiento debe ser efectivo. Si por alguna razón, se utiliza una librería escrito en Java, suponiendo que fuera compatible, el factor de acoplamiento deberá ser óptimo. Para ello, la cantidad de capas que son utilizadas para garantizar la comunicación y la adaptación "impedancia baja" deben procurar ser lo más eficaces posibles. Por tanto, existe un enormes compromisos para favorecer altos factores de performance para los acomplamientos.
Continúa en la Parte III
Continúa en la Parte III
Muchas Gracias por su Atención.
Job Systems Solutions
No hay comentarios:
Publicar un comentario