Mostrando entradas con la etiqueta Reglas de la Ingeniera de Software. Mostrar todas las entradas
Mostrando entradas con la etiqueta Reglas de la Ingeniera de Software. Mostrar todas las entradas

domingo, 2 de octubre de 2022

Reglas de la Ingeniería de software (índice)


Cuando comencé este blog, lo hice con la intención de facilitar el trabajo de las personas que se dedican al desarrollo de software, de forma profesional. Creo que existen muchos blogs y libros tecnológicos que hablan desde el desarrollo de software desde un punto de vista académico y teórico, pero muy pocos que lo atacan desde un punto de vista laboral (sobre todo en español).

Desde hace bastantes años, tengo ciertas labores de “tutelaje”, intentando ayudar a nuevos profesionales, a que vean el panorama completo desarrollo de software empresarial. Con base a eso, comencé a elaborar una serie de “Reglas de la Ingeniería de Software”, puntos cuya lectura nos ayudarían a hacer software de calidad en un tiempo razonable. Se analizaba todo los que nos llevaba al éxito y los que nos llevaba al fracaso (y creedme he estado en ambos tipos de proyectos), y se intentaba sintetizar en un punto fácil de recordar.


Las reglas son de diversa índole, por que abarcan el desarrollo empresarial de software en general. Así que se puede encontrar reglas que hagan referencia a aspectos técnicos de los mismos lenguajes de programación, pero también a aspectos como la gestión del tiempo, la responsabilidad, o la relación entre los integrantes de un equipo de desarrollo.

Algunas reglas pueden parece que están repetidas, esto porque hay una constante que se da en el desarrollo empresarial, que es el cambio rápido y constante en el negocio que abarcan los sistemas, los que provoca cambios en los mismos sistemas, a sus capacidades y sus necesidades, forzándonos a que sea simple y fácil de mantener. Muchas reglas girar en torno a esas ideas, aunque enfocadas cada una desde un punto de vista diferente, aunque parecido.

Por otro lado este es un proyecto vivo, en el que se agregaran, corregirán (y quitaran si es necesario), las reglas que sean necesarias, para mantenerlas coherente y útiles para la vida laboral del desarrollador de software.

Las reglas actuales son las siguientes:

  • Regla N°1: Va a cambiar

    31 de agosto de 2014


    Todo cambia muy rápido en el proceso de desarrollo de un software, el mismo software construido, genera nuevos requisitos, la satisfacción de las necesidades de un proyecto, genera a su vez nuevas necesidades.

    Explicado de otra forma la resolución de un problema, genera un entendimiento más claro de dicho problema, y en esta situación, se abren nuevas posibilidades sobre las capacidades del software y el negocio que sustenta.

  • Regla N°2: Va a fallar

    6 de septiembre de 2014


    Es imposible determinar si cierto software continuara funcionando en un futuro, porque las condiciones en las que se verá envuelto son impredecibles (actualizaciones de software con el que convive, actualizaciones de hardware… incluso la fecha y hora del ordenador).

    Hay que evitar actitudes optimistas con respecto a la posibilidad que ocurran fallos, puesto que es muy probable que estos ocurran, por otro lado considerar que el usuario del sistema hará un uso “lógico” o coherente de este, es un desatino, generalmente nos sorprenderá descubriendo errores, que nunca se nos hubieran pasado por la cabeza.

  • Regla N°3: Se va a mantener

    15 de septiembre de 2014


    El tiempo que vas a estar creando software nuevo, es incomparablemente menor al tiempo que vas a estar manteniéndolo.

    Gran parte nuestros esfuerzos de desarrollo van a estar enfocados en el mantenimiento de código que bien puede ser código ajeno o código de otras personas.

  • Regla N°4: Todo sistema tiene un propósito

    21 de septiembre de 2014


    Es común que olvidemos que lo que estamos creando no es un conjunto de variables, y líneas de código, sino que tiene que un significado y relevancia especial fuera del lenguaje de programación en el que los estamos creando. Por ejemplo, una trasferencia bancaria de un millón de dólares errónea no es una posición de memoria mal inicializada, sino que puede ser una pérdida real, o un sistema médico de monitores mal construido puede poner en juego vidas humanas. Hay que pensar en nuestros sistemas por lo que van a poner aportar en un negocio o los problemas que van a poder resolver y no como simples entes tecnológicos.

  • Regla N°5: No te repitas

    28 de septiembre de 2014


    La regla "No te repitas", hace referencia a que no se deben repetir porciones de código, con funcionalidades iguales, "casi iguales", o parecidas a los largo del código. Aunque técnicamente funcione y de el resultado correcto, aumenta el grado de "degeneración del software" (el tiempo en que se echa a perder un software, hasta que ya no es útil su funcionalidad).

  • Regla N° 6: Lo importante es el "Que hace" y no el "Como lo hace"

    16 de octubre de 2014


    En la programación orientada a objetos, se diseñan elementos, donde prima la representación de "Que es lo que hace" y "Que son", principalmente, a través de sus atributos y sus métodos. también se establece como se relacionan con otros tipos de elementos (o clases), ya sea mediante técnicas como la composición, la herencia, o el envió y recepción de mensajes.

    De esta forma centrándonos en "Que hace" obtenemos una comprensión mas clara del sistema, y de sus requisitos, además construiremos un sistemas más escalable (debido a que no nos estamos centrando en "Como lo hace"). Inclusive tendremos muchas más posibilidades de conectarnos a otros sistemas, puesto que queda mas claramente establecidos los limites y conectores de estos.

  • Regla N°7: Primero los objetos, después las base de datos

    2 de enero de 2015


    El enfoque más idóneo para comenzar un sistema, es el diseño de los objetos o las clases de los que se va a constituir nuestros sistemas. Generalmente al comienzo del diseño, tenemos una idea de cómo queremos hacer las cosas, dicha idea no tiene que estar completa, y cuanto más avancemos en ella, mas compresión tendremos sobre la misma. Podemos ir reduciendo el nivel de abstracción según los necesitemos. Igualmente podemos usar relaciones de objetos, ya sea de jerarquía (herencia) o de contención (un objeto contiene a otro, o a varios). Y lo más importante, se comprende que los datos, van de la mano de las operaciones (funcionalidad), y se diseña el sistema de dicha forma.

  • Regla N°8: El entusiasmo da el conocimiento, el conocimiento sin entusiasmo no sirve de nada

    19 de julio de 2015


    El conocimiento sin entusiasmo es, en el mejor de los casos, fortuito, y casi nunca podrá convertirse en algo útil para el negocio, en sistemas y herramientas funcionalidades que creen una diferencia significativa. En la mayoría de los casos lo único que podrá realizarse sin entusiasmo es lo que yo llamo “tareas de escuela”, programas técnicamente correctos, hechos según un enunciado claro como una receta, pero que no sirve en la práctica de absolutamente nada, que son imposibles de encajar en un ambiente laboral real. En última instancia los conocimientos, y más en esta profesión, son caducos.

  • Regla N°9: Los warnings de hoy son los errores de mañana (programa sin warning)

    8 de agosto de 2015


    Cuando me enfrento al mantenimiento de código realizado por diversos equipos de trabajo (y he de reconocer que no con menos frecuencia al creado por mi), me encuentro con que al compilar, existe un excesivo número de advertencias (excesivo son más de 200 o incluso mas). Advertencias que se han ido creando a lo largo del tiempo y que nadie se ha molestado en revisar jamás, acumulándose más y más cuanto más codificadores mueven el sistema. Generalmente nadie toca dichos warning debido al temor que entraña "mover" algo cuyo sentido desconocemos (el hecho que lo desconozcamos ya debiera hacer saltar la alarma). Bueno, si existe el warning, seguramente no tenga ningún significado oculto y simplemente es una omisión no intencional que deba ser corregida.

    Los warnings se acumulan a lo largo de los años y el número dificulta entender claramente lo que nos están indicando, cuanto más warnings haya, menos casos les haremos, hasta que uno de esos warnings realmente representen un potencial problema en producción que se nos pase completamente desapercibido, convirtiéndose en un dolor de cabeza (con suerte) o en algo más grave, porque "los warnings de ahora son los errores de mañana".

  • Regla N°10: Si no es sencillo está mal hecho

    1 de diciembre de 2015


    El desarrollo de un software tiende a complicarse… a complicarse mucho. Se complica sacar el proyecto a tiempo, se complica las relaciones con el equipo de trabajo, con los proveedores o con los recursos. Debemos mantener esa complejidad bajo control, teniendo en la mente que todo lo que hagamos cumplir la regla "Si no es sencillo, está mal hecho".

    La sencillez muchas veces se confunde. Sencillez no quiere decir fácil, es casi siempre lo contrario. Es difícil hacer las cosas sencillas. Casi siempre invertir en sencillez es un proceso costoso al principio y ventajoso al final del proyecto. El objetivo de esta regla es conseguir sistemas que estén guiados por la sencillez.

  • Regla N°11: El primer código es para entender el problema, los restantes para hacerlo bien

    5 de febrero de 2017


    Como defensor del las metodologías agiles, creo que la mejor forma de comprender un problema es descomponiéndolo en problemas más sencillos y solucionándolos uno a uno.

    Esto implica que hasta que no tengamos algo de código listo, no comprenderemos exactamente la necesidad que estamos intentando suplir. Es más, el ver la necesidad resulta, pueda cambiar los requisitos y la percepción sobre el problema real a tratar.

    Los motivos anteriores hacen que el primer código no sea el más optimo, porque su objetivo está más orientado al análisis que a la implementación. La situación en este punto es tenemos un código que nos muestra una solución, pero es una solución temporal y estática (Si hubiera un cambio en los requisitos sería muy difícil de ajustar estar código para atenderlo).

    Es por lo cual necesitamos realizar una segunda revisión del código, en estas caso ya no para resolver el problema de negocio, sino para asegurarnos que nuestro código sea de calidad, cumpliendo los siguientes requisitos:

  • Regla N°12: “Hay que prepararse para la lluvia cuando hace sol” (olvídate de “Si funciona no lo toques”)

    16 de agosto de 2017


    Si funciona no lo toques”, es un refrán bastante popular y además es horrible, promueve una filosofía de conformismo e incluso de cobardía ante el cambio. Me imagino que funcionaria en algún momento, donde tu zona de confort sea enorme. En la actualidad, y particularmente en nuestro negocio, las zonas de confort tienen a hacerse muy pequeñas muy rápidamente.

    ¿Cuándo es el momento de “tocar” algo que funciona?, si crees que es “cuando no funcione”, estas en un error, ese es el peor momento para plantearte cambios. Por la misma circunstancia que “no funciona”, el objetivo, o la prioridad ya no es crear ningún tipo de mejora, si no conseguir que el sistema siga funcionando.

  • Regla N°13: Simplifica las cosas

    10 de noviembre de 2017


    Cuanto te enfrentes a un problema grande, simplifícalo en problemas más pequeños. Si estos problemas siguen siendo demasiado grandes repite el proceso hasta que tengas un problema lo suficientemente pequeño para poderlo resolver.

    Para simplificar partimos siempre de un problema grande que es irresoluble en su tamaño actual, e identificamos las partes que lo componen. Lo dividimos en pequeños problemas que son más fáciles de resolver, repitiendo el proceso hasta que podémonos identificar dichos problemas con una solución clara y sencilla, y además que solo sirva para resolver exclusivamente una necesidad.

  • Regla N°14: Evita la ceguera voluntaria

    16 de diciembre de 2017


    La ceguera voluntaria es ignorar de forma deliberada situaciones que si bien son perjudiciales, no suponen un daño inmediato, con lo que no se hace nada para corregirlas, convirtiéndose en una posible situación catastrófica a futuro.

    Cuando aparecen problemas en mitad de un desarrollo y no son atacados y mitigados cuando son pequeños, aumentaran de forma progresiva hasta ser de tal tamaño que afecte al tiempo, calidad o costo del proyecto.

  • Regla N°15: Haz que las cosas sucedan

    11 de enero de 2018


    Una de las cosas que más afectan al éxito del desarrollo de un sistema es la pasividad y falta de proactividad. Esto es, principalmente, quedarse esperando a que las cosas pasen por si solas.

    Muchas veces existen escenarios de incertidumbre en que no queda claro que es lo tenemos que hacer en cuanto al sistema que se está construyendo, esto puede ser por muchos motivos, puede ser una falta de visibilidad del objetivo real o que no se nos ha comunicado apropiadamente.

    Pero es necesario comprender que mientras el sistema no esté finalizado (satisfaga las necesidades del cliente), hay tareas pendientes que hay que resolver y es responsabilidad de cada integrante del equipo conocer cuáles son y conseguir se lleve a cabo. Es responsabilidad de cada uno hacer que las cosas sucedan.

  • Regla N°16: Por defecto todo es privado (principio de ocultación)

    25 de enero de 2018


    He observado un problema muy común de muchos programadores noveles, escriben la palabra "public" por inercia al momento de crear una nueva funcionalidad, sin pararse realmente a pensar que están haciendo, y solo cuando lo meditan bien escriben "privado". A veces no se dan cuenta de esta circunstancia, otra veces piensan que es más sencillo, si todo es público, a veces incluso piensan que le dan cierto sentido de "libertad", cuando lo que se hace abrir puerta al desastre (la “libertad” la da en todo caso, compartir el código fuente, no habilitar métodos privados como públicos).

  • Regla N°17: Primero el camino principal luego las excepciones

    23 de diciembre de 2018


    Si es bien es cierto que uno se tiene que prepara para los fallos (para que el software falle) la ruta principal (de ejecución) debe ser un camino limpio y fácilmente trazable. Incluso desde el momento del diseño, no podemos dejarnos abrumar con todas las excepciones y giros posibles de la lógica de negocio, debemos diseñar el camino feliz, y asumir que habrá excepciones en algún momento. Esto es porque si bien el camino principal suele ser sencillo, los recovecos, bifurcaciones y excepciones suelen ser muchas, muy variadas y a veces difíciles de ver, si el cambio principal y los secundarios están entrelazados obtendremos un código sumamente difícil de comprender.

  • Regla N°18: Mejor herencia que sentencias condicionales (evita el código espagueti)

    4 de febrero de 2019


    El código Espagueti es aquel que tiene un control de flujo demasiado complicado para entenderlo claramente, además de que son prácticamente imposibles de modificar por miedo a que mientras cambiamos algo se estropee otra cosa. El código esta enredado tal como un plato de espagueti, se llega a esta situación (en la actualidad), a base de agregar sentencias if encadenas, grandes y complejas. Ya de por sí, tener dos if encadenados aumenta la complejidad del código y la dificultad de mantenerlo.

    Para evitar que nuestro programa contenga código espagueti debemos evitar usar instrucciones condicionales, cuando estas pueden evitarse usando mecanismos de herencia, "Mejor herencia que condicionales".

  • Regla N°19: Enfoque en el mantenimiento y la escalabilidad antes que en la optimización

    22 de agosto de 2020


    Primero haga que funcione, después hágalo rápido. Esto es cierto incluso si se está seguro de que una trozo de código es realmente importante y se sabe que será un cuello de botella es el sistema. No lo haga. Primero, consiga que el sistema tenga un diseño lo más simple posible. Entonces, si no es suficientemente rápido, optimícelo. Casi siempre descubrirá que «su» cuello de botella no es elproblema. Guarde su tiempo para lo verdaderamente importante. Bruce Eckel, Thinking in C++

    La mantenibilidad son las modificaciones que hacemos a un sistema para que siga funcionando con el propósito para el cual fue diseñado (para resolver la necesidad de el negocio para que fue creado). La escalabilidad es la facilidad con la que podemos agregar nueva funcionalidad a un software para que se adapte a las nuevas necesidades de un mercado y para que nuestro negocio no se quede estancando y obsoleto.

  • Regla Nº20: No depender del llamador, ni de la implementación (evitar acoplamientos)

    13 de marzo de 2021


    Una clase no debe cambiar su comportamiento según quien la consume, igualmente el consumidor de una clase no debiera saber cómo esta implementada internamente dicha clase, para poder consumirla de forma adecuada.

    Idealmente una clase solo se debiera relacionar con otras a través de la definición de los métodos, el cómo hagan esos métodos sus funciones con cosas que nos deben preocupar. Lo anterior se ve claramente cuando podemos sustituir la referencia de una clase, por la referencia a una interface, y cambiar la clase que lo implementan sin mucho (o ningún) impacto.



