jueves, 16 de marzo de 2017

Refactory - Refactoreo del Software - Parte II

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.

Public Interface IClear
    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 Class MathBase

    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

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

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

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

Public Class Esfera
    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

Muchas Gracias por su Atención.
Job Systems Solutions

No hay comentarios:

Publicar un comentario