Testing en Paradigma Orientado a Objetos

En la programación estructurada, solo nos preocupamos por llegar de un punto A a un punto B. O en otras palabras, dada una entrada, deseamos obtener una salida específica sin importar el proceso para obtenerla. No importa cuantas veces repitamos el proceso, obtendremos la misma salida para la misma entrada.

En la programación estructurada solo nos importa obtener una salida dada una entrada.
Puede ser una caja negra, sin problemas. Fuente: https://previews.123rf.com/images/onlyblack/onlyblack1811/onlyblack181100119/113271703-draws-a-line-from-a-to-b-male-hand-writing-text-on-white-background-.jpg

Cuando aprendí que el paradigma orientado a objetos, se trataba de generar objetos que se poseyeran parámetros que los definieran, métodos que delinearan su comportamiento y que estos serían necesarios para comunicarse entre objetos a través de mensajes, se me figuró que este paradigma era como tener una mascota.

Lo importante de una mascota no es solo las entradas y salidas ( en una mascota son triviales dichas entradas y salidas, comida y residuos). Sino que también es importante el estado y comportamiento de los objetos, como su interacción con los otros para que el sistema funcione. ( Si a las mascotas las quisieran tan solo por alimentarlas y limpiarles sus residuos, la gente tendría plantas).

Un sistema orientado a objetos, puede definirse como código vivo, como una mascota.
Fuente: https://www.65ymas.com/uploads/s1/65/73/5/bigstock-dog-beagle-having-fun-running-294325987-1.jpeg

De cierta forma, podemos decir que el código generado a través del paradigma orientado a objetos será un código vivo.

Esto nos da ventajas al diseñar nuestros sistemas. A fin de cuentas, el proceso de diseñar entidades y sus interacciones es un proceso ligado íntimamente a la estructura del lenguaje.

Sin embargo, para evaluar una entidad orientada a objetos, es necesario observar el cambio de estado, comportamiento y salidas de los distintos objetos.
En el caso de una mascota, poco caso tendría evaluar si, dada cierta cantidad de comida, obtenemos cierta cantidad de residuos. El análisis del estado de la mascota sería muy limitado y no representa a una mascota ( con una mascota jugamos, la sacamos a pasear, hace travesuras y nos apapacha).

Por lo tanto, se puede observar que, de buenas a primeras, la evaluación de nuestros sistemas orientados a objetos serán un poco más complicados de evaluar.

Implicaciones en el test de aplicaciones orientadas a objetos

En un sistema orientado a objetos, para poder evaluarlo de forma total, tenemos que considerar:

  • Conductas dependientes del estado
  • Encapsulación
  • Herencia
  • Polimorfismo y enlaces dinámicos
  • Clases abstractas y genéricas
  • Manejo de excepciones

Ya no solo debemos tomar en cuenta entradas y salidas . . .

Por ende, la cantidad de tests para evaluar sistemas cambia considerablemente.

El programa OO debe probarse en diferentes niveles para descubrir todos los errores:

A nivel algorítmico, cada módulo (o método) de cada clase en el programa debe probarse de forma aislada.

Como las clases forman la unidad principal del programa orientado a objetos, la prueba de clases es la principal preocupación al probar un programa OO. A nivel de clase, cada clase debe ser probada como una entidad individual.

Después de realizar la prueba a nivel de clase, se deben realizar pruebas a nivel de clúster. Como las clases se colaboran (o integran) para formar un pequeño subsistema (también conocido como clúster), es necesario probar cada clúster individualmente.

En este nivel, la atención se centra en probar los componentes que se ejecutan simultáneamente, así como en la interacción entre clases. Por lo tanto, las pruebas a este nivel pueden verse como pruebas de integración donde las unidades a integrar son clases. Una vez que se prueban todos los clústeres en el sistema, comienza la prueba a nivel del sistema. En este nivel, se prueba la interacción entre grupos.

Test basados en el estado

Los objetos se representan con máquinas de estados, un estado es el conjunto de valores dados para los atributos de un objeto.

Por lo tanto, los test basados en el estado del objeto inspeccionan el estado interior de un objeto, a manera de caja blanca.

Test basado en una máquina de estados finitos

Estos test evalúan el conjunto de estados del objeto y sus transiciones. Se debe tomar en cuenta la llegada a todos los estados, todas las transiciones entre estados, incluso aquellas transiciones no permitidas. Para esto, se debe tener conocimiento profundo de los estados y transiciones de sistema.

Test para evaluación de relaciones de herencia

Uno de los mayores errores es pensar que si reutilizamos código proveniente de una superclase, no sería necesario testear ese código en el contexto de la clase hija.

De hecho, por utilizar mismo código en distintos contextos es que cobra más fuerza la necesidad de testear de nuevo dicho código. Recordemos que, al aplicar conceptos de herencia, aumentan los problemas como:

  • Inicialización incorrecta de atributos de superclase por la subclase
  • Faltan métodos sobreescritos
  • Acceso directo a los campos de superclase desde la subclase
  • Una subclase viola un invariante de la superclase, o crea un estado no válido
  • y más . . .

Para cubrir todos los escenarios generados posibles en el testeo de clases generadas debemos tomar en cuenta que los métodos heredados se deben volver a probar en el contexto de una subclase. Además, si agregamos o cambiamos una subclase, necesitamos volver a probar todos los métodos heredados de una superclase en el contexto de la subclase nueva / modificada.

Las pruebas son una actividad continua durante el desarrollo de software. En los sistemas orientados a objetos, las pruebas abarcan tres niveles, a saber, pruebas unitarias, pruebas de subsistemas y pruebas de sistemas.

Examen de la unidad

En las pruebas unitarias, se evalúan las clases individuales. Se ve si los atributos de clase se implementan según el diseño y si los métodos y las interfaces están libres de errores. Las pruebas unitarias son responsabilidad del ingeniero de aplicaciones que implementa la estructura.

Prueba de subsistema

Esto implica probar un módulo particular o un subsistema y es responsabilidad del líder del subsistema. Implica probar las asociaciones dentro del subsistema, así como la interacción del subsistema con el exterior. Las pruebas del subsistema se pueden usar como pruebas de regresión para cada versión recién lanzada del subsistema.

Prueba de sistema

La prueba del sistema implica probar el sistema en su conjunto y es responsabilidad del equipo de garantía de calidad. El equipo a menudo usa pruebas de sistema como pruebas de regresión cuando ensambla nuevas versiones.

Testeo de excepciones

Para cubrir los escenarios donde se ejecuten procesos causados por el manejo de excepciones, es necesario inyectar en el sistema valores de entrada erroneos los cuales se espere dicho comportamiento anómalo. En estos casos se testea que efectivamente la excepción adecuada sea lanzada o cachada.

Testeo de interacciones

Por lo general, existe la idea errónea de que si las clases individuales están bien diseñadas y han demostrado que funcionan de manera aislada, entonces no hay necesidad de probar las interacciones entre dos o más clases cuando están integradas. Sin embargo, esto no es cierto porque a veces puede haber errores, que solo pueden detectarse mediante la integración de clases. Además, es posible que si una clase no contiene un error, otra clase pueda usarla de manera incorrecta, lo que lleva a un fallo del sistema.

Para observar el conjunto de interacciones entre clases, se puede recurir a un diagrama de secuencia. Este muestra la cascada de mensajes entre un conjunto de objetos
Puede haber varios diagramas que muestran diferentes variaciones de la interacción.

Los test deben cubrir todos los mensajes posibles y condiciones, tanto escenarios esperados como alternativos.

Quizá sea mi estado anímico, pero para mí, este post ha sido un conjunto de ideas voladoras que dificilmente se conectan entre sí. En realidad es un intento de mi parte de darle orden a las ideas que hallaba por ahí en la web. Por ejemplo, para hallar ideas condensadas para el testeo de aplicaciones los encuentro aqui.

Por otro lado, me abrió el panorama la lectura de este pequeño blog.

Como cierre puedo decir: para código vivo, no es suficiente el testeo estático.

Verificación y validación de software

Durante el desarrollo de este semestre hemos hablado sobre la importancia del modelado y diseño de software para realizar software de calidad. Sin embargo, hasta ahora no nos hemos detenido a pensar que es un buen software.

En resumidas cuentas, podemos decir que buen software es aquel el que cumple las expectativas del cliente de forma eficaz y eficiente.

Esta idea nos lleva a pensar en dos componentes a revisar de nuestro producto:
¿Estamos realizando el producto que nuestro cliente desea?
¿Estamos construyendo código que funcione efectivamente?

Para comprobar estas dos características fundamentales de nuestro proyecto, es necesario realizar procesos de validación y verificación.

¿Por qué es necesario V&V?
V&V es necesario porque los diseñadores y desarrolladores de sistemas basados ​​en computadora son humanos; Ellos cometerán errores. Estos errores darán lugar a fallas no detectadas en los sistemas entregados. Las fallas pueden provocar fallas peligrosas que causan la pérdida de vidas, pérdidas financieras o daños a la propiedad. Por lo tanto, la misión de V&V es encontrar y corregir los errores lo antes posible en el ciclo de vida del desarrollo, evitando así la entrega de un producto defectuoso a un cliente.