Si te ha gustado el artículo, compártelo en tus redes sociales ;-)

sábado, 13 de marzo de 2021

Regla Nº20 No depender del llamador, ni de la implementación (evitar acoplamientos)

Frecuéntenme, en nuestra vida personal y laboral, cambiamos nuestro comportamiento y actitud según con quien estemos hablando con la finalidad de poder conseguir y resolver las necesidades o circunstancias que se nos plantean. No es lo mismo (o no diera) que un doctor este dando una clase sobre alguna enfermedad que le este comunicando dicha enfermedad a un paciente, ni tampoco es igual como hablamos con nuestros padres o hijos a como hablamos con nuestros compañeros de trabajo. Por otro lado tampoco recibimos la información de la misma forma de personas que apreciamos y conocemos que de desconocidos, “modulamos” nuestro comportamiento según con quien estemos hablamos.

Ese punto de vista, tan normal en interacciones humanas, supone un problema a la hora de implementar software.

Una clase no debe cambiar su comportamiento según quien la consume, igualmente el consumidor de una clase no debiera saber cómo esta implementada internamente dicha clase, para poder consumirla de forma adecuada.

Estos días he tenido que dar mantenimiento a un sistema, un sistema que es consumido por otros, bastante viejo, ¡pero no tanto como para los problemas que me he encontrado!


Había que agregar un nuevo comportamiento, hasta ahí todo normal, como ya hemos hablado en varias circunstancias (Regla N°1: Va a cambiar), el software se modifica contantemente para adaptarse a negocio cambiante. ¡El problema es que básicamente toda la lógica de negocio estaba en una misma función, con multitud de if anidados  y ramificaciones! (Regla N°18: evita el código espagueti). No solo era difícil de entender, si no extremadamente delicado de modificar, cada vez que se moviera algo, podría afectar de cualquier forma a los consumidores, para colmo el sistema reacciona de forma diferente si lo llama un sistema o si lo llama otro, es decir la clases tiene que reconocer que quien le está llamando, para saber cómo debe comportarse.

 Lo anterior resume un gran problema de acoplamiento y falta de cohesión, lo que está ligado directamente a la dificulta de mantenimiento del software (lo que a su vez es ligado a costos económicos, tiempo, y muy posibles desastres productivos)

Acoplamiento


El acoplamiento entre dos clases es una medida que indica cuanto depende una clase de otra, específicamente cuando depende de su implementación interna. Cuando mas acoplamiento, peor es nuestro código, cuando mas desacoplado este, mas fácil de mantener.

Idealmente una clase solo se debiera relacionar con otras a través de la definición de los métodos, el cómo hagan esos métodos sus funciones con cosas que nos deben preocupar. Lo anterior se ve claramente cuando podemos sustituir la referencia de una clase, por la referencia a una interface, y cambiar la clase que lo implementan sin mucho (o ningún) impacto.

Si podemos cambiar una clase consumida por otra, sin realizar cambios en el consumidor, hemos conseguido un nivel aceptable de desacoplamiento.

Con respecto a no depender de la “implementación interna de una clase”, se dan varios escenarios que demuestran un alto acoplamiento:

  • Cuando la clase consumidora debe almacenar información que no le sirve de nada, salvo para consumir otra clase. Aquí es donde se ve un defecto de diseño, puesto que debiera ser la clase consumida la que debe contener sus propios datos, sin forzar a los consumidores a hacerlo.

  • Las clases se comunican de forma estrafalaria y poco comprensible, por ejemplo se usan cadenas de texto, para comunicar cosas que claramente no lo son, como números, fechas u objetos. El ejemplo de la fecha es muy común, se pasa una fecha en formato texto, pero hay muchas formas de expresar una fecha por texto, no es lo mismo el 1 de Febrero (01/02/2021), que el 2 de Enero  (02/01/2021), pero según el formato de la fecha podría ser una u otra. La única forma correcta de pasar una fecha es mediante un objeto de tipo fecha.

  • Los métodos de una clase deben llamarse en un orden determinado para que funcionen, si no dan resultados inesperados, cada métodos debe hacer algo en concreto, y debe hacerlo independiente en el orden en lo que se les llama, si no puede hacerlo por algún motivo, debe lanzar una excepción (es mejor no hacer algo que hacerlo mal).

    Una situación reciente que me acaba de pasar, tenía que usar un control grafico de terceros, un datagrid para mostrar tabla en pantalla (en una aplicación de escritorio). Una vez mostrada la información en pantalla había que cambiar el valor de una celda en particular, pero por algún motivo extraño, la celda tenía que estar visible, si no ignoraba la instrucción para cambiar el valor (sin producir ningún fallo), así que la solución fue posicional visualmente el componente en la fila requerida y cambiar el valor, y después volver a la fila que estaba el usuario actualmente (que pudiera ser otra), pero esta daba otro problema como un parpadeo innecesario en el programa, eso se solucionaba desactivando el control antes de realizar la operación. Al final el programa consumidor, se lleno de instrucciones que hay que ejecutar antes (y después) de cambiar el valor de una celda, y no se comprende muy bien porque es así, y desde luego si se cambia el componente todo ese código esta de mas.

  • La clase consumida cambia su comportamiento según quien lo está consumiendo, ese es el acoplamiento más rocambolesco de todos. Es decir si la clase A, y B consumen la clase C,  la C cambia su funcionalidad según quien la este llamando.

    Para comenzar, de por si suena muy extraño. Una clase debe tener una misión sea cual sea, debe ser clara y concisa y realizarla de la misma forma independientemente de quien le este llamando.

    Que modifique su comportamiento según el llamador, suena a que la clase no tiene un diseño bueno orientado a objetos, donde se usa una serie de estructuras condicionales gigantescas, en lugar de usar herencia y polimorfismo.

    Cuando se modifica la clase consumida, se puede afectar a la clase consumidora obligándola también a  ser modificada, si hay más de un sistema que consuma dicha clase, habrá que cambiar todos y cada uno de ellos con un consumo en tiempo y recursos. Si además los sistema a modificar son consumidos por otros sistemas, pueden desencadenas una cadenas de modificaciones enorme y costosa.

    Es posible, no obstante, que debamos modificar el acceso a la funcionalidad en cuestión de permisos, es decir dichas componente tendrá acceso a alguna funcionalidad y otra no. Un ejemplo muy típico de esto es cuando las aplicaciones se logean con facebook y nos muestra un mensaje que nos dice a que tendrá acceso la pagina web donde nos logeamos, puede ser al perfil público y a nuestro correo, otra quieren acceder a nuestra lista de contactos y otra incluso a publicar en nuestro nombre. Pudiera parecer que se está creando un acoplamiento en el que facebook devuelve información según la aplicación que se lo pida pero no cierto. Hay una capa de seguridad adicional que se encarga de generar un “token” con ciertos permisos hacia facebook y la aplicación web consumidora usara este token, pero en ningún caso facebook lo hará por que la pagina web que lo consume sea una o la otra, sino porque se solicito unos permisos y se trabaja con la autorización previa de con dichos permisos.

  • Una medida del acoplamiento de una clase es la cantidad de clases adicionales que consume. Puede ser que parezca que no dependa de la implementación interna de cada uno de los componentes que consume pero cuando una clase consume muchas clases, comienza a perder su identidad y a depender de elementos externos para realizar su tarea,  lo que al final repercute en un alto acoplamiento. En una aplicación empresarial se supone que una clase solo debe consumir las clases que estén en un nivel más por debajo que el suyo con lo que sería bastante raro que consumiera muchas clases.


Cohesión


La cohesión posiblemente sea unos de los conceptos que son más difíciles de explicar y comprender acertadamente de la ingeniera de software.

A veces se explica simplemente como que todo lo que pertenezca a mismo ámbito o tenga que ver entre sí debe estar junto. Así agrupamos los métodos en clases, las clases en namespace (o paquetes), los namespace en componentes y los componentes en sistemas.

Pero lo anterior es solo organización, agrupar las cosas según su semejanza y naturaleza, para que sea más sencillo encontrarlas y usarlas pero no define el nivel de cohesión como tal.

  • Una clase se debe componer de datos, representados por atributos (o propiedades) y por funcionalidad que usa esos datos. ¿Qué tantos atributos debe usar cada función o métodos?, en principio debiera usar todos, ya que los atributos es lo que representa el estado del objeto y cada función debiera cambiarlo (por que debiera hacer algo con el objeto), en la medida que use mas métodos y mas atributos de la clase, la misma clase esta mas cohesionada.

  • Si en una clase, una  colección de métodos usa unos atributos y otra colección de métodos usan otros atributos diferentes entonces la clase no tiene cohesión y realmente son dos clases que se han convertido en una sola, debiéndose separar. Si esto lo tenemos varias veces, lo que tenemos es lo que se llama una “clase gorda”, una clase con muchas cosas dentro sin nada que ver entre sí y que posiblemente solo sea una colección de métodos, que no son parte de una objeto con identidad. Posiblemente las  clase tenga más de un motivo para cambiar y dichos motivos no tengan nada que ver entre sí (lo que hace que no se cumpla el principio de “responsabilidad única”).

  • De igual manera si una clase consume en exceso a otra clase, y sobretodo usa sus atributos (a atreves de sus propiedades o métodos) de forma constante pareciendo que no tiene ningún sentido una clase sin la otra, estaríamos en un caso de “clase famélica”, y posiblemente debiera ser una sola.

  • Lo anterior vuelve a pasar con los namespaces (o paquetes). Estos deben componerse de clases que se consumen o se relacionan entre sí (por medio de mecanismos de (herencia, agregación, composición, colección u otros). También debe evitar incluir clases que no se usan de ningún modo. Esto hacen que las clases sea cohesionadas y tengan un objetivo, fácilmente identificable.

  • Los namespaces se agrupan en componentes. Los componentes igualmente tiene un objetivo común y identificable, pero es una agrupación “física”, en forma de elemento reusable como una librería, un jar de java o un servicio.

  • Los sistemas son agrupaciones de uno o más componentes que interactúan entre sí para resolver una serie de problemas de negocio a través de flujos e interacciones con el usuario.


La cohesión nos ayuda a que los problemas que tenemos que resolver sean pequeños, conexos y fácilmente escalables.

Relación entre cohesión y acoplamiento


Cohesión y acoplamiento son dos valores tan relacionados que siempre se van a encontrar juntos.
  • Si aumenta la cohesión disminuye el acoplamiento.

    Si tenemos juntos los elementos que si deben estar juntos, el acoplamiento debiera disminuir porque la organización de los mimos elementos nos lleva a consumir solo los que necesitamos, con cierto control y estructura.

  • Si disminuye la cohesión aumente el acoplamiento.

    Lo contrario de “tener todo en un lugar”, es “tenerlo todo junto”. Si tenemos todas las clases a la mano, es normal que comencemos a usar clases simplemente porque están accesibles, mezclando cosas que poco o nada tiene que ver, y eso aumentaría el acoplamiento

  • Si disminuye el acoplamiento aumenta la cohesión.

    Si diseñamos nuestro código disminuyendo la cantidad de clases que consumimos, no dependemos de su implementación interna y nos comunicamos a través de interfaces, quiere decir que tenemos un nivel de organización y agrupación lo suficientemente algo.

  • Si aumenta el acoplamiento disminuye la cohesión.

    Si nuestra clase comienza a consumir clases de diversa índole y sin ningún control ni estructura, es forzosamente necesaria tenerlas accesibles y tenerlas accesibles le fuerza a que tenerlas lo mas “cerca” posible, disminuyendo considerablemente la cohesión.




Si te ha gustado la entrada, ¡Compártela! ;-)

Reglas de la Ingeniería de software (índice): https://desdelashorasextras.blogspot.com/2019/12/reglas-de-la-ingenieria-de-software_15.html

sábado, 22 de agosto de 2020

Regla N°19 de la Ingeniería de Software: Enfoque en el mantenimiento y la escalabilidad antes que en la optimización

Primero haga que funcione, después hágalo rápido. Esto es cierto incluso si se está seguro de que una trozo de código es realmente importante y se sabe que será un cuello de botella es el sistema. No lo haga. Primero, consiga que el sistema tenga un diseño lo más simple posible. Entonces, si no es suficientemente rápido, optimícelo. Casi siempre descubrirá que «su» cuello de botella no es elproblema. Guarde su tiempo para lo verdaderamente importante. Bruce Eckel, Thinking in C++

Recuerdo un desarrollo donde el responsable (que no era codificador) quería optimizar al máximo la velocidad de una aplicación, era una obsesión para él. Así que se entro en una restructuración del código tras otra, con tal de obtener un poco mas de velocidad. Al final el resultado fue que se gano aproximadamente unas pocas horas de procesamiento por cada año de operación, lo que en un escenario real, viene a ser nada, por otro lado se obtuvo un código fuente completamente incomprensible y extremadamente difícil de modificar. Cuando llegaron los inevitables cambios, cada cosa que se movía era un problema nuevo, cada pequeño cambio un desastre inesperado, incluso al final el sistema comenzó a ser extremadamente lento. Al final el sistema se descarto, porque era inusable e inmantenible.

Dos términos que definen la calidad del software son la escalabilidad y la mantenibilidad, a veces son usados para referirnos al mismo aspecto del software, a que pueda “cambiar” con facilidad, y sin problemas.

  • La mantenibilidad son las modificaciones que hacemos a un sistema para que siga funcionando con el propósito para el cual fue diseñado (para resolver la necesidad de el negocio para que fue creado)
  • La escalabilidad es la facilidad con la que podemos agregar nueva funcionalidad a un software para que se adapte a las nuevas necesidades de un mercado y para que nuestro negocio no se quede estancando y obsoleto.