¿Que tanta V&V se requiere?
El nivel de esfuerzo aplicado a V&V es una función de la criticidad del software o producto del sistema. Es decir, los riesgos involucrados si el sistema falla.

Verificar: evaluar que nuestro producto funcione.

¿Estamos construyendo el producto bien? La verificación es un proceso interno a la empresa o equipo a desarrollar el producto. Trata de un proceso de chequeo continuo al código que vamos construyendo, que este funcione de la forma adecuada y sin ningún bug. La verificación es el acto de demostrar que las salidas de diseño coinciden con las entradas de diseño. A fin de cuentas, el desarrollo de software se puede ver como un proceso incremental en el cual vamos mejorando nuestro producto hasta que obtengamos el programa ejecutable final. Para poder realizar nuestro proyecto, iniciamos con el diseño de entradas y salidas del sistema.

Dadas las entradas y salidas del sistema, podemos evaluar que nuestro software funcione correctamente. Esto se realiza a travéz de la revisión de código y los test unitarios.

Un test unitario compara el output de un subsistema con la salida esperada, dada cierta entrada. Los Test unitarios, deben evaluar desde los métodos más atómicos en el sistema. El pasado verano formulé una pequeña justificación para esto.

Supongamos que tenemos un proceso compuesto de 0<i<=n subprocesos.

Proceso con n subprocesos

Ahora bien, cada subprocesos contiene d(i) pares de entradas y salidas. Hacer un test unitario para el proceso completo sería cuidar que en todos los casos la salida sea la esperada. Por lo tanto, deberíamos considerar d(1) * d(2 ) * d(3) . . . * d(n) casos en nuestro test.

Sin embargo, si separamos en bloques nuestro proceso, y evaluamos cada bloque por separado ( pequeñas tareas) tendríamos que evaluar todas las posibilidades de cada subproceso, por lo tanto, la cantidad de casos a considerar es:

d(1) + d(2 ) + d(3) . . . + d(n)

Por lo tanto, si los procesos tienen más de 1 estado ( si un método solo aceptara una entrada / salida posible, sería un método inútil) observamos que:

d(1) + d(2 ) + d(3) . . . + d(n) < d(1) * d(2 ) * d(3) . . . * d(n)

Para d(i) >=2.

Por lo tanto, tenemos que evaluar menos casos al separar nuestro sistema en operaciones atómicas.

Además, al hacer test de pequeñas funciones es más fácil tener un entendimiento de lo que el programa realiza en ese pequeño subproceso.

Luego de realizar test unitarios, podemos proceder a los test de integración, donde se evalúa el acoplamiento de subsistemas.

Acorde a este pequeño blog: La verificación establece la verdad de la correspondencia entre un producto de trabajo y su especificación (del latín veritas, «verdad»).

Estandares para verificación de software:

IEEE – 1012

Validar: evaluar que realizamos lo que el cliente necesita.

¿Estamos construyendo un buen producto? La validación es un proceso externo, en el cual mantenemos un proceso de comunicación con el cliente para tener la certeza de que cumplimos sus expectativas. Las expectativas del cliente se trazan en un principio en los requerimientos de alto nivel.

La validación de software es parte de la validación de diseño para un dispositivo terminado, pero no se define por separado en la regulación del Sistema de Calidad. La validación de software se define como la «confirmación por examen y provisión de evidencia objetiva de que las especificaciones del software se ajustan a las necesidades del usuario y los usos previstos, y que los requisitos particulares implementados a través del software se pueden cumplir de manera consistente «.

El proceso de validación puede existir durante todo el proceso de construcción del software. Lo importante radica en tener una correcta retroalimentación de nuestros clientes ( o los requerimientos de alto nivel estipulados) para poder hacer correcciones al producto.

En este post definen la validación de software en 5 pasos:

Paso 1: crear el plan de validación

El primer paso en el proceso de validación es crear un plan de validación (VP) que identifique quién, qué y dónde. El plan generalmente consiste en una descripción del sistema, especificaciones del entorno, suposiciones, limitaciones, criterios de prueba, criterios de aceptación, identificación del equipo de validación, incluidas las responsabilidades de cada individuo, los procedimientos requeridos y la documentación requerida.

Paso 2: definir los requisitos del sistema

El siguiente paso es definir los requisitos del sistema (SRS) que definen lo que espera que haga el sistema. Estos se pueden dividir en dos categorías: requisitos de infraestructura y requisitos funcionales. Los requisitos de infraestructura incluyen la identificación de los recursos de personal necesarios y las instalaciones y equipos necesarios para la tarea. Los requisitos funcionales incluyen cosas como los requisitos de rendimiento, los requisitos de seguridad, las interfaces del sistema y del usuario, y el entorno operativo necesario. Un análisis de riesgo del sistema también es necesario. Este análisis evalúa los requisitos funcionales e identifica cualquier brecha. Luego se analizan las brechas para determinar y mitigar cualquier riesgo.
En el mundo real, las empresas a menudo compran sistemas utilizando requisitos comerciales que pueden ser diferentes a los requisitos funcionales. Por lo tanto, aunque es una buena idea definir todos sus requisitos (comerciales y funcionales), no es raro que la validación se realice en un sistema después de la compra y durante la instalación. En cualquier caso, una vez que haya definido sus requisitos funcionales y el sistema esté instalado en un entorno de prueba / validación, la siguiente fase es verificar los requisitos con pruebas.

Paso 3: Cree el protocolo de validación y las especificaciones de prueba

La fase de prueba comienza con el desarrollo de un plan de prueba (VP-Validation Protocol) y casos de prueba (Especificaciones de prueba). El plan de prueba describe los objetivos, el alcance, el enfoque, los riesgos, los recursos y el cronograma de la prueba de software. Documenta la estrategia que se utilizará para verificar y garantizar que un producto o sistema cumpla con sus requisitos.
Los casos de prueba identifican entradas, acciones o eventos y respuestas esperadas para determinar si una característica de una aplicación está funcionando según sea necesario. Dado que es imposible probar todas las entradas y salidas posibles
combinación, y las empresas se esfuerzan por minimizar los costos asociados con las pruebas, el objetivo es identificar tantos defectos como sea posible con la menor cantidad de pruebas posible. Por lo tanto, los casos de prueba deben escribirse de modo que tengan una alta probabilidad de descubrir tantos errores como sea posible con la menor cantidad de casos de prueba que sea necesario. También se debe desarrollar una matriz de trazabilidad de requisitos durante esta fase para rastrear los requisitos hasta su caso de prueba y durante los pasos restantes de las actividades de validación de software.

Paso 4: prueba

La prueba real está lista para iniciarse. Las pruebas de caja negra (pruebas que ignoran el código interno del sistema o componente y se centran en las entradas y salidas del software) se utilizan para la validación de los sistemas comerciales ya que no es el propietario del código. Las pruebas se ejecutan según el plan de prueba y los casos de prueba. Los errores, defectos, desviaciones y fallas se identifican y registran, y se eliminan en un informe final.

Paso 5: Desarrollar / revisar procedimientos e informe final

Una vez que se completan las pruebas, se deben desarrollar / revisar los procedimientos para el uso y la administración del sistema.
Luego, antes de lanzar el sistema para su uso, se produce, revisa y aprueba un informe final de validación. El Informe final o Informe de validación (VR) generalmente sirve como un resumen de validación. La aprobación de este informe es la versión final para que un sistema entre en producción. El informe debe incluir elementos tales como dónde se puede encontrar soporte del sistema, capacitación del usuario, cómo se abordará la seguridad del sistema y planes de respaldo y recuperación. El Informe Final también puede incluir el informe de prueba que discute el éxito de la prueba y elimina cualquier anomalía que pueda haber ocurrido durante la prueba.
En resumen, no existe un código secreto para validar su software. Cuando se divide en pasos simples y prácticos, la validación se puede realizar con bastante facilidad. Y al final, no solo cumplirá con las regulaciones, sino que su productividad aumentará porque sus sistemas han sido validados y están
trabajando apropiadamente.

Estándares de validación de software: ISO 13485 define los estándares para validación de software, específicamente para productos médicos.

Por otro lado, la FDA ( Federación Americana de Alimentos ) posee estándares para validar software, checa el documento aqui.

Reflexiones de un parcial II

Primer parcial lo puedo definir como un parcial extrínseco. El objetivo de los post de ese periodo, era definir una mega historia que contara los fundamentos para el modelado de software.

Los escritos tenían un carácter más impersonal, el objetivo era transmitir conocimiento a través de pequeños cuentos que fueran fáciles de recordar y generar analogías.


Este parcial fue para mi un parcial de introspección.

El objetivo ha cambiado radicalmente, el punto de estos escritos fue de cierta forma darle forma a mis propias ideas, opiniones y experiencias respecto a la creación de software a partir de un diseño. Desde siempre he tenido la creencia que, el proceso de generación de software es un proceso creativo y como todo proceso creativo se puede disciplinar. La idea generada a partir de dicho proceso creativo puede ser metodizada (tal y como lo presenta el arte conceptual) al grado de ser capaces de construir el producto final de forma más o menos secuencial.