Hay que considerar dos cosas, y más en estos precisos momentos:

  • Todos los negocios al competir entre ellos evolucionan, cambian constantemente para adaptarse a las necesidades de sus clientes (o crear necesidades nuevas).
  • Todos estos negocios se sustentan, de una forma u otra en software que los hacen posibles.

Si el software no es capaz de adaptarse para sostener los continuos cambios de un negocio, va a fallar como sistemas y posiblemente falle el negocio siendo reemplazado por una empresa que si pueda suplir las necesidades del consumidor.

En nuestra vida profesional, vamos a estar más tiempo manteniendo software que creando software de cero. La idea de que se crea un software y nunca se va a tocar, porque “funciona bien a la primera”, es una falacia.

Aquí es donde es fundamental, que el software sea mantenible, si invertimos poco, arquitectónicamente hablando, en la construcción del software, invertiremos mucho en su mantenimiento, y viceversa. Consideran que si vamos a pasar más tiempo mantenimiento un sistema que creando, parece lógico, que debemos optar por abaratar el costo en el mantenimiento.

Volviendo al tema de la velocidad de los sistemas. La preocupación por la velocidad del software, es una “piedra en el zapato”, que a acompañado al desarrollo del software desde sus inicios. El hardware era extremadamente lento (y caro), y había que hacer software rápido, aunque sea sacrificando su propia mantenibilidad (con lo que también hacíamos software caro). Esta idea del de la necesidad de software rápido se incrusto como un paradigma inmutable en la mente de los programadores, aunque ni siquiera tuvieran que haber vivido la lentitud de un hardware tan extremo como en sus inicios.

Así parece que de forma sistemática, cada vez que ser creaba un lenguaje de programación más abstracto con el hardware, se le criticaba de forma inevitable por su lentitud. “C es más lento que ASM”, “C++ es más lento que C”, “Java es más lento que C++”, y así seguirá en un futuro, seguramente.

Las optimizaciones por obtener velocidad, pueden llegar a ser muy confusas, y a veces muy ligadas a situaciones extremadamente particulares (tanto para el software, como para el negocio). Y aquí hay que poner en una balanza varias cosas.

  • ¿Realmente mi software es lento?
  • ¿Realmente necesito que mi software sea más rápido?
  • ¿Cuál es el costo de optimizar el software? O ¿Qué voy a sacrificar para obtener más velocidad?
  • ¿Cuál es el beneficio de la optimizar el software, es real?

Aquí es donde hace sentido, la observación de Bruce Eckel, “Primero haz que funcione, después hágalo rápido”. Asegúrate siempre de programar un software bien construido, que puedas modificar fácilmente.

Un software lento, bien construido, siempre podrás modificarlo para que sea más rápido. Un software rápido mal construido, no vas a poder modificarlo.




Si te ha gustado la entrada, ¡Compártela! ;-)

Reglas de la Ingeniería de software (índice): https://desdelashorasextras.blogspot.com/2019/12/reglas-de-la-ingenieria-de-software_15.html

domingo, 15 de diciembre de 2019

Reglas de la Ingeniería de software (índice)


Cuando comencé este blog, lo hice con la intención de facilitar el trabajo de las personas que se dedican al desarrollo de software, de forma profesional. Creo que existen muchos blogs y libros tecnológicos que hablan desde el desarrollo de software desde un punto de vista académico y teórico, pero muy pocos que lo atacan desde un punto de vista laboral (sobre todo en español).

Desde hace bastantes años, tengo ciertas labores de “tutelaje”, intentando ayudar a nuevos profesionales, a que vean el panorama completo desarrollo de software empresarial. Con base a eso, comencé a elaborar una serie de “Reglas de la Ingeniería de Software”, puntos cuya lectura nos ayudarían a hacer software de calidad en un tiempo razonable. Se analizaba todo los que nos llevaba al éxito y los que nos llevaba al fracaso (y creedme he estado en ambos tipos de proyectos), y se intentaba sintetizar en un punto fácil de recordar.


Las reglas son de diversa índole, por que abarcan el desarrollo empresarial de software en general. Así que se puede encontrar reglas que hagan referencia a aspectos técnicos de los mismos lenguajes de programación, pero también a aspectos como la gestión del tiempo, la responsabilidad, o la relación entre los integrantes de un equipo de desarrollo.

Algunas reglas pueden parece que están repetidas, esto porque hay una constante que se da en el desarrollo empresarial, que es el cambio rápido y constante en el negocio que abarcan los sistemas, los que provoca cambios en los mismos sistemas, a sus capacidades y sus necesidades, forzándonos a que sea simple y fácil de mantener. Muchas reglas girar en torno a esas ideas, aunque enfocadas cada una desde un punto de vista diferente, aunque parecido.

Por otro lado este es un proyecto vivo, en el que se agregaran, corregirán (y quitaran si es necesario), las reglas que sean necesarias, para mantenerlas coherente y útiles para la vida laboral del desarrollador de software.