El conjunto de estas semanas me han ayudado a condensar conocimiento que durante mucho tiempo he intentado formular, para poder reproducirlo.
Me satisface mucho decir, que dadas mis conclusiones en los post pasados puedo tener la certeza del como generar código limpio, de calidad y que sea funcional. Esto me abre los ojos a la realidad: Soy capaz de crear, soy capaz de realizar los proyectos que desee, con la debida planificación y tiempo.

Soy capaz de imaginar y trasladar esas ideas a conceptos transferibles a otros

Durante la semana 6 y 7 presentamos UML. Lo más interesante para mí de UML es que, han formalizado la creación de diseños de productos de software, al grado que la comunicación entre desarrolladores puede ser fluída. Sin embargo, la magia de los diagramas UML es que, a diferencia de algún plano arquitectónico, una ecuación matemática o la representación isométrica de alguna pieza a producir en un torno CNC, los diagramas UML son estúpidamente intuitivos. Los diagramas están hechos para las personas que los usan!!

De esta forma, es que ha sido posible trasladar UML a otras disciplinas, es por ello que los diagramas UML son comprensibles hasta por clientes sin conocimiento técnico.

Desde la perspectiva inversa, el estudio de UML me enseñó que, soy capaz de trasladar mis ideas a un diseño organizado, trasladando los conceptos a una relación sintáctica adecuada.

Dado que soy capaz de generar y plasmar ideas, es posible diseñar productos que satisfagan de la sociedad.

Soy capaz de generar código limpio, de calidad, mantenible. Además soy capaz de modelar bases de datos que cumplan con las necesidades específicas de un cliente.

Al poder generar diagramas de entidad relación, o en su defecto, diagramas de clases ( dado el punto anterior) tengo la certeza de que puedo generar la estructura de mi proyecto. Durante la semana 8 me demostré que tengo las habilidades para construir y optimizar tablas ( relaciones). Durante la semana 9 puse en manifiesto mi metodología para generar código limpio. El mayor problema que me hallo para aplicar mi metodología a lo largo de mi desarrollo académico, es la falta de tiempo para la implementación de los distintos proyectos.

Soy capaz de comunicarme con los miembros de mi equipo, de forma asertiva y mesurada.

La revisión de código es la base de la sociedad de los desarrolladores muertos. Esto, los foros y las stand up meetings en mi opinión son el núcleo social que consolida la comunidad de desarrolladores. Estas actividades sociales, nos han enseñado, ( más que a encontrar bugs y limpiar códigos ) a ser empáticos, asertivos y objetivos con nuestros compañeros de trabajo. Creo yo, que este tipo de prácticas hacen a la comunidad de desarrolladores tan asombrosa, tan humana.

De Sabios a discípulos: las enseñanzas de los grandes

De las 2 charlas que tuvimos la oportunidad de tener con estos grandes magos del desarrollo de software ( que por privacidad de los ponentes me reservaré sus nombres ) solo quiero expresar una idea, que considero es suficiente:

Estamos en edad de experimentar, crecer y crear. Hagámoslo de una buena vez.

Lo demás, sobra

Code review

La revisión de código es un ejercicio social donde la tribu acepta la ofrenda y sacrificio del desarrollador en turno, para construir un proyecto que los dioses de los bugs bendigan.
-Alex, 2019

No tengo mucho que escribir sobre el acto de revisar código.

Para empezar, existe muchísima información al respecto y todos tienen una opinión homogénea de como hacer una buena revisión de código ( hay que ser asertivos en el proceso de revisar código, revisar más de 400 lineas de código suele ser contraproducente, si van a revisar tu código envíalo lo mejor y más limpio posible, ambas partes deben ser agradecidas, positivas y directas ).

Creo que, una correcta revisión de código, que fomente la comunicación y una buena cultura de trabajo en tu equipo, sin sacrificar tu productividad, se puede resumir en estos 2 post:

IBM: 11 proven practices for more effective, efficient peer code review de Jason Cohen

Proven Code Review Best Practices from Microsoft – Doctor McKayla

El pasado verano, al tener la gran oportunidad de trabajar en Facebook como interno, tuve una somera probadita de lo que implica que alguien en mi equipo (mi jefe, específicamente) revise mi código. También observé los problemas que pueden generarse si no se realiza adecuadamente ( y en tiempos correctos ) la revisión. Mi buen amigo Iván me contaba de vez en cuando, como su trabajo se acumulaba en el stack, porque nadie quería revisar su primer commit. Su experiencia es muy provechosa, interesante y en lo personal me moría de risa al escuchar sus lamentos, aquí dejaré su post.

De lo que puedo escribir y creo que será provechoso, es las prácticas que implementé de forma más o menos intuituva para no generarle un dolor de cabeza a mi manager mientras revisaba mi código.

Durante mi estancia en mi internship despertaba demasiado temprano todos los días, por lo que era el primero en llegar a la oficina. Durante esa hora libre me tomaba el tiempo para asegurarme de aislar los objetivos a realizar en pequeños objetivos.

Estos pequeños objetivos se volvían a pequeños commits, los cuales eran de tamaño decente para mi manager. ( Ya no se quejaba por el largo de los commits, se quejaba que producíamos demasiado en poco tiempo).

Aprender a ser objetivo y no tomarse personal las revisiones creo que es lo más complicado, separar trabajo de las emociones, separar el código del desarrollador.

From classes to code

¿Cómo transformar diagramas de clases a una implementación de código? Para mí, esta pregunta esconde el santo grial de la programación.

Luego de adentrarme a la red, para leer la teoría al respecto, o al menos encontrar la opinión pública me encontré con un resultado interesante: Nadie habla específicamente sobre como impelementar código. Lo que hallé en su mayotía, fueron quejas a los diagramas UML y formas alternas para diseñar diagramas fácilmente implementables.

Por ejemplo, propuestas como UMLx y en general, modelos ejecutables. El problema de fondo que encontré fue que solemos pensar en la implementación y luego en el diseño. Pensamos en nuestro diseño en función del lenguaje y el código.

En este post, sin embargo, preferiría hablar de mis propias conclusiones sacadas bajo mi poca experiencia.

En mi opinión, la razón por la que los desarrolladores de software utilicen tan poco diagramas formales y por ende, poco afán tengamos por aprender a modelar desde un inicio utilizando buenas prácticas ( lo que en definitiva, reduciría refactorizaciones e iteraciones a nivel estructural en nuestro producto) es porque no se ha aprendido una técnica formal y estructurada para transformar un (buen) diagrama a código.

Actualmente la implementación la realizamos ad hoc al proyecto, no seguimos una metodología formal para implementar nuestros diagramas, nuestras ideas.

Actualmente, en mi escuela existe un club de programación orientado a problemas de entrevistas de trabajo. Los alumnos se afanan por resolver problemas, uno tras otro, resolviendo situaciones utópicas que se resuelven con la implementación de algún algoritmo específico. Creo que es genial que se promuevan este tipo de movimientos.

Sin embargo, cuando me ha tocado desarrollar si quiera, métodos más complejos, con personas que dedican demasiado tiempo a la algoritmia y no dedican el tiempo suficiente al modelado y diseño simplemente explotan. Ven un problema grande y, a pesar de que sepan implementar código super eficiente, sepan quicksort de memoria y sepan resolver cualquier problema de entrevista por debajo de O(n^2), el resultado queda un código muy sucio y difícil de comprender.

¿Entonces, donde queda toda su habilidad para resolver problemas? Sigue ahí. Sin embargo, para programar es necesario también aprender a diseñar soluciones. Esas habilidades también deberían enseñarse en esos clubs de programación.

Implementar diagramas estructurales.

El primer paso para llevar nuestros diagramas a código es algo obvio. Genera las clases descritas en el código, añade los atributos y define los métodos en cada clase. Además, implementa las interfaces que se indiquen. Existen softwares que realizan esto de forma automática, al menos de UML a Java, por lo que sé. Es más, hay herramientas que generan ingeniería inversa, de código a diagramas. Pero ese es otro tema.

A estas alturas, si nuestro diagrama fue bien diseñado, sabemos que atributos requiere nuestro método y cual es la salida. Tenemos que rellenar dicha caja negra.

Para llenar los métodos con código limpio y que funcione, para mí ha funcionado utilizar el siguiente algoritmo recursivo:

  • Un método suele representar una actividad o comando. Si la implementación de este método, es trivial, terminó nuestro sufrimiento. Lo implementamos, ¡Yay!
  • Si el método requiere de algún algoritmo específico o bien conocido, se llama la librería pertinente y si esta librería no existe ( o llamar esta librería sería un desperdicio de espacio y recursos) lo implementamos ( siguiendo el libro, sin complicaciones, no estamos en examen de algoritmos). Es más, creamos nuestra propia clase (Utils suelo llamarla) para algoritmos.
  • Si existe una iteración, implica que una operación similar se aplicará a distintos objetos. Todo lo que exista dentro de nuestro ciclo, podemos delegarlo a otro método. De esta forma, nuestro código no queda con muchos niveles de tabulación ( demasiado anidado ) y podemos utilizar recursivamente este método ( si fuera necesario).
  • Si el método no es suficientemente trivial ( atómico ) implica que dicha operación se puede separar en acciones más pequeñas. En este punto, es posible realizar una serie de diagramas de objetos, para observar la evolución de nuestro objeto, o un diagrama de estados, para evaluarlo de forma secuencial. Como una receta de cocina, sabiendo los ingredientes y el platillo final, podemos discretear en operaciones más pequeñas nuestro método. Aquí está el secreto de la magia.
  • Dados los métodos generados ( que, al ser subprocesos internos, son automáticamente privados) se aplica nuestro algoritmo para cada una de estas nuevas operaciones.
  • Si se debe implementar una clase anónima, no se hace inline. Se define una variable que contendrá esta clase anónima. A los métodos dentro de esta clase se les aplica el algoritmo.
  • Si requerimos en una clase anónima, llamar para alguna operación, algún objeto fuera de dicha clase, la operación se deberá realizar en un método en el objeto receptor.

Luego de seguir este algoritmo, suelo obtener un código limpio e implementado de forma rápida, ya que en todo momento realizo operaciones pequeñas. El testeo de nuestros métodos y la resolución de bugs se vuelve más simple, ya que testeamos siempre métodos cortos y con operaciones atómicas. Los problemas, son fácilmente diagnosticables.

La mayoría de mis proyectos o trabajos, no han requerido otro lenguaje de programación, fuera de Java. Por lo tanto, no he tenido problemas en acoplar los diagramas a la sintaxis o limitantes de otros lenguajes de programación.

Este será un blog corto, pero creo que será el más importante del curso, ¡lo puedo apostar!

Adiós!

De diagramas a tablas

En la actualidad la información y los datos han cobrado una importancia fundamental. La cantidad de datos que generamos y transmitimos todos los días es increíble. Nuestros cielos se encuentran atestados de ondas electromagnéticas cargadas de información, en todas las frecuencias, por todos lados. Construimos infraestructuras para compartir y guardar esos datos.

Datos por doquier! Hasta nuestra atmósfera está sofocada por datos que se transmiten por todas direcciones.
Fuente: https://www.fundaciontelefonica.com/wp-content/uploads/2015/02/vivir_mar_datos_720.jpg

Guardar. Porque es necesario acumular, preservar mantener. Todas las imágenes, memes, conversaciones que publicas, compartes, generas, en algún lugar del mundo se guardan. Quizá en algún data center sumergido debajo del Pacífico, quizá en algún bunker en Islandia. No lo sé. Pero lo que sí se es que, para poder guardar de forma ordenada y eficiente nuestros datos, es necesaria la implementación de una base de datos.

Una base de datos (relacional) la definiría en tres palabras como un conjunto de tablas. Dichas tablas ( llamadas formalmente relaciones) poseen una lista de datos ( propiedades o campos) para un conjunto de objetos dados. Por otro lado, una base de datos no relacional, estructura la información de forma jerárquica, a manera de un árbol n-ario ( por verlo de una forma).

Una forma de interpretar una base de datos no relacional, es definir el almacenamiento de los datos de forma jerárquica. Un objeto posee un conjunto de propiedades a las cuales se puede accesar. Estas propiedades pueden ser otros objetos.
Fuente: https://previews.123rf.com/images/extracoin/extracoin1711/extracoin171100017/89429152-jerarqu%C3%ADa-en-la-empresa-%C3%A1rbol-de-organigrama.jpg

Las bases de datos merecen un adecuado modelado, un diseño pobre de una base de datos, podría generar información redundante en las tablas ( y por ende, costo de almacenamiento) o en su defecto, podría dejar sin acceso a ciertos fragmentos de la información.

En este momento deberías estar preguntándote querido lector: ¿Como modelo una base de datos?

Diagrama Entidad Relación

UML posee los diagramas entidad relación, en la cual definimos que clase de información vamos a guardar y como está relacionada entre sí. Sin embargo, esto no nos ofrece un modelo de datos ( las tablas explícitas a implementar en la base). Los diagramas en cambio se utilizan como primer paso, para reunir requisitos y definir la arquitectura de los sistemas de bases de datos.

Una entidad puede ser un objeto físico tangible, como una escuela o un estudiante, o un concepto, como una respuesta o una transacción. La entidad se puede identificar extrayendo objetos que sean relevantes y significativos para el dominio del problema y el sistema a desarrollar. Cada entidad trae consigo un conjunto de columnas, que son las propiedades de la entidad a la que pertenecen los atributos. Por ejemplo, la entidad Estudiante tiene nombre, dirección y calificación como columnas (sinónimos: atributos, propiedades, campos). Cada entidad debe tener al menos un atributo que se pueda utilizar para identificar de forma exclusiva la entidad, que se conoce como llave (s) primaria (s) de la entidad.

Las relaciones conectan las entidades. Existen relaciones uno a uno (Para cada zapato izquierdo existe un zapato derecho), uno a muchos( cada hijo tiene una madre, pero eso no implica que una madre no pueda tener más de uno) y relación muchos a muchos ( entre las prendas de mi closet, existen prendas de distintos tipos y colores. Las entidades tipo de prenda y color, poseen una relación muchos a muchos).

Si quieres adentrarte en la notación y detalles de los diagramas haz click aqui. Los diagramas ER no es el tema primario de este post.

Como mapear un diagrama ER a tablas

Supongamos que para este punto, poseemos un diagrama Entidad Relación que represente adecuadamente nuestro sistema.

Para trasladar el diagrama a tablas se aplica un proceso conocido como mapeo. El mapeo sigue una serie de pasos que rezan algo más o menos así:

  • Para cada entidad (fuerte) genera una tabla. Indica su llave primaria.
  • Para cada entidad (débil) genera una tabla con todos sus atributos. Además, agrega como llave foránea ( puntero que indica a quien pertenece este hijo) la llave primaria del padre.
  • Para las relacion 1:1 entre dos entidades E1, E2, agrega la llave primaria E2 llave foránea de E1.
  • Para las relaciones 1:N entre entidades E1, E2, toma la llave primaria de la entidad que solo involucra 1 elemento en la relación ( madre con muchos hijos, toma la madre) y asignala como llave foránea en la Entidad 2. ( Cada hijo sabrá quien es su madre, pero la madre no tiene consciencia de quienes son sus hijos).
  • Para cada relación muchos a muchos se genera otra tabla que relacione la llave primaria de ambas entidades y las propiedades de esta relación.
  • Para cada atributo multivaluado A, cree una nueva tabla R. R incluirá un atributo correspondiente a A, más el atributo PK.

El proceso de mapeo descrito anteriormente es una transcripción resumida del proceso de mapeo descrito en el libro Elmarsi et al. Fundamentos de Bases de datos. Consulte su ficha aqui.

Normalización

Al mapear un diagrama Entidad Relación a tablas, ya podemos implementar nuestras tablas en alguna base de datos. Sin embargo, es posible que a estas alturas nuestra base de datos posea información redundante, o cual implica un costo en el almacenamiento. Además, pueden existir problemas de mantenimiento de los datos por la redundancia entre tablas. Para eliminar redundancias y proporcionar un acceso ordenado a toda la información se procede a la normalización de las tablas.

Con la normalización se pretende:

  • El número mínimo de atributos necesarios para soportar los requisitos de datos de la empresa;
  • Atributos con una relación lógica cercana se encuentran en la misma relación;
  • Redundancia mínima con cada atributo representado solo una vez con lo importante excepción de los atributos que forman todo o parte de claves foráneas.

La normalización tiene distintos niveles. Se clasifican con números, a mayor número, mejor normalizada se halla la tabla.

En esta sección me permitiré presentar un método que formulé para normalizar tablas hasta en 3FN.

Mi método inédito para normalizar tablas

Primero debo explicar brevemente el concepto de dependencia funcional.

Dependencia Funcional Decimos que un atributo Y de una relación “depende funcionalmente” de otro atributo X de la relación si a todo valor de X le corresponde siempre el mismo valor de Y. 
Para normalizar aprovecharemos el concepto de dependencia entre distintos atributos.
Las dependencias funcionales se representan:
X -> Y

Algoritmo para normalizar ( con dibujitos ).
La tabla no normalizada se puede interpretar como un grafo dirigido. Donde los nodos son los atributos y las aristas son las dependencias funcionales:
Por ejemplo, sea la tabla

Parcela:
( IdPropiedad, Municipio, NoParcela, Area, Precio, Impuestos ).

Además, supongamos que conocemos las distintas dependencias funcionales entre los atributos:
idPropiedad -> Municipio,NoPropiedad,Impusetos, Area
Municipio,NoPropiedad -> Area, Impuestos, precio, IdPropiedad
Area -> Precio
Municipio -> Impuestos

Podemos representar nuestra tabla con un grafo dirigido:
La tabla puede representarse como un grafo dirigido: Los nodos son atributos y las aristas representan la dependencia funcional.
Dibujo realizado por mi

Cada nodo representa un atributo. Cada arista representa una dependencia funcional.

Como primer paso, debemos eliminar aristas, de tal forma que el grafo se transforme en un árbol. (Aún no conozco algún algoritmo que realice este proceso, pero de haberlo, habré automatizado el proceso de normalización de tablas!). Con el proceso anterior se eliminarían las relaciones transitivas.

Se podrá observar, que existen más de una solución viable.

Para este punto, obsérvese que los atributos ya están organizados jerárquicamente, de tal forma que representan una base de datos no relacional!