Las reglas actuales son las siguientes:

  • Regla N°1: Va a cambiar

    31 de agosto de 2014


    Todo cambia muy rápido en el proceso de desarrollo de un software, el mismo software construido, genera nuevos requisitos, la satisfacción de las necesidades de un proyecto, genera a su vez nuevas necesidades.

    Explicado de otra forma la resolución de un problema, genera un entendimiento más claro de dicho problema, y en esta situación, se abren nuevas posibilidades sobre las capacidades del software y el negocio que sustenta.

  • Regla N°2: Va a fallar

    6 de septiembre de 2014


    Es imposible determinar si cierto software continuara funcionando en un futuro, porque las condiciones en las que se verá envuelto son impredecibles (actualizaciones de software con el que convive, actualizaciones de hardware… incluso la fecha y hora del ordenador).

    Hay que evitar actitudes optimistas con respecto a la posibilidad que ocurran fallos, puesto que es muy probable que estos ocurran, por otro lado considerar que el usuario del sistema hará un uso “lógico” o coherente de este, es un desatino, generalmente nos sorprenderá descubriendo errores, que nunca se nos hubieran pasado por la cabeza.

  • Regla N°3: Se va a mantener

    15 de septiembre de 2014


    El tiempo que vas a estar creando software nuevo, es incomparablemente menor al tiempo que vas a estar manteniéndolo.

    Gran parte nuestros esfuerzos de desarrollo van a estar enfocados en el mantenimiento de código que bien puede ser código ajeno o código de otras personas.

  • Regla N°4: Todo sistema tiene un propósito

    21 de septiembre de 2014


    Es común que olvidemos que lo que estamos creando no es un conjunto de variables, y líneas de código, sino que tiene que un significado y relevancia especial fuera del lenguaje de programación en el que los estamos creando. Por ejemplo, una trasferencia bancaria de un millón de dólares errónea no es una posición de memoria mal inicializada, sino que puede ser una pérdida real, o un sistema médico de monitores mal construido puede poner en juego vidas humanas. Hay que pensar en nuestros sistemas por lo que van a poner aportar en un negocio o los problemas que van a poder resolver y no como simples entes tecnológicos.

  • Regla N°5: No te repitas

    28 de septiembre de 2014


    La regla "No te repitas", hace referencia a que no se deben repetir porciones de código, con funcionalidades iguales, "casi iguales", o parecidas a los largo del código. Aunque técnicamente funcione y de el resultado correcto, aumenta el grado de "degeneración del software" (el tiempo en que se echa a perder un software, hasta que ya no es útil su funcionalidad).

  • Regla N° 6: Lo importante es el "Que hace" y no el "Como lo hace"

    16 de octubre de 2014


    En la programación orientada a objetos, se diseñan elementos, donde prima la representación de "Que es lo que hace" y "Que son", principalmente, a través de sus atributos y sus métodos. también se establece como se relacionan con otros tipos de elementos (o clases), ya sea mediante técnicas como la composición, la herencia, o el envió y recepción de mensajes.

    De esta forma centrándonos en "Que hace" obtenemos una comprensión mas clara del sistema, y de sus requisitos, además construiremos un sistemas más escalable (debido a que no nos estamos centrando en "Como lo hace"). Inclusive tendremos muchas más posibilidades de conectarnos a otros sistemas, puesto que queda mas claramente establecidos los limites y conectores de estos.

  • Regla N°7: Primero los objetos, después las base de datos

    2 de enero de 2015


    El enfoque más idóneo para comenzar un sistema, es el diseño de los objetos o las clases de los que se va a constituir nuestros sistemas. Generalmente al comienzo del diseño, tenemos una idea de cómo queremos hacer las cosas, dicha idea no tiene que estar completa, y cuanto más avancemos en ella, mas compresión tendremos sobre la misma. Podemos ir reduciendo el nivel de abstracción según los necesitemos. Igualmente podemos usar relaciones de objetos, ya sea de jerarquía (herencia) o de contención (un objeto contiene a otro, o a varios). Y lo más importante, se comprende que los datos, van de la mano de las operaciones (funcionalidad), y se diseña el sistema de dicha forma.

  • Regla N°8: El entusiasmo da el conocimiento, el conocimiento sin entusiasmo no sirve de nada

    19 de julio de 2015


    El conocimiento sin entusiasmo es, en el mejor de los casos, fortuito, y casi nunca podrá convertirse en algo útil para el negocio, en sistemas y herramientas funcionalidades que creen una diferencia significativa. En la mayoría de los casos lo único que podrá realizarse sin entusiasmo es lo que yo llamo “tareas de escuela”, programas técnicamente correctos, hechos según un enunciado claro como una receta, pero que no sirve en la práctica de absolutamente nada, que son imposibles de encajar en un ambiente laboral real. En última instancia los conocimientos, y más en esta profesión, son caducos.

  • Regla N°9: Los warnings de hoy son los errores de mañana (programa sin warning)

    8 de agosto de 2015


    Cuando me enfrento al mantenimiento de código realizado por diversos equipos de trabajo (y he de reconocer que no con menos frecuencia al creado por mi), me encuentro con que al compilar, existe un excesivo número de advertencias (excesivo son más de 200 o incluso mas). Advertencias que se han ido creando a lo largo del tiempo y que nadie se ha molestado en revisar jamás, acumulándose más y más cuanto más codificadores mueven el sistema. Generalmente nadie toca dichos warning debido al temor que entraña "mover" algo cuyo sentido desconocemos (el hecho que lo desconozcamos ya debiera hacer saltar la alarma). Bueno, si existe el warning, seguramente no tenga ningún significado oculto y simplemente es una omisión no intencional que deba ser corregida.

    Los warnings se acumulan a lo largo de los años y el número dificulta entender claramente lo que nos están indicando, cuanto más warnings haya, menos casos les haremos, hasta que uno de esos warnings realmente representen un potencial problema en producción que se nos pase completamente desapercibido, convirtiéndose en un dolor de cabeza (con suerte) o en algo más grave, porque "los warnings de ahora son los errores de mañana".

  • Regla N°10: Si no es sencillo está mal hecho

    1 de diciembre de 2015


    El desarrollo de un software tiende a complicarse… a complicarse mucho. Se complica sacar el proyecto a tiempo, se complica las relaciones con el equipo de trabajo, con los proveedores o con los recursos. Debemos mantener esa complejidad bajo control, teniendo en la mente que todo lo que hagamos cumplir la regla "Si no es sencillo, está mal hecho".

    La sencillez muchas veces se confunde. Sencillez no quiere decir fácil, es casi siempre lo contrario. Es difícil hacer las cosas sencillas. Casi siempre invertir en sencillez es un proceso costoso al principio y ventajoso al final del proyecto. El objetivo de esta regla es conseguir sistemas que estén guiados por la sencillez.

  • Regla N°11: El primer código es para entender el problema, los restantes para hacerlo bien

    5 de febrero de 2017


    Como defensor del las metodologías agiles, creo que la mejor forma de comprender un problema es descomponiéndolo en problemas más sencillos y solucionándolos uno a uno.

    Esto implica que hasta que no tengamos algo de código listo, no comprenderemos exactamente la necesidad que estamos intentando suplir. Es más, el ver la necesidad resulta, pueda cambiar los requisitos y la percepción sobre el problema real a tratar.

    Los motivos anteriores hacen que el primer código no sea el más optimo, porque su objetivo está más orientado al análisis que a la implementación. La situación en este punto es tenemos un código que nos muestra una solución, pero es una solución temporal y estática (Si hubiera un cambio en los requisitos sería muy difícil de ajustar estar código para atenderlo).

    Es por lo cual necesitamos realizar una segunda revisión del código, en estas caso ya no para resolver el problema de negocio, sino para asegurarnos que nuestro código sea de calidad, cumpliendo los siguientes requisitos:

  • Regla N°12: “Hay que prepararse para la lluvia cuando hace sol” (olvídate de “Si funciona no lo toques”)

    16 de agosto de 2017


    Si funciona no lo toques”, es un refrán bastante popular y además es horrible, promueve una filosofía de conformismo e incluso de cobardía ante el cambio. Me imagino que funcionaria en algún momento, donde tu zona de confort sea enorme. En la actualidad, y particularmente en nuestro negocio, las zonas de confort tienen a hacerse muy pequeñas muy rápidamente.

    ¿Cuándo es el momento de “tocar” algo que funciona?, si crees que es “cuando no funcione”, estas en un error, ese es el peor momento para plantearte cambios. Por la misma circunstancia que “no funciona”, el objetivo, o la prioridad ya no es crear ningún tipo de mejora, si no conseguir que el sistema siga funcionando.

  • Regla N°13: Simplifica las cosas

    10 de noviembre de 2017


    Cuanto te enfrentes a un problema grande, simplifícalo en problemas más pequeños. Si estos problemas siguen siendo demasiado grandes repite el proceso hasta que tengas un problema lo suficientemente pequeño para poderlo resolver.

    Para simplificar partimos siempre de un problema grande que es irresoluble en su tamaño actual, e identificamos las partes que lo componen. Lo dividimos en pequeños problemas que son más fáciles de resolver, repitiendo el proceso hasta que podémonos identificar dichos problemas con una solución clara y sencilla, y además que solo sirva para resolver exclusivamente una necesidad.

  • Regla N°14: Evita la ceguera voluntaria

    16 de diciembre de 2017


    La ceguera voluntaria es ignorar de forma deliberada situaciones que si bien son perjudiciales, no suponen un daño inmediato, con lo que no se hace nada para corregirlas, convirtiéndose en una posible situación catastrófica a futuro.

    Cuando aparecen problemas en mitad de un desarrollo y no son atacados y mitigados cuando son pequeños, aumentaran de forma progresiva hasta ser de tal tamaño que afecte al tiempo, calidad o costo del proyecto.

  • Regla N°15: Haz que las cosas sucedan

    11 de enero de 2018


    Una de las cosas que más afectan al éxito del desarrollo de un sistema es la pasividad y falta de proactividad. Esto es, principalmente, quedarse esperando a que las cosas pasen por si solas.

    Muchas veces existen escenarios de incertidumbre en que no queda claro que es lo tenemos que hacer en cuanto al sistema que se está construyendo, esto puede ser por muchos motivos, puede ser una falta de visibilidad del objetivo real o que no se nos ha comunicado apropiadamente.

    Pero es necesario comprender que mientras el sistema no esté finalizado (satisfaga las necesidades del cliente), hay tareas pendientes que hay que resolver y es responsabilidad de cada integrante del equipo conocer cuáles son y conseguir se lleve a cabo. Es responsabilidad de cada uno hacer que las cosas sucedan.

  • Regla N°16: Por defecto todo es privado (principio de ocultación)

    25 de enero de 2018


    He observado un problema muy común de muchos programadores noveles, escriben la palabra "public" por inercia al momento de crear una nueva funcionalidad, sin pararse realmente a pensar que están haciendo, y solo cuando lo meditan bien escriben "privado". A veces no se dan cuenta de esta circunstancia, otra veces piensan que es más sencillo, si todo es público, a veces incluso piensan que le dan cierto sentido de "libertad", cuando lo que se hace abrir puerta al desastre (la “libertad” la da en todo caso, compartir el código fuente, no habilitar métodos privados como públicos).

  • Regla N°17: Primero el camino principal luego las excepciones

    23 de diciembre de 2018


    Si es bien es cierto que uno se tiene que prepara para los fallos (para que el software falle) la ruta principal (de ejecución) debe ser un camino limpio y fácilmente trazable. Incluso desde el momento del diseño, no podemos dejarnos abrumar con todas las excepciones y giros posibles de la lógica de negocio, debemos diseñar el camino feliz, y asumir que habrá excepciones en algún momento. Esto es porque si bien el camino principal suele ser sencillo, los recovecos, bifurcaciones y excepciones suelen ser muchas, muy variadas y a veces difíciles de ver, si el cambio principal y los secundarios están entrelazados obtendremos un código sumamente difícil de comprender.

  • Regla N°18: Mejor herencia que sentencias condicionales (evita el código espagueti)

    4 de febrero de 2019


    El código Espagueti es aquel que tiene un control de flujo demasiado complicado para entenderlo claramente, además de que son prácticamente imposibles de modificar por miedo a que mientras cambiamos algo se estropee otra cosa. El código esta enredado tal como un plato de espagueti, se llega a esta situación (en la actualidad), a base de agregar sentencias if encadenas, grandes y complejas. Ya de por sí, tener dos if encadenados aumenta la complejidad del código y la dificultad de mantenerlo.

    Para evitar que nuestro programa contenga código espagueti debemos evitar usar instrucciones condicionales, cuando estas pueden evitarse usando mecanismos de herencia, "Mejor herencia que condicionales".




Si te ha gustado la entrada, ¡Compártela! ;-)

lunes, 4 de febrero de 2019

Regla N°18 de la Ingeniería de Software: Mejor herencia que sentencias condicionales (evita el código espagueti)


El primer ordenador que tuve fue un Sinclair QL, era una especie de evolución del famoso ZX Spectrum. En su tiempo fue un ordenador personal poderoso, aunque no tan popular como su antecesor. Desde que iniciaba, como intérprete de comandos y lenguaje principal tenía una versión de BASIC, llamada SuperBASIC. Recuerdo como pasaba días enteros haciendo código en BASIC, para posteriormente imprimirlos en una impresora matricial, tratando de encontrar el bug perdido que no conseguía hallar y buscando alguna forma de recodar que se supone que debieran hacer los programas, a veces era más sencillo tirar todo el código y volver a hacerlo de nuevo. Si bien antes tenía el entusiasmo y el tiempo para hacer un código de cero por no entenderlo, ahora solo me queda el entusiasmo y muy poco tiempo. Aunque estaba muy orgulloso de los mis primeros códigos, he de reconocer, al pasar del tiempo, que eran horribles, tan difíciles de entender, como de modificar.



El código Espagueti es aquel que tiene un control de flujo demasiado complicado para entenderlo claramente, además de que son prácticamente imposibles de modificar por miedo a que mientras cambiamos algo se estropee otra cosa. El código esta enredado tal como un plato de espagueti, se llega a esta situación (en la actualidad), a base de agregar sentencias if encadenas, grandes y complejas. Ya de por sí, tener dos if encadenados aumenta la complejidad del código y la dificultad de mantenerlo.

Primitivamente el código espagueti era causado por los saltos de línea en el código, las famosas instrucciones goto, que ya no son comunes, básicamente por que los lenguajes modernos no la incluyen. La instrucción goto cambia la línea actual de un programa a otra línea, continuando la ejecución desde el punto señalado. Veamos un ejemplo de esto en BASIC primitivo (No estructurado):

Ejemplo sacado e la Wikipedia https://es.wikipedia.org/wiki/BASIC