Como segundo paso, para realizar la 2FN, donde todos los atributos no llaves primarias, dependen funcionalmente de forma completa de la llave primaria, significa que todos los nodos  no raíz del árbol sean hojas del árbol. Si no son hojas ( es decir existe un subárbol en dicho nodo) lo que hacemos es tomar dicho nodo y creamos un nuevo árbol con el. Además, dicho nodo permanece en el árbol original como hoja.

Cada árbol, representa una tabla. Cada nodo raíz es la llave primaria. Y si una hoja aparece como hoja raíz en otro árbol, implica que es una llave foránea. 

Con esta disposición de tablas, automáticamente se tiene una normalización 3FN. 

Conclusiones

Obtener una implementación eficiente de una base de datos a partir de nuestros diagramas entidad relación, no es un proceso trivial. Espero hallar un algoritmo programático para automatizar el proceso de normalización de tablas.

Todo es más fácil haciendo dibujitos!

UML II: la venganza de los diagramas

En el post anterior inicié una exploración a través del Lenguaje de Modelado Unificado (UML por sus siglas en inglés). Quizá abruptamente inicié la controversial discusión si utilizar o no usar UML y porqué. Además, exploramos algunos de los diagramas más utilizados en la industria.

UML posee 19 diagramas distintos, con los que cubre la mayoría de necesidades de modelado de software. En la práctica, se dice que el 80% por ciento de las necesidades de los proyectos se cubren con los diagramas más comunes ( descritos aqui ). El día de hoy echaremos un vistazo a diagramas que, si bien no son los más comunes, su aprovechamiento es esencial para el diseño de sistemas.

Antes de comenzar, haré un breve paréntesis, para indicar las clases de diagramas UML.

Clasificación de diagramas

UML posee dos categorías de diagramas: Los diagramas estructurales y los diagramas de comportamiento.

Digamos que, los diagramas estructurales presentan un panorama estático del sistema, su composición, como está construido. Los diagramas de comportamiento ofrecen un panorama dinámico, es decir, presenta la evolución e interacción de los subsistemas entre sí ( a veces me pongo como ejercicio mental construir definiciones con lo que previamente sé sobre un tema. En esta ocasión, se halla muy acercada a lo que hallé en este blog).

Diagramas de estado

Los diagramas de estado pertenecen a los diagramas de comportamiento. Se utilizan para modelar el comportamiento discreto de un sistema a través de transiciones de estados finitos. Son muy útiles para simplificar complicados comportamientos.
Estos diagramas permiten que podamos visualizar el comportamiento del sistema como conjunto, representado en forma de grafo.
Los vértices (nodos) representan estados o pseudoestados del sistema. Un estado modela una situación en la cual un conjunto de condiciones permanecen invariantes a lo largo del tiempo. Durante este periodo de tiempo, el sistema se mantiene continuamente realizando la misma actividad. Cuando las condiciones cambian, se produce una transición de estado.
Las aristas (flechas) direccionadas representan transiciones. Las transiciones son cambios entre estados. Para que exista una transición, suele ocurrir una actividad en el exterior que dispare este cambio. En las transiciones de estados pueden ocurrir decisiones que dirijan a estados distintos, bifurcaciones o uniones de estados.

Diagrama de estado: Vertices indican estados, aristas indican trancisiones.
Fuente: https://www.abiztar.com.mx/articulos/imagenes/011.gif

Los diagramas de estados describen detalladamente la vida y evolución de un objeto. Todos conocemos la idea del ser humano como autómata: Naces, creces, te reproduces y mueres.

Diagramas de paquetes

Los diagramas de paquetes muestran la estructura de un sistema en un nivel de abstracción muy general. Los paquetes se representan como carpetas que contienen todas las dependencias necesarias para poder funcionar dicho componente. Un paquete reúne un conjunto de elementos que comparten elementos semánticos. La clave de estos diagramas es la agrupación. Los paquetes recogen las clases que comprenden un subsistema como conjunto.

Las clases que comparten misma composición, herencia o utilizan una gran cantidad de datos comunes, suelen pertenecer al mismo paquete.

El diagrama de paquetes sigue la estructura jerárquica de los paquetes anidados. Los módulos atómicos para paquetes anidados suelen ser diagramas de clases. Existen pocas restricciones al usar diagramas de paquetes, son las siguientes.

  • El nombre del paquete no debe ser el mismo para un sistema, sin embargo, las clases dentro de diferentes paquetes podrían tener el mismo nombre.
  • Los paquetes pueden incluir diagramas completos, nombres de componentes solos o ningún componente.
  • El nombre completo de un paquete tiene la siguiente sintaxis.

En los diagramas de paquetes, utilizando dibujos y diagramas se condensa mucha información: Acceso, visibilidad e importación de clases.

( Hallé muy útiles estos dos artículos para tener una comprensión general de este tipo de diagramas: busca aquí o aquí ).

Diagramas de componentes

Hace un par de años, un buen amigo comenzó a trabajar en una prestigiosa empresa de software, aquí en México. Yo aún no comenzaba a estudiar CS (y ni siquiera lo tenía contemplado). Mientras tanto, mi buen amigo me contaba que su trabajo consistía en crear sistemas basados en componentes, dichos componentes se encargaban de realizar tareas bastante específicas, de tal forma que existiera relaciones débiles entre servicios. En ese entonces, poco entendí y posiblemente quedé con una cara de intriga.

Hoy por hoy, puedo al menos decir que, para el modelado de este tipo de sistemas podemos aplicar los diagramas de componentes de UML. En estos diagramas se pretende mostrar la relación y estructura de un sistema basado en componentes.

Hasta donde comprendo, un componente puede llamarse como tal, cuando es una entidad bien definida, realiza tareas específicas y es posible sustituirlo de forma modular.

Para explicar más a fondo el diagrama como tal, sé que necesito mayor conocimiento en SOA. De cualquier forma, lo que he entendido hasta ahora ha sido gracias a este post y, como se va haciendo costumbre con UML, con las notas de IBM al respecto.

Sumario UML

Durante mis 6 años como entrenador de la selección jaliscience de física, aprendí algo muy importante:

Si puedes imaginar el problema, tienes la mitad del camino para resolverlo. Si puedes imaginar el sistema, sus subsistemas y como esperas que se comporte, podrás modelar las ecuaciones pertinentes que describan el movimiento de los objetos implicados. Es por ello que es tan importante realizar dibujos y diagramas de las interacciones que sufre el objeto a analizar, la física sale sola, si imaginaste bien el sistema.

Trasladando estas ideas al desarrollo de software puedo decir que:

Si puedes imaginar el problema, tienes la mitad del camino para implementar una solución. Si puedes imaginar el sistema , sus subsistemas y como esperas que se comporte, podrás modelar la estructura de tu sistema pertinente que describa el interacción de los objetos implicados. Es por ello que es tan importante realizar dibujos y diagramas de las interacciones y relaciones que sufren los subsistemas a analizar, la implementación se produce de forma automática, si imaginaste bien el sistema.

Los diagramas, son fundamentales para la descripción de sistemas y comportamientos. Si puedes plasmar el modelo del sistema en un diagrama completo y universal, el sistema será fácilmente replicable.
Fuente: https://www.jfinternational.com/images/diag1.gif

Para realizar diagramas ( de fuerzas, torques, campos etc) en física, existen un conjunto de convenciones bien conocidas. Estas convenciones hacen que los diagramas sean universales. En software, tenemos UML.

Si realizas un buen modelado de tu sistema, la implementación será intuitiva.

O al menos, esa es mi creencia.

UML I: el inicio

A lo largo de lo que llevo de formación universitaria en ciencias computacionales, la insistencia de algunos profesores en el uso de diagramas UML ha sido avasallante. Por mucho tiempo se nos enseñó e insistió en crear diagramas UML de todo tipo para el diseño y documentación de nuestros proyectos.

Sin embargo, cuando me enfrento al mundo laboral ( y escucho experiencias de compañeros, familiares y amigos cercanos en la industria) no se suele escuchar que, durante el proceso de modelación se utilice UML de forma explícita.

Por lo que, al enfrentarme a la tarea de esta semana me genera la duda: ¿Deberíamos estar preguntándonos porqué usar UML o más bien la pregunta adecuada será porqué no usarlo? Y si no debiéramos usarlo, que deberíamos hacer para reemplazarlo?

Durante los post anteriores utilicé la analogía de la construcción de una casa para explicar la importancia del proceso unificado de software (click AQUI). Sin embargo, luego de pensar durante las próximas semanas me di cuenta de que la analogía no es del todo exacta.

En la construcción, es difícil quitar, poner o reemplazar subsistemas. Por naturaleza están fuertemente conectados entre sí. Fuente: https://i.pinimg.com/474x/8e/e8/a2/8ee8a27ae7c1b4a2c0badb34edabd8b9.jpg

En software, puedes construir componentes de forma modular y con débiles conexiones con otros componentes. En un hogar no es así, no puedes reemplazar recámaras así no más, quitando y poniendo baños, ensanchando paredes y alargando techos. No funciona así. En los planos arquitectónicos viene expresada la forma final de la edificación.

Con software, para bien o para mal, al crear un sistema compuesto por otros sistemas ( que podemos soñar considerarlos como cajas negras ) podríamos reemplazar estilos, componentes y elementos del sistema de forma rápida y simple. Lo cual implica que el proyecto tiene una alta probabilidad a ser mutado y en la práctica suele ser así.