Ejemplo de SuperBASIC (No estructurado)

  
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
10 INPUT "Cuál es su nombre:"; NN$
20 PRINT "Bienvenido al 'asterisquero' ";NN$
25 PRINT
30 INPUT "con cuántos asteriscos inicia [Cero sale]:"; N
40 IF N<=0 THEN GOTO 200
50 AS$=""
60 FOR I=1 TO N
70 AS$=AS$+"*"
80 NEXT I
90 PRINT "AQUI ESTAN:"; AS$
100 INPUT "Desea más asteriscos:";SN$S
110 IF SN$="" THEN GOTO 100
120 IF SN$<>"S" AND N$<>"s" THEN GOTO 200
130 INPUT "CUANTAS VECES DESEA REPETIRLOS [Cero sale]:"; VECES
140 IF VECES<=0 THEN GOTO 200
150 FOR I=1 TO VECES
160 PRINT AS$;
170 NEXT I
180 PRINT
185 REM A repetir todo el ciclo (comentario)
190 GOTO 25
200 END
 

  
10 INPUT "Cuál es su nombre:"; NN$
20 PRINT "Bienvenido al 'asterisquero' ";NN$
25 PRINT
30 INPUT "con cuántos asteriscos inicia [Cero sale]:"; N
40 IF N<=0 THEN GOTO 200
50 AS$=""
60 FOR I=1 TO N
70 AS$=AS$+"*"
80 NEXT I
90 PRINT "AQUI ESTAN:"; AS$
100 INPUT "Desea más asteriscos:";SN$S
110 IF SN$="" THEN GOTO 100
120 IF SN$<>"S" AND N$<>"s" THEN GOTO 200
130 INPUT "CUANTAS VECES DESEA REPETIRLOS [Cero sale]:"; VECES
140 IF VECES<=0 THEN GOTO 200
150 FOR I=1 TO VECES
160 PRINT AS$;
170 NEXT I
180 PRINT
185 REM A repetir todo el ciclo (comentario)
190 GOTO 25
200 END

Al principio lo que más destaca es la dificulta para leerlo y comprenderlo. Es mas hay que leer todo como un bloque y de arriba abajo. Básicamente la salida del programa es la siguiente:


Cuál es su nombre: Jose Luis
Bienvenido al 'asterisquero' Jose Luis
con cuántos asteriscos inicia [Cero sale]: 5
AQUI ESTAN:*****
Desea más asteriscos: S
CUANTAS VECES DESEA REPETIRLOS [Cero sale]: 5
*************************

Analizándolo, vemos que no tiene ningún tipo de estructura reconocible, simplemente el programa comienza en la línea 10 y llegado un momento, según condiciones, mueve el flujo a la línea corresponda, por ejemplo en la línea 40, si N<=0 salta a la línea 200. El problema de estos saltos es básicamente que no sabemos dónde vamos a acabar sin leer todo el bloque, los saltos pueden ser hacia delante, y hacia atrás en el código (si ningún tipo de restricción), incluso si nos viéramos en la necesidad de agregar nuevas líneas debiéramos tener cuidado en donde las insertamos, pues no sabemos desde donde se van a llamar en nuestro código, y si le va pegar a lo ya establecido. En definitiva es muy difícil tener el control de la secuencia de ejecución una vez que ya se ha establecido.

El mismo código en un BASIC que permite programación estructura es el siguiente:

Ejemplo de SuperBASIC (Eestructurado)

  
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
iTrue = -1        'Flag en Verdadero
INPUT "¿Cuál es su nombre"; NombreUsuario$
PRINT "Bievenido al 'asterisquero',"; NombreUsuario$
DO
  PRINT ""
  INPUT "¿Con cuántos asteriscos inicia [Cero sale]:"; NroAsteriscos
  IF NroAsteriscos<=0 THEN EXIT DO
  Asteriscos$ = ""
  FOR I=1 TO NroAsteriscos
     Asteriscos$=Asteriscos$ + "*"
  NEXT I
  PRINT "AQUI ESTAN: "; Asteriscos$
  DO
     INPUT "Desea más asteriscos:";SN$
  LOOP UNTIL SN$<>""
  IF SN$<>"S" AND SN$<>"s" THEN EXIT DO      'Salida
  INPUT "CUANTAS VECES DESEA REPETIRLOS [Cero sale]:";iVeces
  IF iVeces<=0 THEN EXIT DO    'Salida
  FOR I = 1 TO iVeces
     PRINT Asteriscos$;
  NEXT I
  PRINT
LOOP WHILE iTrue
END
 

  
iTrue = -1        'Flag en Verdadero
INPUT "¿Cuál es su nombre"; NombreUsuario$
PRINT "Bievenido al 'asterisquero',"; NombreUsuario$
DO
  PRINT ""
  INPUT "¿Con cuántos asteriscos inicia [Cero sale]:"; NroAsteriscos
  IF NroAsteriscos<=0 THEN EXIT DO
  Asteriscos$ = ""
  FOR I=1 TO NroAsteriscos
     Asteriscos$=Asteriscos$ + "*"
  NEXT I
  PRINT "AQUI ESTAN: "; Asteriscos$
  DO
     INPUT "Desea más asteriscos:";SN$
  LOOP UNTIL SN$<>""
  IF SN$<>"S" AND SN$<>"s" THEN EXIT DO      'Salida
  INPUT "CUANTAS VECES DESEA REPETIRLOS [Cero sale]:";iVeces
  IF iVeces<=0 THEN EXIT DO    'Salida
  FOR I = 1 TO iVeces
     PRINT Asteriscos$;
  NEXT I
  PRINT
LOOP WHILE iTrue
END

Si nos fijamos, aunque sea por el indentado, es mucho más fácil de leer, también que no tenga números de líneas facilita la tarea.

A simple vista es un bucle DO..WHILE, con varios bucles FOR..NEXT y DO…UNTIL, internos. Incluso en este caso el código sigue algo “espaguetizado”, debido a la repetición de código (que dificulta su mantenimiento y compresión), por ejemplo este código se repite, casi por igual:


  
1
2
3
4
FOR I=1 TO NroAsteriscos
    Asteriscos$=Asteriscos$ + "*"
NEXT I
PRINT "AQUI ESTAN: "; Asteriscos$
 

  
FOR I=1 TO NroAsteriscos
    Asteriscos$=Asteriscos$ + "*"
NEXT I
PRINT "AQUI ESTAN: "; Asteriscos$

Y


  
1
2
3
FOR I = 1 TO iVeces
    PRINT Asteriscos$;
NEXT I
 

  
FOR I = 1 TO iVeces
    PRINT Asteriscos$;
NEXT I

Con todo, hubo gran mejora en el primer código y en el Segundo.

Como comentábamos en la actualidad es muy difícil que un lenguaje moderno tenga una instrucción goto valida. El código espagueti se consigue cuando la combinación de condicionales, y estructura de bloques, se anida, se complica y se extiende demasiado.

Para evitar que nuestro programa contenga código espagueti debemos evitar usar instrucciones condicionales, cuando estas pueden evitarse usando mecanismos de herencia, "Mejor herencia que condicionales".

En los siguientes párrafos estaremos viendo un ejemplo de la "desespaguetización" de un código creado en C#, para Visual Studio.

Se puede descargar el código desde GitHub en:



Un ejemplo actual de código espagueti


Imaginemos el siguiente ejemplo, queremos crear una API que guarda un mensaje (una simple cadena de texto que nos informe de algún suceso). Las opciones para guardar un mensaje son:

  • En base de datos
  • En un archivo de texto plano
  • En el log de eventos de Windows.

En un primer acercamiento ponernos definir una clase de nombre GestorMensajes, con un métodos que se llame Guardar, que recibirá dos parámetros, uno es el cómo va a guardar, dos el mensaje a guardar.



El código de la clase es el siguiente:


  
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/// Guarda el mensaje segun el metodo que se indica como parametro
/// </summary>
/// <param name="modo">Modo para guardar</param>
/// <param name="mensaje">Mensaje a guardar</param>
public void Guardar(ModoGuardado modo, string mensaje)
{
    if (modo == ModoGuardado.ArchivoPlano) //Guardo la información en el archivo de texto
    {
        string directorioArchivo = AppDomain.CurrentDomain.BaseDirectory;
        string rutaArchivo = Path.Combine(directorioArchivo, "bitacora.txt");
 
        File.AppendAllText(rutaArchivo, $"{mensaje}{Environment.NewLine}");
    }
    if (modo == ModoGuardado.BaseDatos) //Guardo la información en la base de datos
    {
        using (EjemploCodigoEspaguetiEntities context = new EjemploCodigoEspaguetiEntities())
        {
            //Si no existe la base de datos (archivo mdf) lo creo
            string directorioBaseDatos = AppDomain.CurrentDomain.BaseDirectory;
            AppDomain.CurrentDomain.SetData("DataDirectory", directorioBaseDatos);
            string rutaBaseDatos = Path.Combine(directorioBaseDatos, @"EjemploCodigoEspagueti.mdf");
 
            context.Database.CreateIfNotExists();
 
            //Agrego el mensaje en la base de datos
            context.Bitacora.Add(new Bitacora { mensaje = mensaje });
            context.SaveChanges();
        }
    }
    else if (modo == ModoGuardado.VisorEventos) //Lo guardo en el Visor de eventos de Windows
    {
        //Si no existe en memoria lo creo
        string source = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
        string log = "Application";
        if (!EventLog.SourceExists(source))
        {
            EventLog.CreateEventSource(source, log);
        }
 
        //Guardo el mensaje.
        System.Diagnostics.EventLog appLog = new System.Diagnostics.EventLog();
        appLog.Source = source;
        appLog.WriteEntry(mensaje);
    }
    else
    {
        throw new InvalidOperationException($"No se reconoce el tipo de guardado {modo}");
    }
}
 

  
/// Guarda el mensaje segun el metodo que se indica como parametro
/// </summary>
/// <param name="modo">Modo para guardar</param>
/// <param name="mensaje">Mensaje a guardar</param>
public void Guardar(ModoGuardado modo, string mensaje)
{
    if (modo == ModoGuardado.ArchivoPlano) //Guardo la información en el archivo de texto
    {
        string directorioArchivo = AppDomain.CurrentDomain.BaseDirectory;
        string rutaArchivo = Path.Combine(directorioArchivo, "bitacora.txt");

        File.AppendAllText(rutaArchivo, $"{mensaje}{Environment.NewLine}");
    }
    if (modo == ModoGuardado.BaseDatos) //Guardo la información en la base de datos
    {
        using (EjemploCodigoEspaguetiEntities context = new EjemploCodigoEspaguetiEntities())
        {
            //Si no existe la base de datos (archivo mdf) lo creo
            string directorioBaseDatos = AppDomain.CurrentDomain.BaseDirectory;
            AppDomain.CurrentDomain.SetData("DataDirectory", directorioBaseDatos);
            string rutaBaseDatos = Path.Combine(directorioBaseDatos, @"EjemploCodigoEspagueti.mdf");

            context.Database.CreateIfNotExists();

            //Agrego el mensaje en la base de datos
            context.Bitacora.Add(new Bitacora { mensaje = mensaje });
            context.SaveChanges();
        }
    }
    else if (modo == ModoGuardado.VisorEventos) //Lo guardo en el Visor de eventos de Windows
    {
        //Si no existe en memoria lo creo
        string source = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
        string log = "Application";
        if (!EventLog.SourceExists(source))
        {
            EventLog.CreateEventSource(source, log);
        }

        //Guardo el mensaje.
        System.Diagnostics.EventLog appLog = new System.Diagnostics.EventLog();
        appLog.Source = source;
        appLog.WriteEntry(mensaje);
    }
    else
    {
        throw new InvalidOperationException($"No se reconoce el tipo de guardado {modo}");
    }
}

Este es un buen ejemplo de código espagueti, vemos como todo esta resuelvo en un único y exclusivo método, allí mediante una serie de if encadenados seleccionamos como tenemos que guardar el mensaje.

¿Cuál es el principal problema del código anterior?, la dificultad del mantenimiento que ofrece el método, que mezcla muchos conceptos agrupados en muy poco espacio, y su falta de escalabilidad, si tuviéramos que agregar una nueva forma de guardar mensajes, debiéramos agregar un nuevo, ify aumentar el tamaño y complejidad del código. Este solo es solo un métodos, pero imaginemos que tenemos un método para consultar mensajes (según se haya guardado) y otro para eliminarnos, nuestra complejidad crecería enormemente.

Simplificación del código espagueti


Mejoremos el código anterior. Lo primero que podemos hacer es separa el método en funciones más pequeñas que solo realizan una tarea, así tendremos una para guardar archivos planos, otra para base de datos y otra para el visor de eventos, adicionalmente separaremos cualquier función necesaria para configurar los destinos del mensaje.

Convertiremos la estructura de if, en un switch, que se lee mas fácilmente:




  
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
/// <summary>
/// Gestor de mensajes, su misión es guardar mensajes de bitacora en diversos medios.
/// </summary>
public class GestorMensajes
{
    /// <summary>
    /// Guarda el mensaje segun el metodo que se indica como parametro
    /// </summary>
    /// <param name="modo">Modo para guardar</param>
    /// <param name="mensaje">Mensaje a guardar</param>
    public void Guardar(ModoGuardado modo, string mensaje)
    {
        switch (modo)
        {
            case ModoGuardado.ArchivoPlano:
                //Guardo la información en el archivo de texto
                GuardarArchivoPlano(mensaje);
                break;
 
            case ModoGuardado.BaseDatos:
                //Guardo la información en la base de datos
                GuardarBaseDatos(mensaje);
                break;
 
            case ModoGuardado.VisorEventos:
                //Lo guardo en el Visor de eventos de Windows
                GuardarVisorEventos(mensaje);
                break;
 
            default:
                throw new InvalidOperationException($"No se reconoce el tipo de guardado {modo}");
        }
    }
 
    /// <summary>
    /// Guarda el mensaje dentro de un archivo plano
    /// </summary>
    /// <param name="mensaje">Mensaje a guardar</param>
    private void GuardarArchivoPlano(string mensaje)
    {
        string directorioArchivo = AppDomain.CurrentDomain.BaseDirectory;
        string rutaArchivo = Path.Combine(directorioArchivo, "bitacora.txt");
 
        File.AppendAllText(rutaArchivo, $"{mensaje}{Environment.NewLine}");
    }
 
    /// <summary>
    /// Configura la base de datos, creandola si no existe
    /// </summary>
    private void ConfigurarBaseDatos()
    {
        using (EjemploCodigoEspaguetiEntities context = new EjemploCodigoEspaguetiEntities())
        {
            //Si no existe la base de datos (archivo mdf) lo creo
            string directorioBaseDatos = AppDomain.CurrentDomain.BaseDirectory;
            AppDomain.CurrentDomain.SetData("DataDirectory", directorioBaseDatos);
            string rutaBaseDatos = Path.Combine(directorioBaseDatos, @"EjemploCodigoEspagueti.mdf");
 
            context.Database.CreateIfNotExists();
        }
    }
 
    /// <summary>
    /// Guarda el mensaje dentro de una base de datos
    /// </summary>
    /// <param name="mensaje">Mensaje a guardar</param>
    private void GuardarBaseDatos(string mensaje)
    {
        using (EjemploCodigoEspaguetiEntities context = new EjemploCodigoEspaguetiEntities())
        {
            ConfigurarBaseDatos();
 
            //Agrego el mensaje en la base de datos
            context.Bitacora.Add(new Bitacora { mensaje = mensaje });
            context.SaveChanges();
        }
    }
 
    /// <summary>
    /// Crea la fuente para el visor de eventos.
    /// </summary>
    private void ConfigurarVisorEventos()
    {
        //Si no existe en memoria lo creo
        string source = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
        string log = "Application";
        if (!EventLog.SourceExists(source))
        {
            EventLog.CreateEventSource(source, log);
        }
    }
 
    /// <summary>
    /// Guarda el mensaje en el visor de eventos de windows
    /// </summary>
    /// <param name="mensaje">Mensaje a guardar</param>
    private void GuardarVisorEventos(string mensaje)
    {
        ConfigurarVisorEventos();
        string source = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
 
        //Guardo el mensaje.
        System.Diagnostics.EventLog appLog = new System.Diagnostics.EventLog();
        appLog.Source = source;
        appLog.WriteEntry(mensaje);
    }
}
 

  
/// <summary>
/// Gestor de mensajes, su misión es guardar mensajes de bitacora en diversos medios.
/// </summary>
public class GestorMensajes
{
    /// <summary>
    /// Guarda el mensaje segun el metodo que se indica como parametro
    /// </summary>
    /// <param name="modo">Modo para guardar</param>
    /// <param name="mensaje">Mensaje a guardar</param>
    public void Guardar(ModoGuardado modo, string mensaje)
    {
        switch (modo)
        {
            case ModoGuardado.ArchivoPlano:
                //Guardo la información en el archivo de texto
                GuardarArchivoPlano(mensaje);
                break;

            case ModoGuardado.BaseDatos:
                //Guardo la información en la base de datos
                GuardarBaseDatos(mensaje);
                break;

            case ModoGuardado.VisorEventos:
                //Lo guardo en el Visor de eventos de Windows
                GuardarVisorEventos(mensaje);
                break;

            default:
                throw new InvalidOperationException($"No se reconoce el tipo de guardado {modo}");
        }
    }

    /// <summary>
    /// Guarda el mensaje dentro de un archivo plano
    /// </summary>
    /// <param name="mensaje">Mensaje a guardar</param>
    private void GuardarArchivoPlano(string mensaje)
    {
        string directorioArchivo = AppDomain.CurrentDomain.BaseDirectory;
        string rutaArchivo = Path.Combine(directorioArchivo, "bitacora.txt");

        File.AppendAllText(rutaArchivo, $"{mensaje}{Environment.NewLine}");
    }

    /// <summary>
    /// Configura la base de datos, creandola si no existe
    /// </summary>
    private void ConfigurarBaseDatos()
    {
        using (EjemploCodigoEspaguetiEntities context = new EjemploCodigoEspaguetiEntities())
        {
            //Si no existe la base de datos (archivo mdf) lo creo
            string directorioBaseDatos = AppDomain.CurrentDomain.BaseDirectory;
            AppDomain.CurrentDomain.SetData("DataDirectory", directorioBaseDatos);
            string rutaBaseDatos = Path.Combine(directorioBaseDatos, @"EjemploCodigoEspagueti.mdf");

            context.Database.CreateIfNotExists();
        }
    }

    /// <summary>
    /// Guarda el mensaje dentro de una base de datos
    /// </summary>
    /// <param name="mensaje">Mensaje a guardar</param>
    private void GuardarBaseDatos(string mensaje)
    {
        using (EjemploCodigoEspaguetiEntities context = new EjemploCodigoEspaguetiEntities())
        {
            ConfigurarBaseDatos();

            //Agrego el mensaje en la base de datos
            context.Bitacora.Add(new Bitacora { mensaje = mensaje });
            context.SaveChanges();
        }
    }

    /// <summary>
    /// Crea la fuente para el visor de eventos.
    /// </summary>
    private void ConfigurarVisorEventos()
    {
        //Si no existe en memoria lo creo
        string source = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
        string log = "Application";
        if (!EventLog.SourceExists(source))
        {
            EventLog.CreateEventSource(source, log);
        }
    }

    /// <summary>
    /// Guarda el mensaje en el visor de eventos de windows
    /// </summary>
    /// <param name="mensaje">Mensaje a guardar</param>
    private void GuardarVisorEventos(string mensaje)
    {
        ConfigurarVisorEventos();
        string source = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;

        //Guardo el mensaje.
        System.Diagnostics.EventLog appLog = new System.Diagnostics.EventLog();
        appLog.Source = source;
        appLog.WriteEntry(mensaje);
    }
}

Las ventajas de esto es que hemos hecho más sencillo el código (aunque hemos necesitado mas código), cada función realiza solo una tarea y son fáciles de modificar. El problema es lo terriblemente poco cohesionado que esta nuestra clase. La cohesión hace referencia a que los elementos que tienen que ver entre sí deben estar juntos y los que no, deben estar separados y ser independientes. En esta clase poco tiene que ver que se guarde un dato en una base de datos, con que se guarde en un archivo. Al disminuir la cohesión, aumentamos el acoplamiento (y viceversa), y todo eso impacta en el manteniendo y escalabilidad de un código.