Por lo que, como podemos inferir, el proyecto final quizá no quede como en los planos iniciales y si estos servirán como documentación es posible que haya que también modificarlos. Por lo que, ¿Es útil crear planos de productos que no terminarán siendo como dice el diagrama?

Por lo tanto, partí de estas ideas para adentrarme en la red y aprender sobre las opiniones de la gente, lo que dicen por ahí, desde en blogs, tutoriales hasta . . . en Reddit.
Para empezar creo que debo indicar, el bias evidente que hallo sobre las páginas que hablan en favor total del uso de UML, todas ellas portales de herramientas para generación de diagramas y modelos. Sin embargo, sus argumentos no dejan de ser válidos.

Argumentos a favor de UML

Uno de los argumentos a favor es que los diagramas son más fáciles de entender que el texto. Tiene mucho sentido, no por nada dicen que una imagen vale más que mil palabras. ( Sin embargo, esto no sustituye comentar el código de vez en cuando ).

Además, al utilizar un lenguaje bien definido, podemos transmitir ideas a miembros nuevos o ajenos, además es fácil de comprender la estructura del proyecto, incluso para clientes o personas que no posean conocimiento en software. (Draw.io defiende el uso de UML. Tu que opinas? Aqui te dejo sus argumentos. Draw.io es una excelente herramienta online para hacer diagramas. En lo personal, me ha sacado de más de un apuro ).

Una razón interesante para utilizar UML es que es difícil hallar como suplantarlo. UML es un lenguaje de modelado bien definido por lo que, en el modelado de software supera con creces en su simpleza, coherencia y los diagramas UML son excelente referente a lo que se refiere a documentación.

UML para bien o para mal, es el lenguaje estándar para la modelación de software. Por lo tanto, existen infinidad de herramientas y software para la creación de diagramas y que son compatibles para generar ingeniería inversa, modificar diagramas a tiempo real y una infinidad de aplicaciones útiles para diseñar la arquitectura de la aplicación. Además, UML al llevar más de dos décadas en el mercado se ha perfeccionado para cubrir las necesidades de casi cualquier proyecto. ( Razones para usar o no usar UML, un excelente post ).

Razones para no usar UML . . . (?)

Tanto reddit o stackoverflow son buenos lugares para hallar la voz de los programadores. Es interesante que, a pesar de los tantos beneficios que posee UML ( de unificación, estandarización y simpleza ) los programadores en sus ambientes de trabajo utilicen al mínimo estas herramientas. En general la voz pública indica que el uso de UML es bueno, sin embargo la mayoría de las veces es suficiente con el uso de diagramas en pizarrón, además pocos conocen o utilizan muchos de los diagramas. Con los diagramas de clase, secuencia y casos de uso cubren la mayoría de las necesidades de los proyectos.

Algunos otros solamente utilizan los diagramas UML ( más formales ) como documentación. Durante el proceso de diseño y creación ( que constantemente requiere de iteraciones ) prefieren diagramas en pizarrón, y si hay una modificación solo se enfoca en dicha área a cambiar.
Otros desarrolladores dejan los diagramas en la fase de diseño. Otros más extremistas dicen que UML está muerto!

( Si quieres leer más? da click en este foro de reddit o aqui, para hallar los diálogos en StackOverflow).

En este punto que hallo polarizadas opiniones, es necesario desenredar el mar de puntos de vista y de forma atrevida dar una opinión.

Creo que el mayor problema con UML es que no sabemos utilizarlo. Y con saber utilizarlo, me refiero a aprender correctamente todas sus simbologías y tener un entendimiento del significado de estas; como consecuencia de un mal aprendizaje del uso del paradigma orientado a objetos es que no aplicamos UML correctamente.

Por otro lado, a pesar de que existan múltiples herramientas para aplicar UML sea necesaria alguna que transforme la escritura directa y los diagramas a mano que se realizan en las fases de diseño a diagramas digitalizados y formales.

Finalmente, considero que el uso de UML depende mucho de la culturalización de la comunidad de desarrolladores, de darle valor a la fase de diseño de software. Actualmente se considera que el intentar diseñar en su totalidad una aplicación es prácticamente imposible, sin embargo, el constantemente rediseñar una aplicación (sin crecerla o mejorarla, tan solo redefiniendola ) no es viable.

Si en realidad aplicáramos una correcta conceptualización del paradigma orientado a objetos, lo que se requeriría cambiar son tan solo subsistemas aislados, no la totalidad de un producto.
Quizá sea necesario discretizar el diseño de la producción, para tener ingenieros mejor capacitados en ambas áreas ( como el análogo en la construcción con el ingeniero civil y el arquitecto).

Creo que podría concluir con esto: La informalidad de los diagramas depende directamente de que tan probable sea que el proyecto sea mutado considerablemente durante el proceso de construcción. Si en un proyecto no se sabe lo que quiere, ni se tiene una buena formación en el diseño del producto, además de que se utilice un proceso dinámico para la producción del software, es posible que los diagramas formales sean despreciados.

Diagramas UML más comunes

Los diagramas UML se clasifican en dos tipos: Diagramas de estructura y diagramas de comportamiento. Los diagramas de estructura definen la arquitectura del sistema, mientras que los diagramas de comportamiento definen como se relacionarán los componentes internos del sistema entre sí o elementos externos.

Diagramas de clases

Siendo un diagrama estructural, define la construcción del sistema a través de las distintas clases, interfaces, métodos y tipos de datos que contendrá el sistema. Con estos elementos podremos definir que y como está compuesto el sistema, ya que estos elementos representan «el esqueleto» del sistema.

Una definición interesante y sencilla que hallé (aqui )es: Los diagramas de clases explayan los bloques de construcción que constituirán el sistema orientado a objetos.

Los diagramas de clases definen las relaciones entre clases ( componentes ) estas relaciones pueden ser de agregación ( tiene) herencia (generalización, es ) entre otras.

Cada clase contiene los parámetros que describen a la clase, así como los comportamientos permitidos. De esta forma se describe exitosamente las características de la clase y lo que puede hacer. Para nosotros no nos importa por ahora como va a realizar dichos comportamientos, solo se trata de una descripción.

En mi experiencia, es el diagrama que más he utilizado a lo largo de mi desarrollo profesional.

IBM tiene detalladas explicaciones sobre notaciones y los tipos de relaciones en un diagrama de clases. Te recomiendo le des una leída aqui. Explicar un diagrama de clases implica lidiar directamente con conceptos ligados profundamente al paradigma a objetos ( lo cual va más allá del alcance de este post).

Diagramas de objetos

Así como escribí más arriba que un diagrama de clases es el esqueleto de un componente, ya que detalla tipos de comportamientos y datos, un diagrama de objetos sería análogo a un muñeco que haga uso de dicho esqueleto.

Un diagrama de objetos es la representación de un objeto ( la instancia de una clase ) en un instante dado. Es decir, a todos los atributos especificados en el diagrama de clases, se les personaliza, se les asigna valores. De esta forma podemos analizar un objeto ( y su evolución deseada ) en el sistema.

En el diagrama de objetos, los atributos contienen valores dados.
Fuente: https://d2slcw3kip6qmk.cloudfront.net/marketing/pages/chart/UML-object-diagram-tutorial/Object_Diagram.PNG

Diagramas de secuencia

Los diagramas de secuencia determinan la evolución de la interacción entre componentes de un sistema a lo largo del tiempo. Los diagramas pretenden mostrar el orden de la secuencia de interacciones entre elementos externos al sistemas y componentes de este.

En resumidas cuentas podría describirlos como: Dado un conjunto de partes discretas de un sistema y elementos ajenos a este, se representa la vida (linea de tiempo) de cada uno de ellos por medio de una linea vertical hacia abajo. Las interacciones entre sistemas (mensajes entre subsistemas) se representan con flechas horizontales direccionadas, en ellas se describe brevemente el mensaje a transmitir. El tiempo t=0 se presenta en la parte superior de la hoja, por lo que, entre más abajo se encuentre una interacción, más al futuro se halla. Los diagramas de secuencia llevan una especie de lógica de escalera.

Lineas verticales: Linea de tiempo del componente
Lineas horizontales: Interacciones entre componentes.
Descripciones en lienas horzontales: Tipo de mensaje ( interacción entre componentes)
Cajas: Instancia del subsistema, componente en el sistema.
La escencia de un diagrama de secuencia en 4 lineas.
Fuente: https://www.geeksforgeeks.org/unified-modeling-language-uml-sequence-diagrams/

Los diagramas de secuencia son útiles ya que describe el comportamiento del sistema a lo largo del tiempo, así como muestra la respuesta desencadenada del sistema a un agente externo.

De igual forma, IBM tiene un post muy detallado sobre Diagramas de Secuencia. Sinceramente, creo que por más que mis manos se deshagan en escribir, no podré hacerle justicia a ese post.

Conclusiones

UML ofrece diagramas que, a pesar de estar formalmente establecidos, mantienen un formato intuitivo para su construcción. En mi opinión, si invertimos en aprender adecuadamente a utilizar el paradigma orientado a objetos, obtendremos una ganancia ( y será fácil de utlizar) los diagramas UML.