A la tercera va la vencida (Orientación a objetos)


Cambiaremos la solución a un enfoque basado en objetos, con esto esperamos conseguir:

  • Eliminar el cogido espagueti al evitar usar condicionales de ningún tipo.
  • Aumentar la cohesión (cada clase solo tendrá métodos relacionados entre sí).
  • Se disminuirá el acoplamiento, cada clase podrá ser cambiada e incluso modificada sin afecta al resto del código.

Para ello la clase GestorMensajes, pasara a ser una clase abstracta con un método, también abstracto llamado "Guardar". Es abstracta por que no tiene sentido instanciarla por si misma (siempre se guardar de alguna "forma" y dicha forma no ni tiene que estar especificada directamente en la clase GestorMensajes).


  
01
02
03
04
05
06
07
08
09
10
11
/// <summary>
/// Gestor de mensajes, su misión es guardar mensajes de bitacora en diversos medios.
/// </summary>
public abstract class GestorMensajes : IGestorMensajes
{
    /// <summary>
    /// Guarda el mensaje segun el metodo que se indica como parametro
    /// </summary>
    /// <param name="mensaje">Mensaje a guardar</param>
    public abstract void Guardar(string mensaje);
}
 

  
/// <summary>
/// Gestor de mensajes, su misión es guardar mensajes de bitacora en diversos medios.
/// </summary>
public abstract class GestorMensajes : IGestorMensajes
{
    /// <summary>
    /// Guarda el mensaje segun el metodo que se indica como parametro
    /// </summary>
    /// <param name="mensaje">Mensaje a guardar</param>
    public abstract void Guardar(string mensaje);
}

De la clase GestorMensajes heredaran tres clases; una para guardar en un archivo, otra para base de datos y una final para el Visor de Eventos de Windows. Estas clase solo implementaran las funciones relativas al guardado de su tipo.

Nótese que cada clase, GestorMensajesArchivoPlano, GestorMensajesBaseDatos y GestorMensajes desconocen la existencia de las otras clases, son completamente independientes y pueden modificarse de la manera que nos plazca sin afectar al resto en alguna forma.

Un ejemplo seria la clase de guardar en base de datos:


  
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/// <summary>
/// Gestor de mensajes, su misión es guardar mensajes de bitacora en diversos medios.
/// </summary>
public class GestorMensajesBaseDatos : GestorMensajes
{
    /// <summary>
    /// Contructor por defecto
    /// </summary>
    public GestorMensajesBaseDatos()
    {
        //Inicializa la base de datos
        ConfigurarBaseDatos();
    }
 
    /// <summary>
    /// Configura la base de datos, creandola si no existe
    /// </summary>
    private void ConfigurarBaseDatos()
    {
        using (EjemploCodigoEspaguetiEntities context = new EjemploCodigoEspaguetiEntities())
        {
            //Si no existe la base de datos (archivo mdf) lo creo
            string directorioBaseDatos = AppDomain.CurrentDomain.BaseDirectory;
            AppDomain.CurrentDomain.SetData("DataDirectory", directorioBaseDatos);
            string rutaBaseDatos = Path.Combine(directorioBaseDatos, @"EjemploCodigoEspagueti.mdf");
 
            context.Database.CreateIfNotExists();
        }
    }
 
    /// <summary>
    /// Guarda el mensaje segun el metodo que se indica como parametro
    /// </summary>
    /// <param name="mensaje">Mensaje a guardar</param>
    public override void Guardar(string mensaje)
    {
        using (EjemploCodigoEspaguetiEntities context = new EjemploCodigoEspaguetiEntities())
        {
            //Agrego el mensaje en la base de datos
            context.Bitacora.Add(new Bitacora { mensaje = mensaje });
            context.SaveChanges();
        }
    }
}
 

  
/// <summary>
/// Gestor de mensajes, su misión es guardar mensajes de bitacora en diversos medios.
/// </summary>
public class GestorMensajesBaseDatos : GestorMensajes
{
    /// <summary>
    /// Contructor por defecto
    /// </summary>
    public GestorMensajesBaseDatos()
    {
        //Inicializa la base de datos
        ConfigurarBaseDatos();
    }

    /// <summary>
    /// Configura la base de datos, creandola si no existe
    /// </summary>
    private void ConfigurarBaseDatos()
    {
        using (EjemploCodigoEspaguetiEntities context = new EjemploCodigoEspaguetiEntities())
        {
            //Si no existe la base de datos (archivo mdf) lo creo
            string directorioBaseDatos = AppDomain.CurrentDomain.BaseDirectory;
            AppDomain.CurrentDomain.SetData("DataDirectory", directorioBaseDatos);
            string rutaBaseDatos = Path.Combine(directorioBaseDatos, @"EjemploCodigoEspagueti.mdf");

            context.Database.CreateIfNotExists();
        }
    }

    /// <summary>
    /// Guarda el mensaje segun el metodo que se indica como parametro
    /// </summary>
    /// <param name="mensaje">Mensaje a guardar</param>
    public override void Guardar(string mensaje)
    {
        using (EjemploCodigoEspaguetiEntities context = new EjemploCodigoEspaguetiEntities())
        {
            //Agrego el mensaje en la base de datos
            context.Bitacora.Add(new Bitacora { mensaje = mensaje });
            context.SaveChanges();
        }
    }
}

Adicional a esto declaramos una interfaz de nombre IGestorMensajes, que contendrá un solo método llamado Guardar. Nuestro sistema trabajara solo con la interfaz y no con la clase abstracta. El cuándo usar interfaz o una clase abstracta (o con los dos) en un tema que a veces se torna complicado, pero trabajar con interfaces, que son implementadas por clases abstractas nos da mucha muchas ventajas; por ejemplo al usar nuestro código la interfaz, se puede enfocar solo en los asuntos de negocio que debiera hacer, lo cual reduce el acoplamiento, al usar la clase abstracta, se define los flujos que deben seguir dicha clase y sus hijas, sin entrar en detalles concretos de implementación. Más información en "¿En qué se diferencia las interfaces de las clases abstractas?".


  
01
02
03
04
05
06
07
08
09
10
11
/// <summary>
/// Interfaz de GestorMensaje
/// </summary>
public interface IGestorMensajes
{
    /// <summary>
    /// Guarda el mensaje segun el metodo que se indica como parametro
    /// </summary>
    /// <param name="mensaje">Mensaje a guardar</param>
    void Guardar(string mensaje);
}
 

  
/// <summary>
/// Interfaz de GestorMensaje
/// </summary>
public interface IGestorMensajes
{
    /// <summary>
    /// Guarda el mensaje segun el metodo que se indica como parametro
    /// </summary>
    /// <param name="mensaje">Mensaje a guardar</param>
    void Guardar(string mensaje);
}

Nuestro diagrama quedaría así:



Salta a la vista que según nuestra necesidad debemos usar una clase o otra, lo cual podría se engorroso, sobre todo si se lo dejamos al sistema consumidor.

Para solucionar el problema vamos a delegar la creación de la clase adecuada a otra clase, usando el patrón Factory. Nuestra clase erigirá el método adecuado de guardado de mensajes según una configuración externa (en el archivo app.config asociado a la aplicación).

Esta es la configuración:


  
1
2
3
4
5
<DesdeLasHorasExtras.EjemploCodigoEspagueti3.Properties.Settings>
  <setting name="GestorMensajes" serializeAs="String">
    <value>EjemploCodigoEspagueti3.Mensajes.GestorMensajesArchivoPlano</value>
  </setting>
</DesdeLasHorasExtras.EjemploCodigoEspagueti3.Properties.Settings>
 

  
<DesdeLasHorasExtras.EjemploCodigoEspagueti3.Properties.Settings>
  <setting name="GestorMensajes" serializeAs="String">
    <value>EjemploCodigoEspagueti3.Mensajes.GestorMensajesArchivoPlano</value>
  </setting>
</DesdeLasHorasExtras.EjemploCodigoEspagueti3.Properties.Settings>

Esa es la clase:



  
01
02
03
04
05
06
07
08
09
10
11
12
13
14
/// <summary>
/// Clase que se encarga de crear el objeto de tipo GestorMensajes
/// </summary>
public static class GestorMensajesFactory
{
    /// <summary>
    /// Crea una clase de Gestor Mensajes, segun los indicado en el App.config
    /// </summary>
    /// <returns></returns>
    public static IGestorMensajes Crear()
    {
        return (IGestorMensajes)Assembly.GetExecutingAssembly().CreateInstance(Settings.Default.GestorMensajes);
    }
}
 

  
/// <summary>
/// Clase que se encarga de crear el objeto de tipo GestorMensajes
/// </summary>
public static class GestorMensajesFactory
{
    /// <summary>
    /// Crea una clase de Gestor Mensajes, segun los indicado en el App.config
    /// </summary>
    /// <returns></returns>
    public static IGestorMensajes Crear()
    {
        return (IGestorMensajes)Assembly.GetExecutingAssembly().CreateInstance(Settings.Default.GestorMensajes);
    }
}

Como vemos mediante reflexión, se crea la clase de guardado de mensajes adecuada, nuestro consumidor lo puede usar de la siguiente forma:

//Instancio la clase gestor de mensajes, es la encargada de guardar los mensajes IGestorMensajes gestor = GestorMensajesFactory.Crear(); //Guardo un mensaje de cada tipo. gestor.Guardar("Mensaje de ejemplo.");

Como vemos, y a aunque a primea vista puede parece los contrario, se simplifico el código usando objetos, se crearon varias clases más pequeñas, pero con una sola funcionalidad, se aumento la escalabilidad del sistema, y su cohesión. Para entender por qué esto es importante, conviene revisar la regla "Va a cambiar", la regla "Va a fallar" y la regla "Se va a mantener".

Por último hacer notar que es un ejemplo sencillo, pero si se quiere revisar un buen código que aplica los mismos principios, y con una funcionalidad semejante, recomiendo echar un vistazo al proyecto https://nlog-project.org/, diseñador para guardar logs en distintas fuentes y distintos criterios.