Reflexiones de un parcial

Han pasado ya algunas semanas y es tiempo de recapitular lo que he aprendido en lo que va de este curso. Más de alguna vez he remarcado la importancia de las analogías e historias en algunos de mis post. Creo firmemente que una de las historias más importantes de las cuales puedo escribir es sobre el caminar en un constante aprendizaje. A fin de cuentas, lo que sabemos forma parte importante de lo que somos.

Para este punto, creo que lo que he aprendido han sido un par de ideas, concretas pero valiosas.

He aprendido a procrastinar con inteligencia.

Suena extraño y para este punto quizá pienses, mi querido escritor, que me he vuelto más flojo. Quizá así sea, pero a eso no me refiero.
Durante el verano pasado aprendí a medir la velocidad de mi trabajo y productividad de forma aceptable. Actualmente tengo una idea realista sobre el tiempo que tardaré en realizar mis tareas. Considero que es una herramienta valiosa, me da la oportunidad de llegar a ser consciente de mi tiempo, valorarlo y dosificarlo en las distintas áreas de mi vida. Al iniciar el semestre pensaba que al fin llegaría a ese sueño utópico de balancear mi vida. Sin embargo, luego de todos los contratiempos familiares que he tenido, me di cuenta que debía ser más creativo al gestionar mis tiempos.

Conociendo mis tiempos estimados comencé a jugar Tetris con mis tareas este semestre, acomodando mis pendientes en los huecos libres que me quedaban. Debido a que los eventos inesperados aparecían una y otra vez, tenía que mover como bloques mis ocupaciones a través de mis calendarios. Esto me llevó a procrastinar. En la clase aprendí que la rigidez y la presión por los deadlines solo generan estrés innecesario. Se muy bien que generalmente trabajo mejor sin presión. Sin embargo, a veces, cuando se tiene tiempo libre las tareas se extienden y tardan un poco más, el tener más tiempo del que necesitas para un trabajo suele ser peligroso, se termina desperdiciando el sobrante.

Procrastinar puede sonar peligroso, pero si conoces bien tu ritmo de trabajo, puede que no sea tan mala idea dejar el trabajo para… mañana.
Foto tomada por mi en calle Asunción, a un par de cuadras de Av. Patria.

Procrastinar con inteligencia implica optimizar mis recursos temporales, sin darle tantas vueltas al mismo trabajo. Procrastinar con inteligencia implica ser flexible con uno mismo y con los demás, ( eso no significa ser condescendiente o complaciente).

Procrastinar con inteligencia, me lleva inmediatamente a pensar en los ciclos de vida de software. Todo proyecto y proceso conlleva un orden. Optimizar los recursos temporales y de trabajo para ellos ( lo que generalmente resulta en un impacto económico) no es una tarea fácil. En el diseño y desarrollo de software es necesario seguir un orden bien estudiado para llevar a cabo bien los proyectos en el menor tiempo posible. Aprendí de ese tema que no existe un protocolo mágico para hacer todos los proyectos bien y en el menor tiempo posible.

A cada proyecto se le puede asignar un ciclo de vida que mejor se le adecue, según el tipo de proyecto, tamaño, recursos y duración del software ( entre otras variables). Por otro lado, se han llegado a conclusiones interesantes: se han creado pautas, reglas de diseño y procesos que son aplicables al diseño de cualquier tipo de software. Estas ideas, al ser bastante generales y flexibles son aplicables y adecuadas para diseñar nuestros productos de software. El conjunto de reglas y principios de diseño de software se han condensado en los distintos procesos unificados de software.

Aprendí que todo proyecto tiene un ciclo de vida intrínseco (muchas veces no es un proceso óptimo, como en la mayoría de procesos burocráticos), conocer las fases de dichos ciclos nos ayuda a aprender a ajustar una estrategia de producción bien probada o bien, optimizar cada una de las fases.

Aquí debo detenerme un poco. En la frase anterior mencioné la expresión «todo proyecto». Con esto me refiero a toda área de producción. El software es solamente un producto más que se beneficia del diseño. El mundo contemporáneo está inmerso de la teoría del diseño, del paradigma orientado a objetos.

Warhol – Sopas Campbell. Foto tomada en mi visita al SFMOMA.
En el cuerpo humano, en la producción industrializada y hasta el arte está inmerso del Paradigma Orientado a objetos.

Gracias a la aplicación del paradigma orientado a objetos en el software, ha sido posible generar principios y buenas prácticas de diseño para el diseño de software.
El diseño implica la representación conceptual del producto, la definición explicita de su arquitectura, a partir de las tareas y necesidades a cumplir con el producto. El panorama del problema a solucionar y las necesidades del ambiente en el cual el sistema operará, se obtienen a partir de los requerimientos, explayados ya sea por casos de uso o historias de usuario. La definición gráfica o conceptual de la arquitectura del sistemas, con la descripción de los componentes se realiza por medio de algún lenguaje de modelado.

Este parcial nos ofrece un panorama completo para iniciar el modelado de un producto. Conocemos las reglas y principios básicos para un buen diseño. Además, podemos implementar estos procesos de diseño, por medio de las herramientas y lenguajes de modelado que existen en el vasto mundo digital.

Patrones de diseño

Tengo una pequeña biblioteca digital en la cual voy guardando libros que me gustaría leer, me encanta releer o aún no consigo su copia en físico. Esa pequeña biblioteca es de temas variados, literatura, poesía, psicología, desarrollo de software, entre otros.
Platicando con un amigo, me hizo recordar esta curiosa antología y hallé el libro Patrones de Diseño, de nuestros 4 jinetes del apocalipsis de la programación orientada a objetos (Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides). ¿Coincidencia? No lo creo. Así que, decidí darle una leída al libro.

Patrones de diseño: un paradigma entero contenido en un libro.

Una leída rápida pero completa. Entre siestas y capítulos de la serie en turno lo terminé en un par de días.
Este post se tratará de una discusión sobre los interesantes conceptos ofrecidos en el libro Patrones de Diseño. Además, haré lo posible por enumerar algunos proyectos en los cuales considero he aplicado ciertos de estos patrones.

Debo de dejar claro que, luego de leer a Gamma y compañía, me doy cuenta de que en realidad, mi capacidad de diseño orientado a objetos tiene aún mucho que desear, pero haré lo posible en explicar lo que he aprendido hasta ahora.

Reflexiones introductorias

Primeramente, me he hallado con la conclusión imperante:

Reusar código es bueno.

Reusar la mayor cantidad de código posible, sabiendo cuando es posible reutilizar, es lo que caracteriza a un desarrollador experimentado. Dicen por ahí que más sabe el diablo por viejo que por diablo.

Reusar, clonar, nos evita tiempo y esfuerzo al crear.

Además, es importante que nuestros sistemas estén formados por objetos que tengan interacciones débiles entre sí, para que los objetos sean cajas negras para otros objetos. Solo envías datos y recibes respuestas. No deberías preocuparte por el comportamiento interno de otro objeto. Si existen interacciones débiles, es fácil reemplazar un objeto que no se halle atado a otros.

El objetivo entonces es identificar las características comunes en problemas, para modelar una misma solución para problemas distintas. Buscamos características comunes . . . ¿Qué no es eso la definición de un patrón?

Luego de un análisis exhaustivo de problemas comunes en el diseño de software, llegaron a la conclusión de generar soluciones de diseño que funcionan de forma general para muchos contextos.
Es decir, los patrones de diseño son soluciones generales a un conjunto de problemas comunes en el diseño (de software).
Se recomienda ampliamente comprender y aprender a usar estos patrones de diseño, a fin de cuentas, son soluciones validadas para una gran gama de problemas. Suena a mucho ahorro de tiempo.

Los patrones de diseño busca desacoplar los distintos subsistemas de software, el procesamiento de datos se realiza en objetos y subsistemas distintos a la representación de estos, por ejemplo.
Desacoplar, reusar, anidar. Es el fin último de un buen diseño en software. Para hacer un buen diseño de nuestro sistema, es necesario comprender que nuestros objetos realizarán operaciones y tendrán comportamientos. La única forma que nuestros objetos realizarán dichas operaciones es al enviarle un mensaje o petición al objeto. Estas operaciones son la única forma de cambiar la información interna del objeto y por ende, cambiar su estado actual.

Diseña interfaces, no clases:

Ha sido una frase que aparece más de una vez en la lectura. Tiene sentido, porque a fin de cuentas, el objetivo buscado es generar un sistema con comportamiento consistente. Lo importante aquí es diseñar un sistema en el cual se tengan bien conocidas las operaciones realizables por cada una de sus clases. Por ahí dicen que somos definidos por nuestras acciones, con el software no es la excepción.
Si ladra y mueve la cola, ha de ser un perro.
La implementación se vuelve secundaria ante un buen diseño. El diseño es la abstracción del sistema, la implementación su especificación. Los objetos pueden trabajar perfectamente como cajas negras, solo conociendo sus operaciones.

El diseño se enfoca en los fines, la implementación en los medios.

Definición de un patrón de diseño

Un patrón de diseño es una solución que puede ser aplicable a una gran variedad de problemas similares. Estos se han expresado a través de una serie de elementos que lo describen:

Nombre y clasificación: El nombre debería reflejar en una o dos palabras la esencia del patrón.
Propósito: Un par de frases breves que describan que es lo que hace el patrón, que problemas intenta resolver.
Aplicabilidad: Se listan situaciones comunes donde se puede aplicar el patrón. Esto ayuda a identificar pobres diseños, para modificarlos.
Estructura: Una representación gráfica de las clases.
Participantes: Las clases y objetos participando en el patrón de diseño y sus responsabilidades.
Colaboraciones: Como los participantes colaboran en cumplir sus responsabilidades
Consecuencias: Cuáles son los resultados de usar el patrón.
Implementación: Pistas, técnicas y trampas comunes debes conocer antes de implementar el patrón.
Ejemplo: Fragmentos de código que muestran como debería implementarse el patrón.
Usos conocidos: Ejemplos de sistemas reales en los cuales debería aplicarse el patrón.
Patrones relacionados: Sin sal no hay pimienta, hay patrones fuertemente relacionados con este patrón, se enumeran y se enlistan las diferencias y similitudes entre ellos. En algunas ocasiones dichos patrones son complementarios.

Catalogo de patrones

En este post hablaré de algunos de los patrones que más me gustan/interesan/conozco/ o he implementado. No hablo de todos, esta historia se está volviendo muy larga.
Hay tres tipos de patrones: Creacionales, estructurales y de comportamiento.

Patrones de Diseño Creacionales

Es importante separar las operaciones de creación, composición y comportamiento de los objetos. Los patrones de diseño creacionales se basan en la instanciación de los objetos ( o la delegación de operaciones de creación a otras clases).

Fabrica abstracta: patrón de diseño que te permite producir un conjunto de objetos relacionados sin especificar sus clases concretas. Este patrón de diseño especifica solamente tanto las operaciones como características que conllevará el objeto. Busca dejar de forma explicita las interfaces de creación de los objetos. Este patrón de diseño no se encarga de la implementación. En mi opinión considero que este patrón es útil, porque deja en claro las características generales de los objetos a instanciar.

Constructor: El constructor permite separar el proceso de instanciación de la representación del objeto. De esta forma podemos crear objetos con distintas representaciones utilizando el mismo constructor. El constructor construye de forma secuencial, paso por paso. Es uno de los patrones de diseño que más he utilizado en mi corta vida como desarrollador. De cierta forma, estamos acostumbrados a esa construcción secuencial. Construimos las paredes, luego los techos. No en otro orden. No somos maestros jedi para construir por separado techos y paredes, para luego ponerlas en su lugar simultaneamente ( o si lo somos?).

Prototipo: Especifica el tipo de objetos a crear utilizando una instancia a partir de un prototipo. Es muy común crear un nuevo documento a partir de alguna plantilla específica. La clave está en clonar un objeto modelo para crear nuestra nueva instancia. Gracias al prototipado nos ahorramos la creacíon de código extra. A fin de cuentas, tan solo copiamos y pegamos. Es útil para instanciar clases dinámicas, es decir, especificadas en tiempo de ejecución, por ejemplo.

Las plantillas nos son útiles para la creación de nuevos objetos.

Singleton: Asegura que la clase solamente tiene una instancia y proporciona un acceso global a ella. Este patrón de diseño lo he utilizado cuando se refiere a generar una única aplicación que se conecte con un servicio externo ( como podría ser una API), se utiliza un singleton para representar esta única aplicación. Imaginan que tu aplicación no sea un singleton y por error intentes instanciarla una y otra vez? Sería catastrófico.

Estructurales

Los patrones de diseño estructurales definen como interactuarán los objetos para generar estructuras más grandes. Así como en el cuerpo humano las células componen tejidos, los tejidos órganos y los órganos sistemas. Los patrones de diseño estructurales definen como un conjunto de objetos realizan una nueva funcionalidad.

Adaptador: Cuando me hallaba en mi internship el verano pasado, mi primer tarea consistió en familiarizarme con cierta API, para un servicio de chat. Dicha API contenía una documentación algo oscura y las interfaces que entregaba eran bastante generales y complejas ( los chats que ofrecían eran del tipo abierto, como un canal de conversación en Twitch o un chat en vivo de Youtube. Nosotros deseábamos acoplarlo para crear chats del estilo de Messenger, solo dos usuarios y con ciertas restricciones de acceso). Luego de un par de días, en el que me familiaricé, me di cuenta del suplicio que sería implementar dichas interfaces, una y otra vez donde se requirieran. Poseía muchos parámetros cada firma y teníamos que ajustarlos de forma específica a nuestras necesidades estáticas. Mi primer idea que me vino a la mente fue proteger a mi equipo de esos dolores de cabeza, al crear mi propia interfaz que adapte la de la API. Mi equipo utilizaba mis procedimientos y yo me peleaba con los parámetros que necesitaba la API. Sin saberlo, acababa de realizar un Adaptador.

Adaptador: transforma una interfaz en otra utilizable por el sistema.

Decorador: añade responsabilidades adicionales a un objeto de forma dinámica. Este patrón no pretende extender la clase del objeto, solo pretende agregarle nuevas responsabilidades o características. Estos adornos pueden ser independientes del objeto, por lo que pueden utilizarse en distintas clases. Es fácil entenderlo como una piel del objeto. No es el objeto ni cambia su comportamiento, pero puede cambiar sus propiedades.

Facade: Unifica las distintas interfaces de subsistemas. De esta forma, la interfaz única que observa el cliente representa el comportamiento del sistema como conjunto. Esto simplifica el uso del sistema, al darle un sentido de pertenencia a los distintos subsistemas en el sistema conjunto. Además puede ofrecer protección del cliente a las subclases internas del sistema. El facade es muy útil para darle una fachada simplificada al cliente, cuando hace uso del sistema.

Proxy: ofrece un contenedor para controlar el acceso a cierto objeto. En algunas ocasiones el objeto en cuestión es costoso de instanciar. Aplicamos una instanciación floja ( lazy instantiation), en otras palabras, lo instanciamos on demand mientras no sea instanciado, el proxy nos indicará que el objeto debería estar ahí y lo instancía cuando es necesario.

Patrones de Diseño de comportamiento

Los patrones de diseño de comportamiento se enfocan en los algoritmos y la asignación entre objetos. Los patrones de comportamiento se enfocan en la comunicación entre objetos, el encapsulamiento de responsabilidades y la delegación de estas. En tiempo de ejecución, los objetos se comunican entre sí, dando como consecuencia un flujo de control que puede volverse complicado fácilmente. Los patrones de diseño aquí se enfocan en que dichas interacciones entre objetos sean lo más débiles posibles, habiendo poco acoplamiento, los objetos trabajan cuasi independientemente, o al menos, en sus propios términos.

Cadena de responsabilidad: Evita el acoplamiento de emisor de una petición con su receptor, al dar la oportunidad a más de un objeto de responder a la solicitud. En la cadena de mando, si un objeto no puede manejar la respuesta a la solicitud, la pasa al objeto que está por encima de él en la cadena. Suena bastante comprensible, no? Este patrón de diseño se utiliza cuando más de un objeto puede manejar la petición, pero no sabemos a priori quien debe manejarlo. Por jerarquía debería asignarse automáticamente al receptor correspondiente.

Memento: ¿Alguna vez, luego de jugar un videojuego por largo tiempo lo has tenido que guardar? El patrón de diseño memento caputura y externaliza los valores de un objeto encapsulado, para luego ser restaurado a dicho estado. Más simple no lo puedo explicar.

Observer: Este patrón de diseño tiene analogía con un vocero, o la prensa. Mantiene una relación 1 a muchos, en la cual notifica a los objetos dependientes sobre el cambio del objeto observado.

Conclusiones
Luego de este ejercicio suicida de leer un libro técnico en menos de una semana ( y vaya que es una biblia el libro Patrones de Diseño) aprendí un par de cosas interesantes:

Primero: Me doy cuenta que sé menos de lo que pensaba referente a programación orientada a objetos. Lo cual, me alegra mucho. Conocer que se ignora, es una gran oportunidad de aprendizaje.

Segundo: los patrones de diseño se hallan implementados en nuestra vida cotidiana. Tengo la teoría que hemos llegado a implementarlos debido al cambio paradígmico que ha sufrido la sociedad moderna. La revolución industrial, la medicina, la arquitectura y el arte moderno fueron esos pioneros ocultos en el paradigma orientado a objetos, en mi opinión. Descubrieron el paradigma a su modo. Es por ello que actualmente, el aprendizaje de dichos patrones de diseño y el entendimiento de este paradigma es relativamente fácil de visualizar, ya que por poco más de un siglo ( al menos ) hemos vivido, sin saberlo, inmersos en él.

Tercero: El libro Patrones de diseño, no es una lectura para un fin de semana en la playa. Es un libro robusto con complejidad técnica y ejemplos no triviales. Es impresionante que The gang of four haya sido capaz de delinear estos patrones de diseño, que sean válidos y consistentes para una gran gama de casos. En lo personal me fija el objetivo de poder crear una obra de este estilo, que agrupe un conjunto de ideas que den a luz a un nuevo paradigma. Pero eso, será harina de otro costal.

Diseña un sitio como este con WordPress.com
Comenzar