Fundamentos de Programación Traducido

113
Fundamentos de Programación Construyendo Mejor Software Por Karl Seguin WWW.CODEBETTER.COM

description

Traducción al español del Foundations of Programming por Karl Seguin de www.codebetter.com licenciado bajo Creative Commons licencia Attribution-NonCommercial-Share-Alike 3.0 Unported.Trabajo de colaboración entre Comunidades Tecnológicas de Microsoft en México, entre otros, por Francisco Flamenco Emilio Reyes Antonio Ortiz Gabriel Oliva Jaime del Palacio Gabriel Flores Haarón González

Transcript of Fundamentos de Programación Traducido

Page 1: Fundamentos de Programación Traducido

Fundamentos de Programación

Construyendo Mejor Software

Por Karl Seguin

WWW.CODEBETTER.COM

Page 2: Fundamentos de Programación Traducido

Fundamentos de Programación

Página intencionalmente dejada en blanco

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

2

Page 3: Fundamentos de Programación Traducido

Fundamentos de Programación

Licencia

El libro Fundamentos de Programación está licenciado bajo la licencia Attribution-NonCommercial-Share-Alike 3.0 Unported.

Básicamente usted es libre de copiar, distribuir, y mostrar el libro. Sin embargo, pido que siempre se me atribuya el libro a mí, Karl Seguin, no lo use para fines comerciales y comparta cualquier alteración que haga bajo la misma licencia.

Usted puede ver el texto complete de la licencia en: http://creativecommons.org/licenses/by-nc-sa/3.0/legalcode

Aplicación de aprendizaje descargable

Leer acerca de código es una buena manera de aprender, pero si es como yo, nada como una aplicación real. Es por esto que he creado la Canvas Learning Application – un simple (aunque completo) sitio web ASP.NET MVC que contiene muchas de las ideas y herramientas cubiertas en este libro. La aplicación es una solución de Visual Studio 2008 con documentación en línea útil para cubrir la brecha entre teoría y práctica. La Canvas Application y este libro son independientes, así que puede hacer el acercamiento en esta ruta de aprendizaje como sea que quiera.

http://codebetter.com/blogs/karlseguin/archive/2009/05/25/revisiting-codebetter-canvas.aspx

Reconocimientos

Hay incontables personas que merecen las gracias. Este libro es una pequeña contribución al incalculable tiempo donado y conocimiento compartido por la comunidad de software en general. Sin la calidad de los libros, foros, grupos de usuarios, blogs, librerías y proyectos open source, estaría todavía tratando de hacer que mi script ASP terminara en tiempo mientras se ciclaba en un recordset (un estúpido MoveNext).

No es sorpresa que la comunidad de software ha aprovechado la aperture en internet más que otra profesión para avanzar en nuestra causa. Lo que es sorprendente es como el fenómeno parece haberse ido sin notarse. ¡Bien!

Claro, hay una persona especial sin la cual esto no hubiera ocurrido.

A Wendy,

La gente me llama suertudo por estar con alguien tan bonita e inteligente como tú. No saben ni la mitad de ello. Tú no solo eres bonita e inteligente, pero me dejas estar demasiado tiempo en mi computadora, ya sea trabajando, aprendiendo, escribiendo o jugando. Además eres por demás contenta de leer sobre mis cosas o escucharme hablar de cosas sin sentido. No te tengo el aprecio suficiente como debería.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

3

Page 4: Fundamentos de Programación Traducido

Fundamentos de Programación

Tabla of Contenido

Acerca del autor..........................................................................................................................................6ALT.NET.......................................................................................................................................................7

Objetivos.................................................................................................................................................8Simplicidad..............................................................................................................................................8YAGNI (You aren’t going to Need It – No vas a necesitarlo)....................................................................8Ultimo momento de responsabilidad......................................................................................................9DRY..........................................................................................................................................................9Explicitud y cohesión...............................................................................................................................9Acoplamiento..........................................................................................................................................9Pruebas unitarias e integración continúa..............................................................................................10En este capitulo.....................................................................................................................................10

..................................................................................................................................................................10Diseño dirigido por dominios DDD............................................................................................................11

Diseño dirigido por Dominios/Datos......................................................................................................11Usuarios, Clientes e Inversionistas.........................................................................................................12El objeto de dominio.............................................................................................................................13Interfaz de Usuario (IU).........................................................................................................................16Trucos y pistas.......................................................................................................................................17

Patrones de fábrica............................................................................................................................17Modificadores de acceso...................................................................................................................18Interfaces...........................................................................................................................................18Ocultar información y Encapsular......................................................................................................19

En este capítulo.....................................................................................................................................19..................................................................................................................................................................20Persistencia...............................................................................................................................................21

La brecha...............................................................................................................................................21DataMapper..........................................................................................................................................22

Tenemos un problema.......................................................................................................................24Limitaciones.......................................................................................................................................26

En este capítulo.....................................................................................................................................27..................................................................................................................................................................27Inyección de dependencias.......................................................................................................................28

No evites el acoplamiento como si fuera una plaga..............................................................................30Inyección de dependencias....................................................................................................................31

Constructor de inyección...................................................................................................................31Marcos de referencia.........................................................................................................................33Una última mejora.............................................................................................................................34

En este capítulo.....................................................................................................................................35..................................................................................................................................................................35Pruebas de Unidad....................................................................................................................................36

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

4

Page 5: Fundamentos de Programación Traducido

Fundamentos de Programación

¿Por qué no hacia pruebas de unidad hace 3 años?..............................................................................37Las Herramientas...................................................................................................................................38

nUnit..................................................................................................................................................38¿Qué es una prueba de unidad?............................................................................................................40Mocking.................................................................................................................................................40Más de nUnit y RhinoMocks..................................................................................................................44Pruebas de la interfaz de usuario y la base de datos.............................................................................44En este capítulo.....................................................................................................................................45

..................................................................................................................................................................45Object Relational Mappers........................................................................................................................46

El debate del infame SQL en línea vs. Los procedimientos almacenados..............................................46NHibernate............................................................................................................................................49Configuración........................................................................................................................................50Relaciones..............................................................................................................................................52Consultas...............................................................................................................................................54Carga diferida........................................................................................................................................54Descarga................................................................................................................................................55En este capítulo.....................................................................................................................................55

De regreso a las bases: Memoria.............................................................................................................56Asignación de Memoria.........................................................................................................................56

El Stack..............................................................................................................................................56El Heap..............................................................................................................................................57Apuntadores......................................................................................................................................58

Modelo de Memoria en la Práctica........................................................................................................60Boxing................................................................................................................................................60ByRef.................................................................................................................................................61Fugas de Memoria Administradas.....................................................................................................64Fragmentación...................................................................................................................................64Fijamiento..........................................................................................................................................65Asignar a null.....................................................................................................................................66

Finalización Determinística....................................................................................................................66En este capítulo.....................................................................................................................................67

..................................................................................................................................................................67 De regreso a las bases: Excepciones.........................................................................................................68

Manejando Excepciones........................................................................................................................68Registros................................................................................................................................................69Limpieza.................................................................................................................................................70Lanzar Excepciones................................................................................................................................71Mecanismo para generarlas..................................................................................................................71Cuándo Lanzar Excepciones...................................................................................................................72Creando Excepciones Personalizadas....................................................................................................73En este Capítulo.....................................................................................................................................76

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

5

Page 6: Fundamentos de Programación Traducido

Fundamentos de Programación

De regreso a las bases: Proxy Esto y Proxy Aquello.................................................................................77Patrón del Dominio del Proxy................................................................................................................78Interception...........................................................................................................................................79En éste capítulo.....................................................................................................................................81

..................................................................................................................................................................81Resumiendo...............................................................................................................................................82

Acerca del autor

Karl Seguin es un desarrollador en Epocal Corporation, un antiguo Microsoft MVP, un miembro de la comunidad influyente CodeBetter.com y un editor para DotNetSlackers. Ha escrito numerosos artículos y es un miembro active de varios grupos de usuario de Microsoft públicos. Vive en Ottawa, Ontario Canada.

Su página personal es: http://www.openmymind.net/

Su blog, además del de numerosos profesionales distinguido, está localizado en: http://www.codebetter.com/

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

6

Page 7: Fundamentos de Programación Traducido

Capítulo 1 – ALT.NET

ALT.NET

SI HAY UNA INCONFORMIDAD CON EL STATUS QUO, BIEN. SI HAY ALBOROTO, MUCHO MEJOR. SI HAY INCONFORMIDAD, ESTOY SATISFECHO. ENTONCES DEJEN QUE HAYA IDEAS, PENSAMIENTO CRÍTICO Y TRABAJO ARDUO. SI EL HOMBRE SE SIENTE PEQUEÑO, DÉJENLO QUE SE HAGA GRANDE. – HUBERT H HUMPHREY

ace algunos años fui afortunado al dar un giro en mi carrera de programación. La oportunidad de un mentor solido se presentó por sí sola y la aproveche al máximo. En un periodo de pocos meses mis habilidades en programación crecieron exponencialmente y a través de los últimos

años he continuado refinando mi arte. Sin duda, aún tengo mucho que aprender y en cinco años más veré el código que escribí el día de hoy y me sentiré avergonzado. Yo solía estar seguro de mis habilidades en programación pero solo una vez que acepte que sabía muy poco, y probablemente así era, empecé a entender.

HMi serie, Fundamentos de Programación es una colección de publicaciones orientadas en ayudar a programadores entusiastas a que se ayuden ellos mismos. A lo largo de la serie veremos un número de temas discutidos profundamente que serán muy útiles para cualquiera a excepción de los que ya los conozcan. Siempre he visto dos fuerzas dominantes en el mundo de .NET, una fuertemente conducida por Microsoft con una progresión natural de VB6 y ASP clásico (comúnmente referido como el estilo MSDN) y la otra fuertemente conducida por prácticas fundamentales de orientación a objetos e influenciados por los mejores proyectos/conceptos de Java (conocidos como ALT.Net).

En realidad, los dos no son realmente comparables. El estilo MSDN libremente define una forma específica de construir un sistema directo a cada llamado individual de un método. (Después de todo, ¿no es solamente la documentación de referencia de API la única razón por la que visitamos MSDN?). Mientras que ALT.NET se centra en tópicos más abstractos mientras provee una implementación más específica. Como Jeremy Miller lo dice: La comunidad .NET ha puesto mucho énfasis en aprender detalles de API’s, marcos de referencia y no suficiente énfasis en diseñar y fundamentos de codificación. Como ejemplo concreto y relevante, el estilo MSDN favorece fuertemente el uso de DataSets y DataTables para toda la comunicación con bases de datos. ALT.NET sin embargo, centra su discusión en patrones de diseño persistentes, desajuste en impedancia de objetos relacionales así como implementaciones específicas tales como NHibernate (Mapeo O/R), MonoRail (ActiveRecord) así como DataSets y DataTables. En otras palabras, a pesar de lo que mucha gente piensa, ALT.NET no es acerca de ALTernativas al estilo MSDN, sino que una creencia de que los desarrolladores deben saber y entender soluciones y aproximaciones alternativas de la cual el estilo MSDN forma parte.

Por supuesto, pensando en la descripción anterior es claro pensar que usar la ruta ALT.NET requiere un gran compromiso así como un conocimiento más amplio. La curva de aprendizaje es considerable y recursos útiles apenas empiezan a surgir (esta es la razón por la que decidí iniciar estas series). Sin embargo, la recompensa vale la pena; para mí, mi éxito profesional me ha llevado una felicidad personal mayor.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

7

1

Page 8: Fundamentos de Programación Traducido

Capítulo 1 – ALT.NET

ObjetivosAunque simplista, cada decisión de programación que hago está en gran parte basada en la capacidad de mantenimiento. La capacidad de mantenimiento es la piedra angular del desarrollo empresarial. Lectores frecuentes de CodeBetter están cansados de escuchar acerca de esto, pero hay una buena razón por la que hablamos de la capacidad de mantenimiento tan seguido – es la llave para ser un gran desarrollador de software. Primero, los estudios y experiencia de primera mano nos dicen que los sistemas gastan una considerable cantidad de tiempo (Más del 50%) en mantenimiento, cambios, solución a problemas o soporte. Segundo, la creciente adopción de desarrollos iterativos significa que los cambios y características son constantemente hechos para código existente (incluso si no has adoptado desarrollo iterativo tal como Agile, tus clientes probablemente continúan solicitándote hacer todo tipo de cambios.). En corto, una solución de fácil mantenimiento no solo reduce tus costos, sino también incrementa el número y la calidad de las características que vas a ser capaz de entregar.

Incluso si eres relativamente nuevo en la programación, hay una buena oportunidad de que ya te hayas formado opiniones acerca de lo que es o no es una solución de fácil mantenimiento basado en tu experiencia trabajando con otros, tomando la aplicación de alguien más o incluso tratando de arreglar algo que escribiste unos meses atrás. Una de las cosas más importantes que puedes hacer es tomar nota concienzudamente cuando algo no se vea correcto y buscar en internet por una mejor solución. Por ejemplo, aquellos que hemos gastado años programando en ASP clásico, saben que la gran integración entre código y HTML no era lo ideal.

Crear código de fácil mantenimiento no es la cosa más trivial. Al inicio, es necesario ser muy cuidadoso y con el tiempo las cosas empiezan a ser más naturales. Como puedes imaginarte, no somos los primeros en escribir sobre crear código de fácil mantenimiento. Hasta aquí, hay algunas ideologías con las que te debes de ir familiarizando. Conforme avancemos, tomate el tiempo para analizarlas más profundamente, busca en internet para obtener más información a detalle y lo más importante, trata de ver como se podrían aplicar a algún proyecto en el que hayas trabajado recientemente.

SimplicidadLa mejor herramienta para hacer código de fácil mantenimiento es conservarlo tan simple como sea posible. Una creencia común es que para que sea de fácil mantenimiento, un sistema debe de ser pre construido para acomodar cualquier solicitud de cambio posible. He visto sistemas construidos en meta-repositorios (tablas con una columna llave y una columna Valor) o configuraciones complejas en XML, que son pensadas para manejar cualquier cambio que los clientes le pidan al equipo. Estos sistemas no solo tienden a tener serias limitaciones técnicas (el desempeño puede verse reducido) pero casi siempre fallan para lo que están hechas (Veremos más de esto cuando hablemos acerca de YAGNI). En mi experiencia, el camino real para la flexibilidad es mantener el sistema tan simple como sea posible, para que tú, u otro desarrollador, pueda fácilmente leer tu código, entenderlo, y hacer los cambios necesarios. ¿Para qué construir un máquina de reglas configurable cuando todo lo que quieres que se haga es verificar que el nombre de usuario es de la longitud correcta? más adelante veremos como un desarrollo basado en pruebas nos puede ayudar a alcanzar altos niveles de simplicidad asegurándonos que nos enfocamos en lo que nuestro cliente nos pagó para hacer.

YAGNI (You aren’t going to Need It – No vas a necesitarlo)

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

8

Page 9: Fundamentos de Programación Traducido

Capítulo 1 – ALT.NET

“No vas a necesitarlo” es una creencia de programación extrema que dice que no deberías construir algo ahora porque crees que lo vas a necesitar en un futuro. La experiencia nos dice que probablemente no lo necesitarás o que necesites algo completamente diferente. Puedes gastar un mes construyendo un sistema sorprendente y flexible para un cliente que tenga 2 líneas simples de correo que sea totalmente inútil. Justo el otro día empecé a trabajar en un sistema de reporteo abierto hasta darme cuenta que entendí mal un correo electrónico y lo que el cliente realmente quería era un reporte simple diario que termino tomándome 15 minutos para construirlo.

Último momento de responsabilidadLa idea detrás del último momento de responsabilidad es que difieres construir algo hasta que lo necesites absolutamente. En verdad, en algunos casos, el último momento de responsabilidad es muy pronto en la fase de desarrollo. Este concepto está fuertemente ligado con YAGNI, debido a que incluso si realmente lo necesitas, deberías de esperar a escribirlo hasta que no puedas esperar más. Esto te da a ti y a tu cliente tiempo para estar seguro de que realmente lo necesitas después de todo y probablemente puedas reducir el número de cambios que tendrás que hacer mientras y después del desarrollo.

DRYLa duplicación de código puede provocar dolores de cabeza a los programadores. No solo hacen difícil el cambio de código (debido a que tienes que encontrar todos los lugares que hace lo mismo), además también tiene el potencial de introducir serios errores y hacerle la vida innecesariamente difícil para nuevos desarrolladores que se unan al desarrollo. Al seguir el principio No te repitas a ti mismo (DRY – Don’t Repeat Yourself) a través del ciclo de vida de un sistema (historias de usuarios, diseño, código, unidades de prueba y documentación) terminaras con un código más limpio y mucho más fácil de mantener. Ten en mente que el concepto va más allá de copiar y pegar y apunta por eliminar funcionalidad/comportamiento duplicado en todas las formas. Encapsulación de objetos y código altamente cohesivo puede ayudarnos a reducir la duplicación.

Explicitud y cohesiónSuena sencillo, pero es importante cerciorarse de que tu código haga exactamente lo que dice que va a hacer. Esto significa que las funciones y las variables deben ser nombradas apropiadamente y usar casos estandarizados y cuando sea necesario proporcionar la documentación apropiada. Una clase de Productores debe hacer exactamente lo que tú, otros desarrolladores en el equipo y tu cliente piensen que debe ser. Además, tus clases y métodos deben ser altamente cohesivos – es decir, deben tener un propósito único. Si estas escribiendo una clase Cliente que esté comenzando a manejar datos de Órdenes hay una muy alta posibilidad de que necesites crear una clase Orden. Las clases responsables de una multiplicidad de componentes distintos llegan a ser rápidamente inmanejables. En el capítulo siguiente, veremos las capacidades de la programación orientada a objetos en lo que se refiere a crear código explícito y cohesivo.

Acoplamiento El acoplamiento se produce cuando dos clases dependen una de la otra. Cuando sea posible, vas a querer reducir el acoplamiento con el fin de reducir al mínimo el impacto causado por cambios y aumentar la capacidad de prueba del código. Reducir o incluso eliminar el acoplamiento es más fácil de

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

9

Page 10: Fundamentos de Programación Traducido

Capítulo 1 – ALT.NET

lo que la mayoría de las personas piensan; existen estrategias y herramientas que te ayudarán. El truco es identificar el acoplamiento indeseable. Cubriremos el acoplamiento, en detalle más adelante.

Pruebas unitarias e integración continúa Pruebas unitarias e integración continua (comúnmente referido como CI) es otro tema que tenemos que aplazar para más adelante. Hay dos cosas que son importantes y debes de conocer de antemano. En primer lugar, ambos son primordiales para lograr nuestro objetivo de código sumamente fácil de mantener. Las Pruebas Unitarias permiten a los desarrolladores crear código con un increíble nivel de confianza. Es impresionante la cantidad de cambios en características y de refactorización que son capaces (o que estás dispuesto) a hacer cuando tienes una red de seguridad de cientos o miles de pruebas automatizadas que validan que estén funcionando bien. En segundo lugar, si no estás dispuesto a adoptar, o al menos intentar, pruebas unitarias, está perdiendo tu tiempo leyendo esto. Gran parte de lo que trataremos está encaminada a mejorar la capacidad de prueba de nuestro código.

En este capítuloAunque este capítulo no tenía nada de código, nos las hemos ingeniado para cubrir varios temas. Como quiero que esto sea más experiencia de primera mano que teoría, nos zambulliremos primero en código real a partir de aquí. Espero que ya hayamos aclarado algunas palabras comunes que has estado escuchando mucho últimamente. Los siguientes capítulos crearan los fundamentos para el resto de nuestro trabajo cubriendo OOP (programación orientada a objetos) y persistencia a un nivel alto. Hasta entonces, espero que inviertas algo de tiempo investigando algunas de las palabras claves que he lanzado. Dado que la mejor herramienta es tu propia experiencia, piensa en proyectos actuales y recientes y trata de listar cosas que no trabajaron tan bien como los que si funcionaron.

.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

10

Page 11: Fundamentos de Programación Traducido

Capítulo 2 – Diseño Dirigido por Dominios DDD

Diseño dirigido por dominios DDD

¿QUÉ ES EL DISEÑO? ES EL LUGAR DONDE SE ESTÁ CON UN PIE EN DOS MUNDOS – EL MUNDO DE LA TECNOLOGÍA Y EL MUNDO DE LA GENTE Y DE LOS PROPÓSITOS HUMANOS – Y TRATA DE MANTENER A AMBOS JUNTOS. – MITCHELL KAPOR

ra de esperarse que iniciara hablando acerca de diseño dirigido por dominios y programación orientada a objetos. En un principio pensé que podría evitar el tema al menos por un par de artículos, pero eso haría que ambos, tanto ustedes como yo, nos desanimáramos. Existe un

número limitado de maneras prácticas para diseñar el núcleo de su sistema. Un enfoque muy común para los desarrolladores de .NET que consiste en utilizar un modelo centrado en datos. Es muy probable que usted ya sea un experto en este enfoque – repeticiones anidadas dominadas, el evento ItemDataBound siempre útil y habilidades para navegar con DataRelations. Otra solución, que es la regla para los desarrolladores de Java y que rápidamente ha ido ganando terreno en la comunidad .NET, favorece al enfoque centrado en dominios.

E

Diseño dirigido por Dominios/Datos ¿Qué quiero decir al hablar de enfoque centrado en dominios? Datos-céntrico generalmente significa que enlaza su sistema entorno al conocimiento de los datos con los que estará interactuando. Es el enfoque generalizado de primero modelar la base de datos creando todas las tablas, columnas y relaciones de llaves foráneas, y después imitarlos en C#/VB.NET. La razón de que este sea tan popular entre los desarrolladores .NET es que Microsoft ha invertido mucho tiempo automatizando el proceso del imitar con DataAdapters, DataSets y DataTables. Todos sabemos que al tener una tabla con datos, podemos generar una aplicación web o una forma de Windows corriendo en menos de cinco minutos con sólo unas cuantas líneas de código. La atención se centra totalmente en los datos – lo cual es una buena idea en muchos casos. A este enfoque se le llama algunas veces desarrollo dirigido por datos.

El diseño dominio-céntrico o como es nombrado generalmente, diseño dirigido por dominios (DDD), se centra en el dominio del problema en general – lo cual no sólo involucra datos, sino todo el entorno. Entonces no sólo nos enfocamos en el hecho de que un empleado tiene un Nombre, sino que en que él puede tener un incremento de sueldo. El Dominio del Problema es una forma de expresar el negocio para el cual se está construyendo un sistema. La herramienta que empleamos es programación orientada a objetos – el emplear un lenguaje orientado a objetos como C# o VB.NET no significa que necesariamente se esté usando programación orientada a objetos (OOP)- .

Las descripciones anteriores son algo engañosas – de cierta forma implican que si utilizara DataSets no necesitaría preocuparse de, o estar preparado para ofrecer el comportamiento para incrementar el sueldo de un empleado. Seguramente ese no es el caso – de hecho ha sido muy trivial presentarlo así. Un sistema data–céntrico no está desprovisto de comportamientos ni los trata como una idea posterior. El DDD simplemente se adapta mejor a la gestión de sistemas complejos de forma más fácil de mantener por una serie de razones – que trataremos en los capítulos siguientes. Esto no lo hace mejor

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

11

2

Page 12: Fundamentos de Programación Traducido

Capítulo 2 – Diseño Dirigido por Dominios DDD

que el dirigido a datos – esto simplemente hace al dirigido a dominios mejor que al dirigido a datos en algunos casos y lo contrario también es cierto. Probablemente ha leído todo esto antes y al final, simplemente tiene que dar un salto de fe y tentativamente aceptar lo que predicamos – al menos lo suficiente para que usted pueda juzgar por sí mismo.

(Esto puede ser un tanto rudo y contradictorio a lo que dije en mi introducción, pero el debate entre el camino MSDN y el de ALT.NET podría resumirse como una batalla entre desarrollar dirigiendo por datos o desarrollar dirigiendo por dominios. No obstante de que los verdaderos ALT.NETeros deberían de apreciar que el diseño dirigido a datos es sin duda una elección adecuada en algunas situaciones. Creo que gran parte de la hostilidad entre los "campamentos" es que Microsoft favorece desproporcionadamente el diseño dirigido a datos a pesar del hecho de que no se ajusta bien con lo que la mayoría de los desarrolladores .NET están haciendo (desarrollo empresarial) y, cuando se utilizan incorrectamente, produce menos código que es más fácil de mantener. Muchos programadores, tanto dentro como fuera de la comunidad de .NET, probablemente se están rascando sus cabezas tratando de comprender por qué Microsoft insiste en ir en contra de la sabiduría convencional y mantener torpemente lo que siempre ha tenido.)

Usuarios, Clientes e Inversionistas Algo que tomo muy en serio del desarrollo ágil es la interacción cercana que el equipo de trabajo mantiene con el cliente y los usuarios. De hecho, en tanto me es posible, yo no veo un equipo de desarrollo y clientes, sino una sola entidad: el equipo. Ya sea que tenga la fortuna o no de estar en tal situación (algunas veces los abogados se interponen, algunas veces los clientes no están disponibles para acuerdos, etc.) Es importante entender lo que le corresponde a cada quien. El cliente es quien paga y como tal, debe realizar las decisiones finales acerca de características y prioridades. Los usuarios, ciertamente, utilizan el sistema. Los clientes, en ocasiones, son usuarios, pero solo en extrañas ocasiones son los únicos usuarios. Un sitio Web, por ejemplo, podría tener usuarios anónimos, usuarios registrados, moderadores y administradores. Por último, los inversionistas es cualquier persona que invierte el sistema. El mismo sitio web podría tener un sitio padre o hermano, patrocinadores o expertos en el dominio.

Los clientes tienen un trabajo muy duro. Ellos deben priorizar de forma objetiva las características que todos desean, aún aquellas que ellos mismos desean de manera objetiva y además deben hacer frente a su presupuesto finito. Evidentemente, ellos harán

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

12

En el pasado, frecuentemente hubiera querido emborracharme con mis clientes. Ellos eran molestos, no sabía lo que querían y siempre realizaban elecciones erróneas.

Sin embargo me puse a pensar, y me di cuenta de que yo no era tan inteligente como lo creía. El cliente sabía mucho más acerca de su negocio que yo. No sólo eso, el comprometía su dinero y mi trabajo era permitirle obtener el máximo provecho de él.

Así que cambiamos la relación por una más activa, colaborativa y positiva y no sólo se obtuvieron resultados en gran medida mejores, sino que la programación se convirtió divertida nuevamente.

Page 13: Fundamentos de Programación Traducido

Capítulo 2 – Diseño Dirigido por Dominios DDD

decisiones equivocadas, tal vez porque no entienden plenamente la necesidad de un usuario, tal vez porque usted cometa un error en la información que le proporcione o tal vez porque ellos darán, incorrectamente, mayor prioridad a sus propias necesidades que a cualquier otra. Como desarrollador, es su trabajo apoyarlos para que cumplan con su función.

Ya sea que esté construyendo un sistema comercial o no, la medida definitiva de su éxito consistirá en cómo se sienten los usuarios con él. Así que mientras esté trabajando estrechamente con su cliente, es de esperar que ambos se preocupen en las necesidades de los usuarios. Si usted y su cliente toman en serio la creación de sistemas para los usuarios, yo le recomiendo leer historias de usuario – un buen punto de partida es el excelente libro “User Stories Applied1” de Mike Cohn.

Por último, y como razón principal de la existencia de esta pequeña sección, están los expertos en el dominio. Expertos en el dominio son las personas que conocen todos los pormenores sobre el mundo en el que vive su sistema. Hace poco formé parte de un proyecto de desarrollo muy grande para un Instituto financiero y hubo literalmente cientos de expertos en el dominio de los cuales la mayoría eran economistas o contadores. Se trataba de personas que están tan entusiasmadas con lo que hacen así como usted lo está acerca de la programación. Cualquier persona puede ser un experto en su dominio – un cliente, un usuario, un inversionista y, eventualmente, incluso usted. Su dependencia de expertos en los dominios crece con la complejidad de los sistemas.

El objeto de dominio Como he dicho antes, la OOP es la herramienta que utilizaremos para darle vida a nuestro diseño dominio-céntrico. En especial, confiaremos en que el poder de las clases y de la encapsulación. En este capítulo nos concentraremos en los conceptos básicos referentes a las clases y en algunos trucos para empezar a usarlas – muchos desarrolladores ya conocerán todo lo que se cubre aquí. No trataremos persistencia (hablar a la base de datos) aún. Si es nuevo en este tipo de diseño, se verá a usted mismo preguntándose constantemente sobre la base de datos y sobre el código para el acceso a datos. Intente no preocuparse demasiado. En el siguiente capítulo trataremos los fundamentos de la persistencia, y en los capítulos siguientes, examinaremos la persistencia en mayor profundidad.

La idea detrás del DDD es construir el sistema de manera que refleje el dominio del problema real que está tratando de resolver. Aquí es donde entran los expertos en el dominio en acción – ellos lo ayudarán a comprender cómo funciona el sistema actualmente (incluso si se trata de un proceso manual con papel) y cómo debería trabajar. Al principio se sentirá abrumado por sus conocimientos – le hablará de lo que usted nunca ha oído hablar y será sorprendido por su aspecto atónito. Utilizará tantas siglas y palabras especiales que podrá comenzar a preguntarse si usted está o no a la altura. En última instancia, esto es el verdadero propósito de un desarrollador empresarial –comprender el dominio del problema. Ya sabe cómo programar, pero ¿sabe cómo programar el sistema de inventario para que haga lo que deben hacer? Alguien tiene que aprender el mundo de la otra persona, y si el experto en el dominio aprende a programar, nos quedaremos sin trabajo.

1 URL en Amazon: http://www.amazon.com/User-Stories-Applied-Development-Addison-Wesley/dp/0321205685

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

13

Page 14: Fundamentos de Programación Traducido

Capítulo 2 – Diseño Dirigido por Dominios DDD

Cualquier persona que pase por lo anterior sabe que comprender un nuevo negocio es la parte más complicada de cualquier trabajo de programación. Por ello, hay beneficios reales al hacer que nuestro código se parezca, en la medida de lo posible, al dominio. Esencialmente estoy hablando de comunicación. Si los usuarios están hablando de resultados estratégicos, lo cual hace un mes no significaba nada para usted y ahora su código habla de resultados estratégicos, significa que algunas de las ambigüedades y gran parte de la mala interpretación están borradas. Muchas personas, incluyéndome a mí, creemos que una buena forma de iniciar es comenzar con los sustantivos claves que utilizan sus expertos en el negocio y los usuarios. Si se estuviera creando un sistema para un concesionario de automóviles y hablara con un vendedor (que es probable que sea un usuario y un experto en el dominio), sin duda hablará de clientes, carros, modelos, paquetes y actualizaciones, pagos y así sucesivamente. Ya que son el núcleo de su negocio, es lógico que sean el núcleo de su sistema. Más allá de los sustantivos está la convergencia en el lenguaje de la empresa, que ha llegado a ser conocida como el idioma ubicuo (ubicuo significa presente en todas partes). La idea es que un único idioma compartido entre los usuarios y el sistema es más fácil de mantener y tiene menor probabilidad de ser mal interpretado.

Cómo iniciar exactamente es algo que realmente tiene que decidir usted. Hacer DDD no significa que necesariamente tiene que iniciar con el modelado del dominio (¡aunque es una buena idea!), más bien significa que debe centrarse en el dominio y dejarlo que dirija sus decisiones. Al principio muy bien puede iniciar con su modelo de datos, cuando exploremos el desarrollo dirigido por pruebas tomaremos un enfoque diferente sobre la creación de un sistema que se adapta muy bien con DDD. Por ahora, no obstante, supongamos que hemos hablado con nuestro cliente y con unos vendedores y hemos obtenido que el punto neurálgico es mantener un seguimiento de la interdependencia entre las opciones de actualización. Lo primero que haremos será crear cuatro clases:

public class Carro{} public class Modelo{} public class Paquete{} public class Actualización{}

Después agregaremos un poco de código basado en algunas cosas que hemos asumido de forma correcta y segura:

public class Carro { private Modelo _modelo; private List<Actualización> _Actualizaciones; public void Agregar(Actualización actualización){ //todo } } public class Modelo { private int _id; private int _año;

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

14

Page 15: Fundamentos de Programación Traducido

Capítulo 2 – Diseño Dirigido por Dominios DDD

private string _nombre;

public ReadOnlyCollection<Actualización> ActualizacionesDisponibles() { return null; //todo } }

public class Actualización { private int _id; private string _nombre; public ReadOnlyCollection<Actualización> ActualizacionesRequeridas { get { return null; //todo } } }

Esto es algo un tanto simple. Hemos añadido algunos campos tradicionales (id, nombre), algunas referencias (Carro y Modelo, Actualizaciones), y hemos añadido una función a la clase Carro. Por ahora podemos olvidarnos de las modificaciones y empezar a escribir un poco sobre el comportamiento real.

public class Carro { private Modelo _modelo; //todo ¿Dónde comenzar? private Lista<Actualización> _actualizaciones;

public void Agregar(Actualización actualización) { _actualizaciones.Agregar(actualización); } public ReadOnlyCollection<Actualización> DependenciasPorActualizar() { Lista<Actualización> porActualizar = new Lista<Actualización>(); foreach (Actualización actualiza in _actualizaciones) { foreach (Actualización actualizaDependencia in actualización.ActualizacionesRequeridas) { if (!_actualizaciones.Contiene(actualizaDependencia)

&& !porActualizar.Contiene(actualizaDependencia)) { porActualizar.Agregar(actualizaDependencia); } } } return porActualizar.AsReadOnly(); } }

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

15

Page 16: Fundamentos de Programación Traducido

Capítulo 2 – Diseño Dirigido por Dominios DDD

Primero, hemos implementado el método Agregar. Después hemos implementado un método que nos permite obtener todas las actualizaciones faltantes. Nuevamente, este es sólo el primer paso; el siguiente paso podría ser rastrear qué actualizaciones son las responsables de las actualizaciones faltantes, por ejemplo: usted debe seleccionar 4 X 4 que es el valor que corresponde a Tracción; sin embargo, hasta aquí llegaremos por ahora. El propósito era sólo señalar cómo podríamos comenzar y cómo se puede ver al inicio.

Interfaz de Usuario (IU)Habrá notado que aún no hablamos de las IUs. Esto es porque el dominio es independiente de la capa de presentación – se puede utilizar para alimentar un sitio Web, una aplicación Windows o un servicio de Windows. Lo último que usted desearía hacer es combinar su lógica de presentación y dominio. Si lo hace, no sólo obtendré como resultado código difícil de cambiar y difícil de probar, sino que también será imposible reutilizar nuestra lógica en múltiples interfaces de usuario (lo cual podría no ser preocupante, pero la legibilidad y capacidad de mantenimiento siempre los son). Lamentablemente, esto es exactamente lo que hacen muchos desarrolladores ASP.NET – combinar su capa de interfaz de usuario y la de dominio. Incluso yo diría que es común verlas mezcladas en el código de los eventos de dar clic a un botón ASP.NET o al cargar una página. El Framework de las páginas ASP.NET está diseñado para controlar la interfaz de usuario de ASP.NET – no para implementar el comportamiento. El evento clic del botón Guardar no debe validar las reglas del negocio complejas (o peor, afectar la base de datos directamente), su propósito más bien es modificar la página ASP.NET según los resultados de la capa de dominio. Tal vez debería redirigir al usuario a otra página, mostrar mensajes de error o solicitar información adicional.

Recuerde que desea escribir código cohesionado. Su lógica de ASP.NET debería centrarse en hacer una cosa y hacerla bien – cualquiera estará de acuerdo en que es administrar la página, lo cual significa que no puede hacer la funcionalidad de dominio. También, la lógica localizada en código oculto viola el principio de “No lo Repitas”, simplemente porque es difícil reutilizar el código dentro de un archivo aspx.cs.

A pesar de lo dicho anteriormente, usted no puede esperar demasiado para trabajar en la interfaz de usuario. Ante todo, queremos obtener retroalimentación de nuestros clientes y usuarios tan pronto como sea posible. Dudo que ellos se queden impresionados si le enviamos un montón de archivos .cs o .vb con nuestras clases. En segundo lugar, al hacer uso real de la capa de dominio se van a revelar algunos errores y deslices. Por ejemplo, la naturaleza desconectada de la web podría significar que tenemos que hacer pequeños cambios a nuestro mundo orientado a objetos puro con el fin de lograr una mejor experiencia de usuario. En mi experiencia, las pruebas unitarias son demasiado estrechas para detectar estas peculiaridades.

También le alegrará saber que ASP.NET y WinForms tratan con código dominio-céntrico, así como con clases data-céntricas. Usted puede enlazar datos a cualquier colección .NET, sesiones de uso y cachés como usted lo hacen normalmente y a hacer cualquier otra cosa a la que esté acostumbrado. De hecho, en general, el impacto en la interfaz de usuario es probablemente el menos significativo. Por supuesto, no debería sorprenderle saber que los ALT.NETeros también creen que usted debe mantener su mente

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

16

Page 17: Fundamentos de Programación Traducido

Capítulo 2 – Diseño Dirigido por Dominios DDD

abierta cuando se trata de su motor de presentación. El Framework de las páginas ASP.NET no es necesariamente la mejor herramienta para el trabajo, muchos de nosotros consideramos que es innecesariamente complicado y frágil. Hablaremos más sobre esto en un capítulo posterior, pero si está interesado en obtener más información, le sugiero que revise Mono Rieles (que es un marco de guías para .NET) o el marco MVC recientemente publicado por Microsoft. Lo último que quiero es desalentar a quien sea con tantos cambios, así que por ahora, volvamos sobre el tema.

Trucos y pistas Terminaremos este capítulo revisando algunas cosas útiles que podemos hacer con las clases. Sólo cubriremos la punta del iceberg, pero esperando que la información le ayude a levantarse con el pie derecho.

Patrones de fábrica ¿Qué hacemos cuando un cliente compra un carro nuevo? Obviamente necesitamos crear una instancia nueva del Carro y especificar el modelo. La forma tradicional de hacerlo es utilizando un constructor y simplemente instanciar un nuevo objeto con la nueva palabra clave. Un enfoque diferente es utilizar una fábrica que cree la instancia:

public class Carro { private Modelo _modelo; private Lista<Actualización> _actualizaciones;

private Carro() { _actualizaciones = new Lista <Actualización>(); } public static Carro CrearCarro(Modelo modelo) { Carro carro = new Carro(); carro._modelo = modelo; return carro; } }

Hay dos ventajas en este enfoque. En primer lugar, podemos regresar un objeto nulo, lo cual es imposible con un constructor – esto podría o no ser útil en su caso en particular. En segundo lugar, si tiene muchas formas de crear un objeto, tendrá la oportunidad de proporcionar nombres de función más significativos. El primer ejemplo que me viene a la mente es cuando quiere crear una instancia de una clase Usuario, a usted le agradaría tener Usuario.CrearPorCredenciales(string nombredeusuario, string clave), Usuario.CrearPorId(int id) y Usuario.ObtenerUsuariosPorRol (string rol). Puede realizar la misma funcionalidad con la sobrecarga del constructor, pero rara vez con la misma claridad. A decir verdad, siempre me tomo un rato en la difícil tarea de elegir cual utilizar, por lo que realmente es una cuestión de gusto y de sentimiento interior.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

17

Page 18: Fundamentos de Programación Traducido

Capítulo 2 – Diseño Dirigido por Dominios DDD

Modificadores de acceso En cuanto usted se dé a la tarea de escribir clases que encapsulen el comportamiento del negocio, una rica API emergerá para ser consumida por su interfaz de usuario. Es una buena idea mantener esta API limpia y comprensible. El método más sencillo para ello es mantener la API pequeña y ocultar todo a excepción de los métodos más necesarios. Algunos métodos obviamente requieren ser públicos y otros privados, pero si aún no está seguro, seleccione el modificador de acceso más restrictivo y cámbielo sólo cuando sea necesario. Yo hago buen uso del modificador interno en muchos de mis métodos y propiedades. Los miembros internos sólo son visibles para otros miembros en el mismo ensamblado – por lo que si usted separa físicamente sus capas a través de varios ensamblados (que generalmente es una buena idea), usted podrá minimizar considerablemente su API.

InterfacesLas interfaces jugarán un papel muy importante para crear código fácil de mantener. Las emplearemos tanto para desacoplar nuestro código como para crear clases simuladas en nuestras pruebas unitarias. Una interfaz es un contrato al cual cualquier clase implementada debe adherirse. Digamos que queremos encapsular toda la comunicación con la base de datos dentro de una clase llamada AccesoADatosSqlServer así:

internal class AccesoADatosSqlServer{ internal List<Actualización> ObtenerActualizaciones() { return null; //todo implement }}public void UnMétodoSimple (){ SqlServerDataAccess da = new AccesoADatosSqlServer (); List<Actualización> actualizaciones = da.ObtenerActualizaciones();}

Puede ver que el código de ejemplo tiene una referencia directa a AccesoADatosSqlServer – como podría hacerlo con muchos otros métodos para comunicarse con la base de datos. Este código altamente acoplado causa problemas para realizar cambios o pruebas (no podríamos realizar una prueba a UnMétodoSimple sin tener el método ObtenerActualizaciones totalmente funcional. El acoplamiento puede ser solucionado haciendo uso de una interfaz:

internal interface IAccesoADatos{ List<Actualización> ObtenerActualizaciones();}

internal class AccesoADatos{ internal static IAccesoADatos CrearInstancia()

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

18

Page 19: Fundamentos de Programación Traducido

Capítulo 2 – Diseño Dirigido por Dominios DDD

{ return new AccesoADatosSqlServer (); }}

internal class AccesoADatosSqlServer : IAccesoADatos{ public Lista<Actualización> ObtenerActualizaciones() { return null; //todo implementar }}public void UnMetodoSimple (){ IAccesoADatos da = AccesoADatos.CrearInstancia(); List<Actualización> actualizaciones = da.ObtenerActualizaciones();}

Hemos introducido la interfaz junto con una clase auxiliar para devolver una instancia de dicha interfaz. Si queremos cambiar nuestra implementación, por decir un AccesoADatosOracle, simplemente creamos la nueva clase de Oracle, nos aseguramos de implementar la interfaz y de cambiar la clase de ayuda para devolverla en su lugar. Y así, en vez de tener que hacer múltiples cambios (posiblemente cientos), simplemente tendremos que hacer uno.

Esto es sólo un ejemplo sencillo de cómo podemos utilizar las interfaces para que ayuden a nuestra causa. Podemos fortalecer el código al crear una instancia de nuestra clase a través de la configuración dinámica de datos o introduciendo un framework especialmente diseñado para el trabajo (que es exactamente lo que vamos a hacer). A menudo podremos ser favorecidos al programar con interfaces en lugar de hacerlo con clases reales, por lo que, si no está familiarizado con ellas, le sugiero hacer alguna lectura adicional.

Ocultar información y Encapsular Ocultar información corresponde al principio de que las decisiones de diseño deberían estar ocultas a otros componentes de su sistema. Por lo general es una buena idea ocultar tanto como sea posible al crear clases y componentes para que los cambios a la aplicación no afecten a otras clases y componentes. La encapsulación es una forma de implementar la información oculta en OOP. Básicamente significa sus objetos de datos (los campos) y los de la aplicación, no deberían ser accesibles a otras clases. El ejemplo más habitual es hacer campos privados con propiedades públicas. Aún mejor es preguntarse a sí mismo si incluso el campo de _id necesita ser una propiedad pública.

En este capítuloLa razón por la que existe el desarrollo empresarial es que no existe un solo producto estándar que pueda resolver con éxito todas las necesidades de un sistema complejo. Simplemente hay demasiados requisitos extraños o entrelazadas y demasiadas reglas de negocio. Hasta la fecha, no hay paradigma más adecuado para esta tarea que la programación orientada a objetos. De hecho, la OOP fue diseñada con el objetivo específico de permitir a los desarrolladores modelar las cosas de la vida real. Todavía

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

19

Page 20: Fundamentos de Programación Traducido

Capítulo 2 – Diseño Dirigido por Dominios DDD

puede ser difícil ver el valor a largo plazo del diseño dirigido por dominio. Compartir un lenguaje común con el cliente y para los usuarios, además de tener una mayor capacidad para realizar pruebas podrían no parecer necesarios. Esperemos que a medida de que avance a través de los capítulos restantes y que experimente por su cuenta, empiece a adoptar algunos de los conceptos que se ajusten a sus necesidades y a las de sus clientes.

.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

20

Page 21: Fundamentos de Programación Traducido

Capítulo 3 - Persistencia

Persistencia

LA INTENCIÓN DE CODD ERA LIBERAR A LOS PROGRAMADORES DE LA OBLIGACIÓN DE SABER LA ESTRUCTURA FÍSICA DE LOS DATOS. NUESTRA INTENCIÓN ES LIBERARLOS AÚN MÁS EVITANDO QUE TENGAN QUE SABER LA ESTRUCTURA LÓGICA. – LAZY SOFTWARE

n capítulos anteriores pudimos platicar ampliamente de DDD sin tener que hablar mucho de base de datos. Si estás acostumbrado a programar con Datasets, entonces has de tener varias dudas de como funcionaria esto. Los Datasets son grandiosos y te han resuelto demasiados problemas.

En este capítulo discutiremos sobre cómo lidiar con ‘persistencia’ usando DDD. EEscribiremos código para unir la brecha entre nuestros Objetos C# y nuestras tablas SQL. En secciones posteriores veremos alternativas más avanzadas (dos diferentes formas de mapeo O/R) que, como los Datasets, hace el trabajo difícil por nosotros. La intención de este capítulo es traer solución a las discusiones previas tratando sobre patrones de persistencia más avanzados.

La brechaComo sabes, tu programa trabaja en la memoria y requiere un lugar para guardar (o persistir) información. Hoy en día la solución favorita es una base de datos relacional. Actualmente la persistencia es un tema de suma importancia en el campo de desarrollo de software porque, sin la ayuda de patrones y herramientas, es una cosa muy difícil de llevar a cabo. Con respecto a Programación Orientada a Objetos, al reto se le ha asignado un término: Disparidad de Impedimento Relación-Objeto. Básicamente esto significa que los datos de relación no se distribuyen perfectamente en objetos y los objetos no se distribuyen perfectamente cuando se archivan datos de relación. Microsoft básicamente trata de ignorar este problema y simplemente hizo una representación de relación dentro del código de Objetos-orientados – una estrategia inteligente pero con sus pros y contras como el pobre desenvolvimiento, descontrol de abstracciones, dificultad para someterlo a prueba, y problemas para darle mantenimiento. (Por otro lado son bases de datos de orientadas a objetos que, hasta donde yo sé, no han despegado tampoco).

Antes que tratar de ignorar el problema, podemos, y debemos afrontarlo. Debemos afrontarlo para que podamos nivelar lo mejor de ambos mundos – reglas complejas de negocios implementadas en OOP y archivado & recuperación de datos vía base de datos relacional. Claro, eso es calculando que podemos cerrar la brecha. Pero ¿qué brecha exactamente? ¿Qué es esta disparidad de impedimento? Probablemente estás pensando que no puede ser tan difícil invocar datos relacionales en objetos y de vuelta a las tablas. Si lo pensaste, entonces ¡tienes toda la razón! (bueno, generalmente... por ahora asumamos que siempre será un proceso simple)

DataMapperPara proyectos pequeños con un puñado de clases de dominio y tablas de base de datos, mi sugerencia siempre ha sido escribir manualmente un código que conecte estos dos mundos. Miremos un ejemplo

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

21

3

Page 22: Fundamentos de Programación Traducido

Capítulo 3 - Persistencia

simple: La primera cosa que hacemos es expandir nuestra Clase de Actualización (nos estamos enfocando solamente en las porciones de datos de nuestra Clase (los campos) desde que es donde reside la Persistencia):

public class Actualización{ private int _id; private string _nombre; private string _descripción; private decimal _precio; private List<Actualización> _actualizacionesRequeridas;

public int Id { get { return _id; } internal set { _id = value; } } public string Name { get { return _name; } set { _name = value; } } public string Descripción { get { return _descripción; } set { _descripción = value; } } public decimal Precio { get { return _precio; } set { _precio = value; } }

public List<Actualización> ActualizacionesRequeridas { get { return _actualizacionesRequeridas; } }}

Hemos incluido los campos básicos que esperabas ver en la Clase. A continuación crearemos la tabla que almacenará, o persistirá, la información de Actualización.

CREATE TABLE Actualiaciones(

Id INT IDENTITY(1,1) NOT NULL PRIMARY KEY,[Nombre] VARCHAR(64) NOT NULL,Descripción VARCHAR(512) NOT NULL,Precio MONEY NOT NULL,

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

22

Page 23: Fundamentos de Programación Traducido

Capítulo 3 - Persistencia

)

Sin sorpresas todavía. Ahora viene la parte interesante (relativamente hablando), comenzamos a construir nuestra capa de acceso de datos, que se sitúa entre el dominio y el modelo relacional (se excluyeron las interfaces por propósitos de brevedad)

internal class SqlServerDataAccess{ private readonly static string _connectionString = "FROM_CONFIG"

internal List<Actualización> ObtenerTodoslosUpgrades() { //use a sproc if you prefer string sql = "SELECT Id, Nombre, Descripción, Precio FROM Actualizaciones"; using (SqlCommand command = new SqlCommand(sql)) using (SqlDataReader dataReader = ExecuteReader(command)) { List<Actualización> actualizaciones = new List<Actualización>(); while (dataReader.Read()) { upgrades.Add(DataMapper.CrearActualización(dataReader)); } return upgrades; } }

private SqlDataReader ExecuteReader(SqlCommand command) { SqlConnection connection = new SqlConnection(_connectionString); command.Connection = connection; connection.Open(); return command.ExecuteReader(CommandBehavior.CloseConnection) }}

ExecuteReader es un método de ayuda que ligeramente reduce el código redundante que tenemos que escribir.

RetrieveAllUpgrades es mas interesante ya que selecciona todas las actualizaciones y las carga en una lista vía la función DataMapper.CreateUpgrade.

CreateUpgrade, mostrado a continuación, es un código re-utilizable que usamos para bosquejar la información de la actualización archivada en la base de datos dentro de nuestro dominio. Es directo porque el modelo del dominio y el modelo de los datos son similares.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

23

Page 24: Fundamentos de Programación Traducido

Capítulo 3 - Persistencia

internal static class DataMapper{ internal static Actualización CrearActualización(IDataReader dataReader) { Actualización actualización = new Actualización(); actualización.Id = Convert.ToInt32(dataReader["Id"]); actualización.Name = Convert.ToString(dataReader["Nombre"]); actualización.Descripción = Convert.ToString(dataReader["Descripción"]); actualización.Price = Convert.ToDecimal(dataReader["Precio"]); return actualización; }}

Si lo necesitamos, podemos re-utilizar CrearActualización tantas veces como sea necesario. Por ejemplo, nos gustaría tener la habilidad de recuperar Actualizaciones por ID o por Precio – ambos serán nuevos métodos en la Clase SqlServerDataAccess –

Obviamente, podemos aplicar la misma lógica cuando queramos archivar objetos Actualización de regreso en la tabla, aquí te damos una posible solución:

internal static SqlParameter[] ConvertirActualizaciónAParametro(Actualización actualización){ SqlParameter[] parameters = new SqlParameter[4]; parameters[0] = new SqlParameter("Id", SqlDbType.Int); parameters[0].Value = actualización.Id;

parameters[1] = new SqlParameter("Nombre", SqlDbType.VarChar, 64); parameters[1].Value = actualización.Name;

parameters[2] = new SqlParameter("Descripción", SqlDbType.VarChar, 512); parameters[2].Value = actualización.Description;

parameters[3] = new SqlParameter("Precio", SqlDbType.Money); parameters[3].Value = actualización.Price; return parameters;}

Tenemos un problemaA pesar de que hemos tomado un ejemplo muy común y simple, todavía caemos en la temida Disparidad de Impedimento. Note que nuestra capa de acceso de datos (SqlServerDataAccess o DataMapper) no maneja la tan necesaria colección ActualizacionesRequeridas. Esto es porque una de las cosas más difíciles de manejar son relaciones. En el mundo de los dominios estas son referencias (o una colección de referencias) hacia otros objetos; donde el mundo relacional usa claves externas. Esta diferencia es una constante que se inclina del lado de los desarrolladores.

La solución no es tan difícil. Primero agregaremos una tabla de unificación que asociará las actualizaciones con otras actualizaciones que sean requeridas (puede ser 0, 1 o más).

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

24

Page 25: Fundamentos de Programación Traducido

Capítulo 3 - Persistencia

CREATE TABLE DependenciasActualizaciones ( IdActualización INT NOT NULL, ActualizaciónRequeridaId INT NOT NULL, )

A continuación modificamos RecuperarTodaslasActualizaciones para cargar las actualizaciones requeridas:

internal List<Actualización> RecuperarTodaslasActualizaciones(){ string sql = @"SELECT Id, Nombre, Descripción, Precio FROM Actualizaciones; SELECT IdActualización, ActualizaciónRequeridaId FROM DependenciasActualizaciones"; using (SqlCommand command = new SqlCommand(sql)) using (SqlDataReader dataReader = ExecuteReader(command)) { List<Upgrade> actualizaciones = new List<Actualización>(); Dictionary<int, Actualización> localCache = new Dictionary<int, Actualización>(); while (dataReader.Read()) { Actualización actualización = DataMapper.CrearActualización(dataReader); actualizaciones.Add(actualización); localCache.Add(actualización.Id, actualización); }

dataReader.NextResult(); while (dataReader.Read()) { int IdActualización = dataReader.GetInt32(0); int ActualizaciónRequeridaId = dataReader.GetInt32(1); Actualización actualización; Actualización requerida; if (!localCache.TryGetValue(actualizaciónId, out actualización)

|| !localCache.TryGetValue(actualizaciónRequeridaId, out requerida)) { //sería buena idea lanzar una excepción continue; } actualización.ActualizacionesRequeridas.Add(actualizaciónRequerida); } return actualizaciones; } }

Llamamos la tabla de unificación extra junto con nuestra solicitud inicial y creamos un diccionario local de búsqueda para acceso rápido a nuestras actualizaciones por su ID. Lo siguiente es que repetimos a través de la tabla de unificación, obtenemos las actualizaciones apropiadas de la búsqueda en el diccionario y las adherimos a la colección.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

25

Page 26: Fundamentos de Programación Traducido

Capítulo 3 - Persistencia

No es la solución más elegante, pero trabaja muy bien! Tendremos la oportunidad de re-factorizar las funciones un poco más para hacerla un poco más entendible, pero por ahora y para este simple caso, hará el trabajo.

LimitacionesAunque solo estamos haciendo una observación inicial al bosquejo. Vale la pena observar las limitaciones a las que nos hemos sometido. Una vez que continúe el camino de escribir manualmente esta clase de código fácilmente se puede escapar de las manos. Si queremos agregar métodos de Filtro/Orden nos conduciría a escribir SQL dinámica o tendríamos que escribir demasiados métodos. Terminaríamos escribiendo un número exagerado de métodos RecuperarActualizacionesPorX que luciría tediosamente similar uno del otro.

A menudo deseará tener relaciones de carga-fácil. Esto es, en vez de cargar todas las actualizaciones requeridas al inicio, quizá solo queremos cargarlas cuando sea necesario. En este caso no es un problema mayor desde que es solo una referencia de 32bit. Un mejor ejemplo sería el Modelo Relacional de Actualización. Es relativamente sencillo implementar las cargas fácilmente, pero como los mencionamos anteriormente, es demasiada repetición de código.

El asunto más importante tiene que ver con la identidad. Si llamamos RecuperarTodaslasActualizaciones dos veces, debemos distinguir entre las instancias de Actualización, esto puede desembocar en inconsistencias, dado que:

SqlServerDataAccess da = new SqlServerDataAccess();Actualización actualización1a = da.RecuperarTodaslasActualizaciones()[0];Actualización actualización1b = da.RecuperarTodaslasActualizaciones()[0];

actualización1b.Precio = 2000;actualización1b.Guardar();

El cambio de precio de la primera actualización no será reflejado en la instancia señalada por actualización1a. En algunos casos eso no sería un problema. Sin embargo, en muchas situaciones desearías que tu capa de acceso de datos lleve registro de la identidad de las instancias ya que crea y fortalece alguna clase de control (puedes informarte mejor vía Google buscando Identificar Mapas de Patrones)

Existen posiblemente más limitaciones, pero la última de la que hablaremos tiene que ver con unidades de trabajo (infórmate más usando Google para buscar el patrón ‘Unidades de Trabajo’)

Esencialmente cuando creas tu código manualmente para tu capa de acceso de datos, tienes que asegurarte que cuando se persiste un objeto tú también persistes, si es necesario, actualiza objetos con referencia. Si se está trabajando en la sección administrativa de nuestro sistema de auto ventas, es deberá por ejemplo crear una nuevo Modelo y agregar una nueva Actualización. Si se llama a Guardar en tu Modelo, se necesita asegurar que tu Actualización sea también grabada. La solución más simple es utilizar Guardar seguido por cada acción individual – pero esto puede ser ambas, difícil (relaciones

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

26

Page 27: Fundamentos de Programación Traducido

Capítulo 3 - Persistencia

pueden tener varios niveles de profundidad) e ineficiente. Igualmente deberás cambiar solamente algunas propiedades y entonces tener que decidir entre volver a grabar todos los campos, o de alguna manera llevar registro de los cambios de propiedades y actualizarlas.

Cabe recordar que para sistemas pequeños, esto no representa gran problema, pero para proyectos grandes, es casi imposible codificar manualmente (además es preferible invertir en la funcionalidad que el cliente solicitó, que perder tu tiempo tratando de implementar tu propia Unidad de Trabajo).

En este capítuloAl final de cuentas, no dependeremos de bosquejar manualmente – no es lo suficientemente flexible y terminaríamos desperdiciando demasiado tiempo escribiendo código que es inútil para el cliente. Sin embargo, es importante ver la función de bosquejar en acción – y aun así que decidimos practicar con un ejemplo simple, nos encontramos con varios inconvenientes. Desde que bosquejar de esta manera es totalmente directo, lo más importante es que entiendas las limitaciones que tiene este método. Imagínate que podría pasar si dos instancias distintas de la misma información están presentes flotando en tu código, o que rápido se expandiría tu capa de acceso de datos cuando se incrementen los requisitos.

No revisaremos la persistencia por algunos capítulos – pero cuando sea necesario re-examinaremos todo el potencial que contiene este método.

.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

27

Page 28: Fundamentos de Programación Traducido

Capítulo 4 – Inyección de dependencias

Inyección de dependencias.

YO DIRÍA QUE LA INGENIERÍA DE SOFTWARE MODERNA ES EL REFINAMIENTO EN CURSO DE LOS SIEMPRE CRECIENTES NIVELES DE ACOPLAMIENTO. SIN EMBARGO, MIENTRAS LA HISTORIA DEL SOFTWARE NOS MUESTRA QUE EL ACOPLAMIENTO ES MALO, TAMBIÉN SUGIERE QUE EL ACOPLAMIENTO ES INEVITABLE. UNA APLICACIÓN TOTALMENTE DESACOPLADA ES TOTALMENTE INÚTIL YA QUE NO AGREGA VALOR. LOS DESARROLLADORES SOLAMENTE PUEDEN AGREGAR VALOR ACOPLANDO COSAS. EL ACTO DE ESCRIBIR CÓDIGO POR SÍ MISMO ES ACOPLAR UNA COSA CON OTRA. LA VERDADERA PREGUNTA ES CÓMO ESCOGER INTELIGENTEMENTE A QUE ACOPLARSE. - JUVAL LÖWY

s común escuchar a desarrolladores promover el uso de capas como un método para proveer extensibilidad. El ejemplo más común, y uno que usé en el Capítulo 2 cuando estábamos revisando las interfaces, es la habilidad de cambiar la capa de acceso a datos para conectarte a

una base de datos distinta. Si tus proyectos no se parecen a los míos, de seguro tú ya sabes qué base de datos vas a utilizar y no vas a tener que cambiarla. De seguro podrías darle esa flexibilidad por adelantado - Por si acaso – pero que me dices de “mantén las cosas simples” y “No vas a necesitarlo” (YAGNI por sus siglas en inglés)

EYo estaba acostumbrado a escribir sobre la importancia de las capas de dominio para tener reusabilidad a través de diferentes capas de presentación: Sitios web, aplicaciones Windows y Servicios Web. Irónicamente, rara vez he tenido que escribir múltiples capas de presentación para una capa de dominio. Yo sigo creyendo que el desarrollo en capas es importante, pero mi razonamiento ha cambiado. Hoy veo el desarrollo basado en capas como algo natural por producto con código de alta cohesión con al menos algunas ideas alrededor de acoplamiento. Es decir, si haces las cosas correctamente, debería de salir automáticamente tu desarrollo en capas.

La verdadera razón por la cual estamos invirtiendo un capítulo completo a desacoplamiento (el desarrollo en capas es una implementación de alto nivel de desacoplamiento) se debe a que es un ingrediente clave para poder escribir código que se puede probar. No fue hasta que comencé con pruebas unitarias que me di cuenta lo enredado y frágil que era mi código. Rápidamente me quedé frustrado porqué el método X dependía de una función en la clase Y que necesitaba una Base de datos activa y funcional. Para prevenir los dolores de cabeza que sufrí, vamos a cubrir acoplamiento y después hablaremos sobre pruebas unitarias en el siguiente capítulo.

(Algo más sobre “no vas a necesitarlo” YAGNI. Mientras varios desarrolladores consideran que es una regla dura, yo normalmente pienso en ella como una guía. Hay muy buenas razones por las cuales quieras ignorar YAGNI, la más obvia es tu propia experiencia. Si tú sabes que algo va a ser difícil de implementar después, sería una buena idea implementarlo ahora, o al menos poner las bases. Esto es algo que yo hago frecuentemente con el almacenamiento en caché, creando una implementación de

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

28

4

Page 29: Fundamentos de Programación Traducido

Capítulo 4 – Inyección de dependencias

ICacheProvider y NullCacheProvider que no hacen nada, a excepción de darme las bases necesarias para una implementación real en el futuro. Dicho esto, de las numerosas implementaciones de directrices que existen, YAGNI, DRY y Paso sostenible son fácilmente las tres que considero más importantes.)

Un vistazo a las pruebas unitariasHablando de acoplamiento con respecto a las pruebas unitarias es muy parecido como el problema del huevo y la gallina. Yo creo que es mejor continuar con acoplamiento y después hablaremos de los fundamentos de pruebas unitarias. Lo más importante de las pruebas unitarias es la unidad. No se enfocan en pruebas de principio a fin, sino más bien en el compartimiento individual. La idea es que puedas probar cada comportamiento de cada método exhaustivamente y probar su interacción con otro, esto te da un sistema sólido. Esto es complicado ya que el método que quieres probar unitariamente puede tener dependencias con otras clases que no pueden ser ejecutadas fácilmente dentro del contexto de una prueba (por ejemplo una base de datos o un componente del navegador). Por esta razón pruebas unitarias hace uso de clases simuladas – o clases fingidas.

Veamos un ejemplo, guardar el estado de un auto:

public class Auto{ private int _id; public void Guardar() { if (!EsValido()) { //Hacer: crear un mejor manejador de Excepciones throw new InvalidOperationException("El auto debe estar en un estado válido"); } if (_id == 0) { _id = AccesoaDatos.CrearInstancia().Guardar(this); } else { AccesoaDatos.CrearInstancia().Guardar(this); } }

private bool EsValido() { //Hacer: asegurarse que el objeto esta en un estado válido return true; }}

Para probar efectivamente el método Guardar, hay 3 cosas que debemos hacer:

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

29

Page 30: Fundamentos de Programación Traducido

Capítulo 4 – Inyección de dependencias

1. Asegurarnos que la excepción adecuada es producida cuando intentamos guardar un Auto en estado inválido,

2. Asegurarnos que el método de Accesoadatos Guardar es llamado cuando es un Auto nuevo, y3. Asegurarnos que el método Actualizar es llamado cuando es un Auto existente.

Lo que no queremos hacer (que es tan importante como lo que queremos hacer), es probar la funcionalidad de EsValido o de las funciones de Accesoadatos Guardar y Actualizar (otras pruebas se encargarán de validarlas). El último punto es importante – todo lo que tenemos que hacer es asegurarnos que estas funciones son llamadas con los parámetros adecuados y que los valores de retorno (en caso de tenerlos) son manejados de manera adecuada. Es difícil encapsular tu cabeza con los conceptos de clases simuladas sin un ejemplo concreto, pero los marcos de referencia de simulación de clases nos van a permitir interceptar las llamadas a Guardar y Actualizar, asegurar que los argumentos adecuados son enviados, y forzar que se regrese el valor que queremos los marcos de referencia de simulación son efectivos y divertidos… a menos que tu código este altamente acoplado.

No evites el acoplamiento como si fuera una plagaEn caso de que hayas olvidado el Capítulo 1, acoplamiento es simplemente lo que llamamos cuando una clase requiere otra clase para poder funcionar. Es esencialmente una dependencia. Todo, incluso las más pequeñas líneas de código tienen dependencias con otras clases. Asi es que si escribes string site = “MejorCódigo”, estas acoplando la clase System.String – Si esta cambia es muy probable que tu código deje de funcionar. Por supuesto lo primero que tienes que saber es que en la gran mayoría de los casos, incluso un ejemplo tan tonto como el de la cadena de texto el acoplamiento no es algo malo. No queremos crear interfaces y proveedores para cada una de nuestras clases. Está bien usarla para la clase Auto para mantener una referencia directa a la clase Actualizar –

en este punto sería muy complicado introducir una interface IActualizar. Lo que no está bien hacer es acoplarse a un componente externo (Base de datos, estado del servidor, cache del servidor, servicio web), cualquier código que requiera una configuración extensiva (esquemas de bases de datos) y, como yo aprendí en mi último proyecto, cualquier código que genera salidas aleatorias (generación de contraseñas, generadores de claves). Esto puede sonar como una descripción vaga, pero después de este capítulo y el siguiente y después de que hayas jugado con las pruebas unitarias por ti mismo, tendrás una buena sensación sobre qué cosas deberás evitar, y cuáles no.

Ya que siempre es una buena idea desacoplar tu base de datos del dominio, usaremos este ejemplo durante este capítulo.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

30

Como se hizo mención de Juval al principio de este capítulo, yo estoy tentado a dar mi voto a favor del desacoplamiento como la necesidad más grande para las aplicaciones modernas.

De hecho, en adición a los beneficios citados de las pruebas unitarias, me he dado cuenta que la ventaja más inmediata esta en ayudar a los desarrolladores a aprender los patrones del buen y mal acoplamiento.

Page 31: Fundamentos de Programación Traducido

Capítulo 4 – Inyección de dependencias

Inyección de dependencias

En el capítulo 2 vimos como las interfaces pueden ayudar nuestra causa – sin embargo, este código no nos permite dinámicamente proveer una implementación simulada de IAccesoaDatos que sea regresada por la fábrica de clases AccesoaDatos. Para poder lograrlo, vamos a utilizar un patrón llamado Inyección de Dependencias (ID). ID es diseñado específicamente para este tipo de situaciones, ya que como su nombre lo indica, es un patrón que cambia una dependencia escrita en código compilado en algo que puede ser inyectado en tiempo de ejecución. Vamos a revisar dos formas de ID, una creada manualmente, y otra que mejora una librería de terceros.

Constructor de inyecciónLa forma más simple de ID es el constructor de inyección – lo que hace es inyectar las dependencias vía un constructor de clases. Primero, veamos nuestra interface de AccesoaDatos otra vez y creemos una implementación (simulada) falsa (no te preocupes, no tienes que crear implementaciones simuladas de cada componente, pero por ahora nos va a ayudar a mantener las cosas simples):

internal interface IAccesoaDatos{

int Guardar(Auto auto);void Actualizar(Auto auto);

}

internal class AccesoaDatosSimulado : IAccesoaDatos{

private readonly List<Auto> _auto = new List<Auto>();

public int Guardar(Auto auto){

_auto.Add(auto); return _auto.Count;

}

public void Actualizar(Auto auto){ _auto[_auto.IndexOf(auto)] = auto;}

}

Aunque nuestra función simulada de Actualizar, podría ser mejorada, por el momento nos funciona bien. Ya que tenemos esta clase falsa, solo necesitamos hacer una pequeña modificación a la clase Auto:

public class Auto{ private int _id; private IAccesoaDatos _ProveedordeDatos;

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

31

Page 32: Fundamentos de Programación Traducido

Capítulo 4 – Inyección de dependencias

public Auto() : this(new SqlServerDataAccess()) { } internal Auto(IAccesoaDatos ProveedordeDatos) { _ProveedordeDatos = ProveedordeDatos; }

public void Guardar() { if (!EsValido()) { //Hacer: crear un mejor manejador de Excepciones throw new InvalidOperationException("El auto debe estar en un estado válido"); } if (_id == 0) { _id = _ ProveedordeDatos.Guardar(this); } else { _ ProveedordeDatos.Actualizar(this); } }}

Vamos a revisar el código y sigámoslo paso a paso. Date cuenta en el uso de la sobrecarga del constructor la cual al introducir ID no tiene ningún impacto en el código existente – si decides no inyectar una instancia IAccesoaDatos, la implementación inicial es utilizada. Por otro lado, si queremos inyectar una implementación específica, como una instancia de AccesoaDatosSimulado podemos hacerlo así:

public void CasiunaPrueba(){ Auto auto = new Auto(new AccesoaDatosSimulado()); auto.Save(); if (auto.Id != 1) { //algo falló }}

Hay variaciones menores disponibles – Pudimos haber inyectado un IAccesoaDatos directamente en nuestro método Guardar o asignar el campo privado _AccesoaDatos a través de una propiedad interna – Lo que uses depende más de tu gusto personal.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

32

Page 33: Fundamentos de Programación Traducido

Capítulo 4 – Inyección de dependencias

Marcos de referenciaHacer ID manualmente funciona perfecto en algunos casos, pero sería desastroso en situaciones más complejas. Un proyecto en el que trabajé recientemente tenía muchos componentes fundamentales que tenían que ser inyectados – uno para el manejo del cache, uno para registro, uno para la base de datos y otro para un servicio web. Las clases se contaminaron con múltiples constructores sobrecargados y demasiados tenían que ver con configurar la clases para pruebas unitarias. Ya que ID es crítico para las pruebas unitarias, y la mayoría de las personas que usan pruebas unitarias aman las herramientas de código abierto, no es sorpresa que exista un buen número de marcos de referencia para ayudar a automatizar la ID. El resto de este capítulo nos vamos a enfocar en StructureMap, un marco de referencia para Inyección de Dependencias creado por mi compañero en CodeBetter Jeremy Miller (http://structuremap.sourceforge.net/)

Antes de utilizar StructureMap debes configurarlo usando un archive de XML (llamado StructureMap.config) o agregando atributos a tus clases. La configuración básicamente dice esta es la interfaz que quiero programar y esta es la implementación inicial. La configuración más sencilla para echar a andar StructureMap es algo así:

<StructureMap><DefaultInstance PluginType="CodeBetter.Foundations.IDataAccess, CodeBetter.Foundations" PluggedType="CodeBetter.Foundations.SqlDataAccess, CodeBetter.Foundations"/> </StructureMap>

Ya que no quiero gastar mucho tiempo hablando de configuración, es importante que sepas que el archivo XML se debe encontrar en la carpeta /bin dentro de tu aplicación. Puedes automatizarlo en VS.NET seleccionando los archivos, y después ve a Propiedades y configurar el atributo Copiar al directorio destino para que quede como Copiar Siempre. (Hay una gran variedad de opciones de configuración disponibles. Si te interesa aprender más sobre esto, te recomiendo que visites el sitio de StructureMap ).

Ya que lo tenemos configurado, podemos deshacer todos los cambios que hicimos a nuestra clase Auto para permitir la inyección del constructor (quita el campo _ProveedordeDatos, y los constructores). Para obtener la implementación correcta de IAccesoaDatos, Solamente necesitamos pedírsela a StructureMap, el método Guardar ahora se ve así:

public class Auto{ private int _id;

public void Guardar() {

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

33

Page 34: Fundamentos de Programación Traducido

Capítulo 4 – Inyección de dependencias

if (!EsValido()) { //Hacer: crear un mejor manejador de Excepciones throw new InvalidOperationException("El auto debe estar en un estado válido"); }

IAccesoaDatos AccesoaDatos = ObjectFactory.GetInstance<IAccesoaDatos>(); if (_id == 0) { _id = AccesoaDatos.Guardar(this); } else { AccesoaDatos.Actualizar(this); } }}

Para usar una simulación en lugar de la implementación original, solamente tenemos que inyectar la simulación dentro de StructureMap:

public void CasiunaPruea(){ ObjectFactory.InjectStub(typeof(IAccesoaDatos), new AccesoaDatosSimulado()); Auto auto = new Auto(); auto.Save(); if (auto.Id != 1) { //AlgoFalló } ObjectFactory.ResetDefaults();}

Usamos InjectStub para que las siguientes llamadas a GetInstance regresen nuestra simulación, y nos aseguramos que todo regrese a su estado original usando ResetDefaults.

Los marcos de referencia como StructureMap son fácil de utilizar y además de mucha utilidad. Con un par de líneas de configuración y unos cambios menores en tu código, disminuyes de manera considerable el acoplamiento lo que incrementa la facilidad para ejecutar pruebas. He llevado StructureMap en proyectos muy grandes y lo he implementado en cuestión de minutos – el impacto es menor.

Una última mejoraCon la introducción de la clase IAccesoadatos así como del uso del marco de referencia de ID, hemos logrado remover la mayoría del mal acoplamiento que se encontraba en nuestro ejemplo. Pudimos haberlo mejorado aún más, incluso a un punto donde pueda hacernos más daño que ayudarnos. Existe

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

34

Page 35: Fundamentos de Programación Traducido

Capítulo 4 – Inyección de dependencias

una última dependencia que me gustaría ocultar – nuestros objetos de negocio estarían mejor que no supieran de nuestra implementación especifica de ID. En lugar de llamar ObjectFactory de StructureMap directamente, le vamos a agregar un nivel más de indireccionamiento:

public static class DataFactory{ public static IAccesoaDatos CreateInstance { get { return ObjectFactory.GetInstance<IAccesoaDatos>(); } }}

De nuevo, gracias a un cambio menor, tenemos la posibilidad de hacer cambios masivos (seleccionando diferentes marcos de referencia de ID) de manera adecuada en el desarrollo de nuestra aplicación fácilmente.

En este capítuloReducir el acoplamiento es una de esas cosas que es fácil de implementar y que genera grandes resultados enfocados a nuestra búsqueda por la facilidad de mantenimiento. Todo lo que se requiere es un poco de conocimiento y disciplina – por supuesto las herramientas no hacen daño. Debería de ser obvio por qué querer disminuir las dependencias entre componentes de nuestro código – especialmente entre los componentes que son responsables de diferentes aspectos del sistema (interfaz de usuario, dominio y datos son los 3 más obvios). En el capítulo siguiente veremos lo que son las pruebas unitarias que van a incrementar los beneficios de la inyección de dependencias. Si estas teniendo problemas en entender lo que son la inyección de dependencias, te recomiendo que cheques el artículo de DotNetSlackers al respecto.

.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

35

Page 36: Fundamentos de Programación Traducido

Capítulo 5 – Pruebas de Unidad

Pruebas de Unidad

ESCOGEMOS NO HACER PRUEBAS DE UNIDAD PORQUE NOS DA MÁS GANANCIAS EL NO HACERLO! - (COMPAÑÍA DE CONSULTARÍA ALEATORIA)

lo largo de este libro hemos hablado de la importancia de la habilidad de probar nuestro código y hemos visto algunas técnicas que nos facilitan el probar nuestro sistema. Uno de los grandes beneficios de escribir pruebas para nuestro sistema es la habilidad de entregarle al cliente un

producto de mejor calidad. Si bien esto es cierto también para las pruebas de unidad, la principal razón por la que yo escribo pruebas de unidad es porque, la mejor forma de facilidad el mantenimiento y evolución de nuestro sistema es teniendo un set pruebas de unidad bien escritas. Constantemente escuchamos a los promotores de las pruebas de unidad hablar de qué tanta confianza les ha dado tener este tipo de pruebas, y eso es exactamente de lo que se trata. En un proyecto en el que estoy trabajando, estamos constantemente haciendo arreglos y mejoras al sistema (mejoras funcionales, de velocidad, refactorizar, etc.); siendo un sistema relativamente grande, deberían existir cambios que nos aterroricen. ¿Es posible hacerlo? ¿Tendrá efectos secundarios extraños? ¿Qué errores serán introducidos? Sin nuestro set de pruebas de unidad, tal vez nos rehusaríamos a hacerlos. Pero sabemos, al igual que nuestros clientes, que los cambios riesgosos son los que tienen mayor potencial de éxito. El tener más de 700 pruebas de unidad que corren en un par de minutos, nos permite romper los componentes, reorganizar el código y construir funcionalidades en las que no pensamos hace un año sin tener que preocuparnos demasiado. Tenemos confianza en que nuestras pruebas de unidad están completas (pruebas completamente nuestro sistema) y sabemos que es poco probable que introduzcamos errores en nuestro ambiente de producción; si, nuestros cambios pueden introducir errores, pero los detectaremos inmediatamente.

A

Las pruebas de unidad no sólo son para mitigar el riesgo de cambios peligrosos. En mi vida de programador, también he sido responsable de errores mayores causados por cambios que parecían de poco riesgo. El punto es que puedo hacer una cambio menor o realmente fundamental en el sistema, correr el set de pruebas de unidad desde el IDE y en menos de 2 minutos saber en dónde estoy parado.

No puedo enfatizar demasiado en la importancia de las pruebas de unidad. Sí, ayudan a encontrar errores y a validar que mi código hace que lo debe hacer, pero mucho más importante es su habilidad mágica de revelar defectos fatales o partes invaluables en el diseño del sistema. Me emociono siempre que encuentro un método o funcionalidad que es increíblemente difícil de probar ya que quiere decir que probablemente he encontrado un defecto en alguna parte fundamental del sistema que pudo haber sido ignorada hasta que nos pidieran un cambio inesperado. De igual forma, cuando escribo una prueba en un par de segundos para algo que creía que iba a ser difícil de probar, sé que alguien en nuestro equipo escribió código que es reusable para otros proyectos.

¿Por qué no hacía pruebas de unidad hace 3 años?Para los que hemos descubierto la alegría de hacer pruebas de unidad, es difícil entender por qué no están haciéndolo todos. Para los que no las han adoptado, probablemente desearías que nos

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

36

5

Page 37: Fundamentos de Programación Traducido

Capítulo 5 – Pruebas de Unidad

calláramos. Por muchos años he leído blogs y platicado con colegas que realmente estaban involucrados en hacer pruebas de unidad, pero yo no lo hacía. En retrospectiva, estas son las razones por lo que me tomó tanto tiempo subirme al tren:

1. Tenía un malentendido acerca del objetivo de las pruebas de unidad. Como ya lo dije, las pruebas de unidad mejoran la calidad de un sistema, pero realmente es acerca de facilitar el hacer cambios y mantener el sistema. Además, si sigues en ese camino y adoptas el siguiente paso lógico utilizando Desarrollo Guiado por las Pruebas (Test Driven Development o TDD) las pruebas de unidad realmente se convierten en diseño. Parafraseando a Scott Bellware, TDD no es acerca de probar el sistema porque no estamos pensando como probadores del sistema cuando hacemos TDD, estamos pensando como diseñadores de software.

2. Como muchos, solía pensar que los desarrolladores no deben escribir las pruebas. No sé la historia detrás de esta creencia, pero ahora creo que es sólo una excusa que utilizan los malos programadores. Probar el sistema es el proceso tanto de encontrar errores como de validar que el sistema hace lo que debe hacer. Tal vez los desarrolladores no son buenos en encontrar errores en su propio código, pero son los que mejor pueden asegurarse que el sistema hace lo que creemos que hace y los clientes son los que mejor se pueden asegurarse de que trabaja como debería. Si estás interesado en saber más sobre el tema, te sugiero que investigues sobre pruebas de aceptación (acceptance testing y FitNesse). Aunque las pruebas de unidad no sean exclusivamente acerca de probar el sistema, los programadores que no creen que deban probar su propio código, simplemente están siendo irresponsables.

3. Probar un sistema no es divertido. Estar sentado frente al monitor, capturando datos y asegurándose que todos esté bien, realmente no es nuestra idea de diversión. Pero hacer pruebas de unidad es programar, lo que quiere decir que hay muchas métricas y parámetros para medir tu éxito. A veces, como programar, es un poco mundano, pero a fin de cuentas no es diferente al tipo de programación que haces cada día.

4. Toma tiempo. Los promotores te dirán que hacer pruebas de unidad no toma tiempo, AHORRA tiempo. Esto es cierto en el sentido que el tiempo que pasas escribiendo las pruebas de unidad, es poco comparado con el tiempo que te ahorrarás en modificaciones y corrección de errores. Eso me parece un poco descabellado. Honestamente hacer pruebas de unidad SÍ toma bastante tiempo (especialmente cuando empiezas). Es posible que no tengas suficiente tiempo para hacer las pruebas de unidad o que el cliente no sienta que el costo inicial se justifique. En estas situaciones, sugiero que identifiques las partes más críticas de tu código y las pruebes lo más posible; un par de horas escribiendo las pruebas de unidad pueden tener un gran impacto.

Al final de todo, hacer pruebas de unidad parecía algo complicado y misterioso que era usado únicamente en proyectos más avanzados, el beneficio parecía inalcanzable y los tiempos no parecían permitirlo. Resulta que tomó mucha práctica (me costó trabajo aprender qué probar y cómo hacerlo), pero los beneficios se notaron casi inmediatamente.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

37

Page 38: Fundamentos de Programación Traducido

Capítulo 5 – Pruebas de Unidad

Las HerramientasCon StructureMap ya configurado del capítulo anterior, sólo necesitamos añadir dos frameworks y una herramienta más para tener nuestra todo lo necesario para hacer pruebas de unidad: nUnit, RhinoMocks y TestDriven.NET.

TestDriven.NET es una extensión (add-in) de ejecución de pruebas para Visual Studio que añade la opción “Run Test” en el menú de contexto (botón derecho), pero no perderemos tiempo hablando de eso. La licencia personal de TestDriven.NET es válida únicamente para proyecto de código abierto (open source) y para uso de evaluación. Pero no te preocupes si el licenciamiento no te parece, nUnit tiene su propio ejecutor de pruebas, sólo que no está integrado en VS.NET (Usuarios de Resharper también pueden utilizar la funcionalidad incluida).

nUnit es el framework de pruebas que estaremos usando. Existen otras alternativas como mbUnit, pero yo no sé tanto de estas como debería.

RhinoMocks es mocking framework que estaremos usando para crear objetos falsos. En el capítulo anterior creamos nuestros mocks manualmente, lo que era limitado y nos tomó tiempo, RhinoMocks nos generará automáticamente las clases mock basadas en una interfaz y nos permitirá verificar y controlar la interacción entre el objeto que estamos probando y estos objetos mock.

nUnitLo primero que debemos hacer es añadir la referencia a nunit.framework.dll y a Rhino.Mocks.dll. Mi preferencia personal es poner las pruebas de unidad en su propio proyecto. Por ejemplo si mi capa de dominio está localizada en CodeBetter.Foundations, Me gusta crear un nuevo proyecto llamado CodeBetter.Foundations.Tests. El inconveniente de esto es que no podremos probar métodos privados (más de esto en un momento). En .NET 2.0+ podemos usar el atributo InternalsVisibleToAttribute para permitir al proyecto de pruebas acceder a los métodos con visibilidad interna (abre el Properties/AssemblyInfo.cs y añade[assembly: InternalsVisibleTo(“CodeBetter.Foundations.Tests”)], es algo que hago típicamente)

Hay dos cosas que debes saber de nUnit. Primero, se configuran las pruebas utilizando atributos. El atributo TestFixtureAttribute es aplicado a la clase que contiene las pruebas y métodos de inicialización y finalización. El atributo SetupAttribute es aplicado al método que quieres que se ejecute para cada prueba (no siempre necesitarás esto), el atributo TearDownAttribute es aplicado al método que quieras que se ejecute después de cada prueba; y finalmente el atributo TestAttribute es aplicado a las pruebas en sí. Existen otros atributos, pero estos 4 son los más importantes. Esto es como se vería una clase de prueba:

using NUnit.Framework;

[TestFixture]public class CarTests{

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

38

Page 39: Fundamentos de Programación Traducido

Capítulo 5 – Pruebas de Unidad

[SetUp] public void SetUp() { //todo }

[TearDown] public void TearDown(){ //todo }

[Test] public void SaveThrowsExceptionWhenInvalid(){ //todo }

[Test] public void SaveCallsDataAccessAndSetsId(){ //todo }

//more tests}

Como puedes ver, cada prueba tiene un nombre muy explícito, es importante expresar exactamente lo que la prueba va a hacer, y ya que las prueba nunca deben hacer demasiado, rara vez tendrás nombres excesivamente largos.

La segunda cosa que hay que saber de nUnit, es que para confirmar que tu prueba se ejecutó como se esperaba utilizas la clase Assert y sus métodos. Ya sé que estos no es correcto, pero si tuviéramos un método que tomara como parámetro un param int[] numbers y regresara la suma de los números, nuestra prueba de unidad de vería así:

[Test]public void MathUtilityReturnsZeroWhenNoParameters(){ Assert.AreEqual(0, MathUtility.Add());}[Test]public void MathUtilityReturnsValueWhenPassedOneValue(){ Assert.AreEqual(10, MathUtility.Add(10));}[Test]public void MathUtilityReturnsValueWhenPassedMultipleValues(){ Assert.AreEqual(29, MathUtility.Add(10,2,17));}[Test]public void MathUtilityWrapsOnOverflow(){ Assert.AreEqual(-2, MathUtility.Add(int.MaxValue, int.MaxValue));}

No se ve en el ejemplo anterior, pero la clase Assert tiene más de una función, como por ejemplo Assert.IsFalse, Assert.IsTrue, Assert.IsNull, Assert.IsNotNull, Assert.AreSame, Assert.AreNotEqual, Assert.Greater, Assert.IsInstanceOfType y otros más.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

39

Page 40: Fundamentos de Programación Traducido

Capítulo 5 – Pruebas de Unidad

¿Qué es una prueba de unidad?Una prueba de unidad es un método que prueba el comportamiento de una parte del código a un nivel muy granular. Los desarrolladores que son nuevos en pruebas de unidad, tienden a permitir que el alcance de sus pruebas crezca demasiado. La mayoría de las pruebas de unidad siguen el mismo patrón: ejecutar código del sistema y asegurarse que se comporte como debe. El objetivo de las pruebas de unidad es validar un comportamiento específico. Si escribiéramos pruebas para un método Save de una clase Car, no escribiríamos una prueba que abarcara todo el funcionamiento del método, más bien haríamos una prueba para cada uno de los funcionalidades o comportamientos esperados que contiene nuestro método: fallando cuando el objeto está en un estado inválido, llamando a nuestro método Save de nuestra capa de datos y asignando el identificador y llamando el método Update de nuestra capa de datos. Es importante que nuestras pruebas sean lo suficientemente granulares para identificar alguna falla con precisión.

Estoy seguro que algunos pensarán que 4 pruebas para cubrir el método MathUtility.Add es algo excesivo. Podrán pensar que estas 4 pruebas podrían ser agrupadas en una sola (y para este caso podría decir que es como ustedes prefieran), pero, cuando empecé a hacer pruebas de unidad, caí en el mal hábito de dejar que el alcance de mis pruebas creciera demasiado. Empezaba con pruebas que creaban un objeto, ejecutaban alguno de sus métodos y se aseguraban que funcionara como quería. Pero siempre decía, “ya que estoy aquí, por qué no añadir unos cuantos asserts más para asegurarme que estos campos tengan los valores que deben tener”. Esto es muy peligroso, porque un cambio en el código podía romper varias pruebas que no tenían relación con el cambio – definitivamente esto es una señal de que no has enfocado tus pruebas lo suficiente.

Esto nos lleva al tema de probar métodos privados. Si googleas esto encontrarás varias discusiones al respecto, pero el consenso general parece ser que no se deben probar los métodos privados directamente. Creo que la razón más importante para no probar métodos privados es que nuestro objetivo no es probar métodos o líneas de código, sino el comportamiento de una parte del código. Esto es algo que siempre debes recordar. Si pruebas exhaustivamente la interfaz pública de tu código, los métodos privados deberían también estar probándose automáticamente. Otro argumento en contra de probar métodos privados es que rompe el encapsulamiento de tu clase. Ya hablamos de la importancia de esconder la información; y los métodos privados contienen detalles de implementación que queremos poder cambiar sin romper el código que utiliza nuestra clase, si probamos métodos privados directamente, es posible que algunos cambios en la implementación de la clase romperán nuestras pruebas, lo que no ayuda a tener código que sea fácil de mantener.

MockingPara empezar a utilizar pruebas de unidad, debes empezar a probar funcionalidades simples; pero rápidamente querrás probar métodos más complejos que tienen dependencias con otros componentes (como con la base de datos), por ejemplo, querrás añadir pruebas para completar la cobertura del método Save de nuestra clase Car. Ya que queremos mantener nuestras pruebas lo más granular y ligeras posible (las pruebas deben poder ejecutarse rápidamente y muy seguido para que tengamos retroalimentación instantánea), realmente no queremos tener que inicializar una base de datos con

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

40

Page 41: Fundamentos de Programación Traducido

Capítulo 5 – Pruebas de Unidad

datos falsos y asegurarnos que se mantenga en un estado predecible entre prueba y prueba. En realidad lo que queremos asegurarnos es que el método Save interactúe de forma correcta con nuestra capa de datos; después podremos añadir otras pruebas para nuestra capa de datos. Si nuestro método Save funciona correctamente, nuestra capa de datos funciona correctamente y los dos interactúan correctamente, tendremos una buena parte del camino avanzado para hacer pruebas más tradicionales.

En el capítulo anterior, vimos el principio de probar con mocks. Estábamos usando una clase falsa o mock creada manualmente que tenía algunas limitaciones importantes. La más significativa era nuestra inhabilidad para confirmar que las llamadas a nuestro objeto mock ocurrieran como esperábamos. Esto, junto con la facilidad de uso, es exactamente el problema que resuelve RhinoMocks. Usar esta herramienta es muy sencillo, le dices que es lo que quieres representar (una interfaz o clase, preferentemente una interfaz), le dices que métodos y con qué parámetros esperas que sean llamados, y le pides que verifique que estas expectativas se cumplieron.

Antes de empezar, necesitamos darle acceso a RhinoMocks a nuestros tipos internos. Esto se hace añadiendo [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] a nuestro archivo Properties/AssemblyInfo.cs.

Ahora podemos empezar a programar nuestra prueba que cubre el camino de actualización de nuestro método Save cuando ya existe el Car en la base de datos:

[TestFixture]public class CarTest{ [Test] public void SaveCarCallsUpdateWhenAlreadyExistingCar() { MockRepository mocks = new MockRepository(); IDataAccess dataAccess = mocks.CreateMock<IDataAccess>(); ObjectFactory.InjectStub(typeof(IDataAccess), dataAccess);

Car car = new Car(); dataAccess.Update(car); mocks.ReplayAll();

car.Id = 32; car.Save();

mocks.VerifyAll(); ObjectFactory.ResetDefaults(); }}

Una vez que el objeto mock ha sido creado, lo que tomó una línea, lo inyectamos en nuestro framework de inyección de dependencias (StructureMap en este caso). Cuando se crea un mock utilizando RhinoMocks, empieza en modo de registro, lo que quiere decir que todas la operaciones que se le

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

41

Page 42: Fundamentos de Programación Traducido

Capítulo 5 – Pruebas de Unidad

hagan, como llamar al dataAccess.Update(car), será registrado por RhinoMocks (realmente no sucede nada, ya que dataAcces es simplemente un objeto mock que no tiene ninguna implementación real). Salimos del modo de registro llamando el método ReplayAll, lo que le dice a RhinoMocks que estamos listos para ejecutar el código real y verificarlo contra la secuencia que registramos. Cuando llamamos VerifyAll después de llamar Save en nuestro objeto Car, RhinoMocks se asegurará de que la llamada se comportó como esperábamos. En otras palabras, lo que hacemos antes de la llamada a ReplayAll es declarar nuestras expectativas, lo que hacemos después es el código que ejecuta la prueba contra nuestro objeto real y al final llamamos a VerifyAll para verificar que las expectativas se cumplieron.

Podemos probar todo esto forzando a nuestra prueba a que falle (noten la llamada extra a dataAccess.Update):

[Test] public void SaveCarCallsUpdateWhenAlreadyExistingCar(){ MockRepository mocks = new MockRepository(); IDataAccess dataAccess = mocks.CreateMock<IDataAccess>(); ObjectFactory.InjectStub(typeof(IDataAccess), dataAccess);

Car car = new Car(); dataAccess.Update(car); dataAccess.Update(car); mocks.ReplayAll();

car.Id = 32; car.Save();

mocks.VerifyAll(); ObjectFactory.ResetDefaults();}

Nuestra prueba falla con un mensaje de RhinoMocks que nos dice que esperábamos dos llamadas al método Update, pero sólo una ocurrió.

Para el caso cuando el Car no existe en la base de datos, la interacción con el método Save de la capa de datos es un poco más compleja, tenemos que asegurarnos que el valor que regresa el método Save del objeto dataAccess, es manejado correctamente. Esta es la prueba:

[Test] public void SaveCarCallsSaveWhenNew(){ MockRepository mocks = new MockRepository(); IDataAccess dataAccess = mocks.CreateMock<IDataAccess>(); ObjectFactory.InjectStub(typeof(IDataAccess), dataAccess);

Car car = new Car();

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

42

Page 43: Fundamentos de Programación Traducido

Capítulo 5 – Pruebas de Unidad

Expect.Call(dataAccess.Save(car)).Return(389); mocks.ReplayAll();

car.Save();

mocks.VerifyAll(); Assert.AreEqual(389, car.Id); ObjectFactory.ResetDefaults();}

Utilizando el método Expect.Call nos permite especificar el valor que queremos regresar cuando llamen a este método. También añadimos una llamada a Assert.Equal para verificar que lo que regresamos como Id desde la capa de datos es lo que se asignó al objeto Car y así validar que la interacción entre los dos objetos es correcta. Espero que las posibilidades de controlar el valor que regresa el método (así como los valores output/ref) te muestren lo fácil que es probar los casos extremos.

Si cambiáramos nuestro método Save para que genere una excepción si el Id que se regresa del dataAccess es inválido, nuestra prueba se vería así:

[TestFixture]public class CarTest{ private MockRepository _mocks; private IDataAccess _dataAccess;

[SetUp] public void SetUp() { _mocks = new MockRepository(); _dataAccess = _mocks.CreateMock<IDataAccess>(); ObjectFactory.InjectStub(typeof(IDataAccess), _dataAccess); } [TearDown] public void TearDown() { _mocks.VerifyAll(); }

[Test, ExpectedException("CodeBetter.Foundations.PersistenceException")] public void SaveCarCallsSaveWhenNew() { Car car = new Car(); Expect.Call(_dataAccess.Save(car)).Return(0); _mocks.ReplayAll(); car.Save(); }}

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

43

Page 44: Fundamentos de Programación Traducido

Capítulo 5 – Pruebas de Unidad

Además de mostrar cómo podemos probar una excepción (utilizando el atributo ExpectedException), también sacamos el código repetitivo que crea, inicializa y verifica el objeto mock a los métodos SetUp y TearDown.

Más de nUnit y RhinoMocksHasta ahora hemos visto la funcionalidad básica que ofrecen nUnit y RhinoMocks, pero hay mucho más que se puede hacer con estas herramientas. Por ejemplo, RhinoMocks puede ser configurado para ignorar el orden en que se llaman los métodos, crear varios objetos mock pero únicamente registrar y verificar sobre uno de ellos o hacer mocks de algunos métodos de la clase y no de otros (mocks parciales).

Combinado con una utilería como NCover, también puedes obtener reportes de la cobertura de tus pruebas. Básicamente, la métrica de cobertura de dice qué porcentaje del código de un assembly/namespace/clase/método fue ejecutado con tus pruebas. NCover tiene un visor de código que resalta en rojo las líneas que no fueron ejecutadas por las pruebas. Generalmente, no me gusta utilizar la métrica de cobertura para medir que tan completo es mi set de pruebas. Después de todo, el ejecutar una línea de código no quiere decir que realmente la probaste. Más bien, para lo que me gusta usar NCover es para identificar el código que no he probado. Es decir, el que una línea o método haya sido ejecutada no quiere decir que la prueba es correcta, pero si una línea de código o método no ha sido ejecutado, entonces necesitas pensar en añadir más pruebas para cubrirlo.

Hemos mencionado el Desarrollo Guiado por las Pruebas (Test Driven Development o TDD) brevemente en este libro. Como mencioné antes, TDD se trata de diseño y no de pruebas. TDD quiere decir que escribes la prueba primero y después escribes el código para hacer que la prueba pase. En TDD habríamos escrito la prueba para el método Save antes de tener cualquier funcionalidad en el método en sí. Claro que la prueba fallaría; entonces escribiríamos la funcionalidad específica para hacer que la prueba pase. El principio para desarrolladores que utilizan TDD es rojo → verde → refactorizar. Lo que quiere decir que el primer paso es tener una prueba que falle, después hacerla pasar y después modificar el código como sea requerido.

En mi experiencia, TDD va muy bien con Diseño Guiado por el Dominio (Domain Driven Design), ya que nos hace concentrarnos en las reglas de negocio del sistema. Si nuestro cliente tienen muchos problemas para mantener las dependencias entre las actualizaciones del sistema, nos enfocamos en escribir pruebas que definan el comportamiento y el API de la funcionalidad específica. Les recomiendo que primero se familiaricen con pruebas de unidad en general antes de entrar en TDD.

Pruebas de la interfaz de usuario y la base de datosAñadir pruebas de unidad a tus páginas de ASP.NET probablemente no valga la pena. El framework de ASP.NET es complicado y sufre de un acoplamiento excesivo entre sus diferentes clases. Muchas veces necesitarás un HTTPContext real, lo que requiere de mucho trabajo para inicializar. Si haces mucho uso de HttpHandlers propios, deberías poder probar esos como cualquier otra clase (claro dependiendo de que hagas exactamente en esas clases).

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

44

Page 45: Fundamentos de Programación Traducido

Capítulo 5 – Pruebas de Unidad

Por otro lado, probar la capa de datos es posible y lo recomendaría. Puede ser que haya mejores métodos, pero mi forma de hacerlos es mantener todos mis CREATE Table / CREATE Sprocs en archivos de texto con mi proyecto, crear una base de datos de prueba y utilizar los métodos SetUp y TearDown de mi clase de prueba para crear y mantener la base de datos en un estado conocido en cada prueba. Este tema quizá sea para un artículo futuro en mi blog, por lo que por ahora se los dejo a su creatividad.

En este capítuloHacer pruebas de unidad no fue tan difícil como me imaginé en un principio. Seguro que mis primeras pruebas no fueron las mejores (a veces escribía pruebas que no tenían ningún sentido, como probar que una propiedad de una clase funcionaba como debía) y otras veces eran demasiado complejas y se salían del alcance definido; pero después de mi primer proyecto, aprendí mucho de qué funcionaba y qué no. Una cosa que me quedó clara inmediatamente, fue qué tan limpio y claro quedaba mi código. También me di cuenta que si algo era difícil de probar, al reescribirlo para que fuera posible probarlo, quedaba mucho más legible, más desacoplado y en general más fácil de trabajar con él. El mejor consejo que les puedo dar es empezar con algo pequeño, experimentar con diferentes técnicas, no tener miedo a fallar y aprender de tus errores; y por supuesto, no esperes a terminar el proyecto para hacer pruebas de unidad, ¡escríbelas conforme escribas el código!

.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

45

Page 46: Fundamentos de Programación Traducido

Capítulo 6 - Object Relational Mappers

Object Relational Mappers

LA OTRA OPCIÓN DISPONIBLE A LA ESCRITURA DE MUCHO SQL – CHRIS KOCH

n el capítulo 3 hicimos nuestro primer esfuerzo para unir el mundo de los objetos con los datos mediante la escritura a mano de nuestra propia capa de acceso a datos y su definición de conversión. Este enfoque resultó ser más bien limitado y requirió una cantidad significativa de

código repetitivo (aunque fue útil para demostrar las bases). Agregar más objetos y funcionalidad sobrecargaría nuestro Capa de Acceso a Datos (DAL) en una enorme violación inmanejable del principio que dicta ‘No te repitas a ti mismo’ (DRY, por sus siglas en inglés). En este capítulo veremos un marco de trabajo real para la definición de conversiones entre Objetos y Entidades Relacionales (O/R Mapping) que haga todo el trabajo pesado por nosotros. Específicamente veremos el popular marco de trabajo de código abierto llamado NHibernate.

E

La única y más grande barrera que impide a la gente adoptar el diseño guiado por el dominio (DDD por sus siglas en inglés), es el problema de la persistencia. Mi propia adopción de las definiciones de conversión entre estructuras relacionales y los objetos (O/R Mappers) inicio con gran confusión y duda. Básicamente se te pedirá que cambies tu conocimiento de un método probado por algo que parece de un poco mágico. Puede ser requerida algo de fe ciega.

La primer cosa con la que hay que llegar a un acuerdo es con que las definiciones de conversión generan tu SQL por ti, lo sé, suena como que será algo lento, inseguro e inflexible, especialmente debido a que probablemente imaginaste que se tendría que usar SQL en línea. Pero sí puedes quitarte esos miedos de tu mente por un segundo, tienes que admitir que podría ahorrarte mucho tiempo y tener como resultado un número mucho menor de defectos. Recuerda, queremos enfocarnos en construir el comportamiento, no preocuparnos con cuestiones de interconexión (y si te hace sentir mejor, una buena definición de conversiones entre estructuras relacionales y objetos te proveerá formas sencillas de desactivar la generación automatizada de código y ejecutar tu propio SQL o tus procedimientos almacenados).

El debate del infame SQL en línea vs. Los procedimientos almacenadosA lo largo de los años, ha existido un poco de debate entre usar SQL en línea o procedimientos almacenados. Este debate ha sido pobremente soportado, debido a que cuando la gente escucha SQL en línea, piensan en código mal escrito, algo como esto:

string sql = @"SELECT UserId FROM Users WHERE UserName = '" + userName + "' AND Password = '" + password + "'"; using (SqlCommand command = new SqlCommand(sql)) { return 0; //todo

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

46

6

Page 47: Fundamentos de Programación Traducido

Capítulo 6 - Object Relational Mappers

}

Por supuesto que formulado de esta manera, el SQL en línea realmente apesta. Sin embargo, si te detienes y lo piensas y realmente comparas manzanas con manzanas, la verdad es que ninguna de las 2 es particularmente mejor que la otra, Examinemos algunos puntos de interés.

Los procedimientos almacenados son más seguros

El SQL en línea debe ser escrito usando consultas parametrizadas de la misma forma en que lo haces con los procedimientos almacenados. Por ejemplo, la forma correcta de escribir el código de arriba para eliminar un posible ataque de inyección de SQL es:

string sql = @"SELECT UserId FROM Users WHERE UserName = @UserName AND Password = @Password"; using (SqlCommand command = new SqlCommand(sql)) { command.Parameters.Add("@UserName", SqlDbType.VarChar).Value = userName; command.Parameters.Add("@Password ", SqlDbType.VarChar).Value = password; return 0; //todo }

De ahí en adelante, no hay mucha diferencia - se pueden usar vistas o configurar roles / usuarios de la base base datos con los permisos apropiados.

Los procedimientos almacenados proveen abstracción al esquema subyacente

Sin importar si estas usando SQL en línea o procedimientos almacenados, la pequeña porción de abstracción que puedes poner en una sentencia de SELECT es la misma. Si algún cambio substancial es realizado, tus procedimientos almacenados dejarán de funcionar y lo más probable es que tendrás que cambiar el código que los invoca para resolver este problema. Esencialmente, es el mismo código, solamente que reside en un lugar diferente, por lo tanto no puede proveer un grado más alto de abstracción. Las definiciones de conversión por otro lado, generalmente proveen una mejor abstracción ya que son configurables e implementan su propio lenguaje de consulta.

Si hago un cambio, no tengo que recompilar el código

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

47

Como he dicho antes, creo que es generalmente mejor errar en el lado de la simplicidad, siempre que sea posible. Escribir un montón de tontos procedimientos almacenados para realizar cada operación de base de datos que creas que vas a necesitar, definitivamente no es a lo que yo considero simple. Ciertamente no intento descartar el uso de los procedimientos almacenados, ¿pero comenzar con procedimientos? Parece un caso bastante extremo de optimización prematuro para mí. -Jeff Atwood, codinghorror.com

Page 48: Fundamentos de Programación Traducido

Capítulo 6 - Object Relational Mappers

En algún lugar, de alguna forma, la gente se metió en la cabeza que las compilaciones de código deben evitarse a toda costa (tal vez esto venga de los días cuando los proyectos podrían tardar días en compilar). Si cambias un procedimiento almacenado, aun tendrás que ejecutar nuevamente tus pruebas unitarias y de integración y liberar el cambio a producción. Realmente me espanta el que los desarrolladores consideren que un cambio a un procedimiento almacenado o a un XML es algo trivial comparado con un cambio similar al código.

Los procedimientos almacenados reducen el tráfico en la red

¿A quién le importa? En la mayoría de los casos tu base de datos esta soportada por una conexión GigE con tus servidores y tú no pagas ese ancho de banda. Estás hablando de fracciones de nanosegundos. Más relevante aún, una definición de conversión bien configurada puede ahorrarte viajes de ida y vuelta gracias a la identificación de las implementaciones de los mapas, el cache y la carga diferida.

Los procedimientos almacenados son más rápidos

Esta es la excusa más frecuentemente utilizada, Escribe una sentencia común y razonable de SQL en línea y después escribe lo mismo con un procedimiento almacenado y cronometra el tiempo de ejecución. Adelante hazlo. En la mayoría de los casos no habrá diferencia o esta será muy poca. En algunos casos los procedimientos almacenados serán más lentos ya que el plan de ejecución no será eficiente con algunos parámetros. Jeff Atwood catalogó el uso de procedimientos almacenados en busca de una mayor velocidad de ejecución como un caso extremo de optimización prematura. Tiene razón. El enfoque adecuado es tomar la estrategia más simple (permite que una herramienta genere el SQL por ti), y optimiza consultas especificas en caso de que identifiques cuellos de botella.

Me tomo un tiempo, pero después de un par de años, me di cuenta que el debate entre el SQL en línea y los procedimientos almacenados era tan trivial como el que se tiene con C# y VB.NET. Si tan solo se trataba de elegir uno u otro, entonces selecciona el que prefieras y continua con tu próximo reto. Si no hay nada más que decir sobre el tema, yo escogería procedimientos almacenados. Sin embargo, cuando añades una definición de conversión Objeto/Estructura relacional a la mezcla, de forma repentina obtendrás ventajas significativas. Dejas de participar en discusiones bizantinas para simplemente decir “quiero eso”.

Específicamente, hay tres grandes beneficios con las definiciones de conversión entre estructuras relacionales y objetos:

1.- Terminarás escribiendo mucho menos código – lo que obviamente resulta en un sistema más mantenible,

2.- Obtendrás un nivel real de abstracción del origen de datos subyacente – por una parte porque estarás consultando la definición de conversión para obtener los datos directamente (y esta a su vez convertirá eso en el SQL apropiado), por otra parte porque estarás proveyendo información de la definición de conversión entre los esquemas de tablas y los objetos de dominio,

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

48

Page 49: Fundamentos de Programación Traducido

Capítulo 6 - Object Relational Mappers

3.- Tu código se vuelve más simple – si tu nivel de discrepancia entre los objetos y las estructuras relacionales es bajo, escribirás mucho menos código repetitivo. Si tu nivel de discrepancia entre los objetos y las estructuras es alto no tendrás que comprometer el diseño de la base de datos y el diseño del dominio – puedes construir ambos de una manera optimizada, y dejar que la definición de conversión maneje la discrepancia.

Al final, todo se traduce en la construcción de la solución más sencilla desde el inicio. La optimizaciones deberían dejarse para después de que el código ha sido perfilado y se han identificado los cuellos de botella reales. Como la mayoría de las cosas, podría no sonar tan sencillo por la complejidad en el aprendizaje de hacerlo por adelantado, pero esa es la realidad de nuestra profesión.

NHibernateDe los marcos de trabajo y herramientas que hemos visto hasta el momento, NHibernate es la más compleja. Esta complejidad ciertamente es algo que deberías tomar en cuanta cuando te decidas por alguna solución de

persistencia, pero una vez que encuentres un proyecto que te permita algún tiempo para investigación y desarrollo, el beneficio valdrá la pena en proyectos futuros. Lo mejor acerca de NHibernate, y una de las principales metas del diseño del marco de trabajo, es que es completamente transparente - tus objetos de dominio no son forzados a heredar ninguna clase base específica y no tienes que usar un montón de atributos de decoración. Esto hace posible que tu capa de dominio pueda ser sometida a pruebas unitarias – si estas usando un mecanismo diferente de persistencia, digamos datasets con tipos definidos, el nivel de acoplamiento tan estrecho entre el dominio y los datos hace muy difícil sino es que imposible el efectuar apropiadamente las pruebas unitarias.

A muy groso modo, configuras Nhibernate diciéndole como tu base de datos (tablas y columnas) hacen referencia con tus objetos de dominio, usa la API de Nhibernate y el lenguaje de consulta (HQL – Hibernate Query Language) para comunicarte con tu base de datos, y deja que el haga el trabajo de bajo nivel con ADO.NET y SQL. Esto no solo provee separación entre la estructura de tu tabla y los objetos de dominio, sino que también

desacopla tu código de la implementación para una base de datos específica.

En capítulos previos nos enfocamos en un sistema para un concesionario de automóviles – específicamente centrándonos en automóviles y actualizaciones. En este capítulo cambiaremos la perspectiva un poco y veremos la venta de automóviles (ventas, modelos y personal de ventas). El modelo de dominio es simple – un vendedor (SalesPerson) tiene 0 o más Ventas (Sales) las cuales hacen referencia a un Modelo (Model) específico.2

También se incluye una solución de VS.NET que contiene código de ejemplo y anotaciones - puedes encontrar un vínculo al final de este capítulo. Todo lo que necesitas para ponerlo en marcha es crear una nueva base de datos, ejecutar la secuencia de comandos de SQL provista (un mecanismo completo para la creación de tablas), y

2 Nota de traducción: para guardar compatibilidad con la aplicación de ejemplo, no se traducirán los nombres de clases, variables y demás.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

49

Recuerda que nuestro objetivo es ampliar nuestra base de conocimientos viendo diferentes formas de construir sistemas con el fin de proveer un mayor valor a nuestros clientes. Mientras que podríamos hacerlo hablando específicamente de NHibernate, el objetivo es introducir el concepto de Definiciones de conversión Entidad relacional / Objeto, y tratar de corregir la fe ciega que tienen puesta los desarrolladores de .NET en los procedimientos almacenados y ADO.NET

Page 50: Fundamentos de Programación Traducido

Capítulo 6 - Object Relational Mappers

configura la cadena de conexión. El ejemplo, y el resto de este capítulo, fueron diseñados con la intención de ayudarte a comenzar a trabajar con NHibernate – un tema que frecuentemente se pasa por alto.

Finalmente, encontrarás que el manual de referencia de NHibernate es de excepcional calidad, el cual es tanto una herramienta útil para empezar, como también una referencia para buscar información de temas específicos.También hay un libro que está siendo publicado por Manning, ‘NHibernate en acción’, el cual estará disponible en junio, mientras tanto puedes comprar una versión en formato electrónico previa al lanzamiento del libro.

Configuración El secreto para la sorprendente flexibilidad de NHibernate radica en su configurabilidad. Inicialmente el proceso de configurarlo puede ser más bien desalentador, pero después de un proyecto de prueba se vuelve natural. El primer paso es configurar el propio NHibernate. La configuración más simple, que debe ser agregada a tu app.config o web.config se ve así:

<configuration> <configSections> <section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" /> </configSections> <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2"> <session-factory> <property name="hibernate.dialect"> NHibernate.Dialect.MsSql2005Dialect </property> <property name="hibernate.connection.provider"> NHibernate.Connection.DriverConnectionProvider </property> <property name="hibernate.connection.connection_string"> Server=SERVER;Initial Catalog=DB;User Id=USER;Password=PASSWORD; </property> <mapping assembly="CodeBetter.Foundations" /> </session-factory> </hibernate-configuration></configuration>

De los 4 valores, dialect es el más interesante, este le dice a NHibernate que lenguaje específico habla nuestra base de datos. Sí, en nuestro código, le pedimos a NHibernate que regrese un resultado paginado de Cars y nuestro dialecto está configurado para SQL Server 2005, NHibernate emitirá una sentencia de SELECT utilizando la función de ranking ROW_NUMBER (). Sin embargo, si el dialecto está configurado a MySql, NHibernate emitirá el SELECT con LIMIT. En la mayoría de los casos, configurarás esto una sola vez y te olvidarás del tema. Pero esto proporciona algunos conocimientos sobre las capacidades provistas por la capa que genera todo tu código de acceso a datos.

En nuestra configuración, también le dijimos a NHibernate que nuestros archivos de definición de conversión estaban localizados en el ensamblado CodeBetter.Foundations. Los archivos de definición son archivos incrustados de XML que le dicen a NHibernate como debe ser persistida cada clase. Con esta información, NHibernate es capaz de regresar un objeto de tipo Car (automóvil) cuando tú se lo

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

50

Page 51: Fundamentos de Programación Traducido

Capítulo 6 - Object Relational Mappers

solicites, así como salvarlo. La convención general es tener un archivo de definición de conversión por cada objeto de dominio, y que estos se localicen dentro de la carpeta Mappings. El archivo de definición para nuestro objeto Model (Modelo), llamado Model.hbm.xml, se ve de esta forma:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="CodeBetter.Foundations" namespace="CodeBetter.Foundations"> <class name="Model" table="Models" lazy="true" proxy="Model"> <id name="Id" column="Id" type="int" access="field.lowercase-underscore"> <generator class="native" /> </id> <property name="Name" column="Name" type="string" not-null="true" length="64" /> <property name="Description" column="Description" type="string" not-null="true" /> <property name="Price" column="Price" type="double" not-null="true" /> </class></hibernate-mapping>

(Es importante asegurarse de que el parámetro Build Action para todos los archivos de definición de conversiones se configuren como Embedded Resources)

Este archivo le dice a NHibernate que la clase Model se refiere a registros en la tabla Models, y que las 4 propiedades Id, Name, Description y Price se refieren a las columnas Id, Name, Description, y Price. La información extra alrededor de la propiedad Id especifica que el valor es generado por la base de datos (en contraposición a NHibernate (para soluciones residentes en clústeres o grupos de servidores), o nuestro propio algoritmo) y que no hay configurador, así que deberá ingresar a través del campo con la convención de nombres especificada (proveemos Id como el nombre y la estrategia de nomenclatura con minúsculas y guión bajo (lowercase-underscore), para que use el campo llamado _id.

Con el archivo de definición de conversiones configurado, podemos comenzar a interactuar con la base de datos:

private static ISessionFactory _sessionFactory;public void Sample(){ //Agreguemos un nuevo modelo de carro Model model = new Model(); model.Name = "Hummbee"; model.Description = "Great handling, built-in GPS to always find your way back home, Hummbee2Hummbe(tm) communication"; model.Price = 50000.00; ISession session = _sessionFactory.OpenSession(); session.Save(model);

//Hagamos un descuento al modelo x149 IQuery query = session.CreateQuery("from Model model where model.Name = ?");

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

51

Page 52: Fundamentos de Programación Traducido

Capítulo 6 - Object Relational Mappers

Model model = query.SetString(0, "X149").UniqueResult<Model>(); model.Price -= 5000; ISession session = _sessionFactory.OpenSession(); session.Update(model); }

El ejemplo de arriba muestra lo fácil que es persistir nuevos objetos a la base de datos, extraerlos y actualizarlos – todo esto sin el uso directo de ADO.NET o SQL.

Tal vez te estés preguntando de donde viene el objeto _sessionFactory, y que es exactamente un ISession. _sessionFactory (que implementa la interfaz ISessionFactory) es un objeto global seguro para el uso en hilos de ejecución el cual es muy probable que crees en el inicio de la aplicación. Típicamente necesitaras uno por cada base de datos que tu aplicación este usando (lo que significa que típicamente necesitaras solo uno), y su trabajo, como con la mayoría de las fabricas de objetos, es crear un objeto pre configurado: un objeto ISession no tiene equivalente en ADO.NET, pero si se relaciona con un bajo nivel de cohesión a una conexión de base de datos. Sin embargo, la creación de un ISession no necesariamente abre una conexión, en lugar de eso, el objeto ISession administra de forma inteligente los objetos de tipo conexión y comando por ti. A diferencia de las conexiones que deben ser abiertas tarde y cerradas de manera temprana, no tienes que preocuparte por tener objetos ISession alrededor por un rato (aún cuando estas no sean seguras para su procesamiento en hilos de ejecución). Si estas construyendo una aplicación ASP.NET, puedes abrir de forma segura un objeto que implemente ISession en el método BeginRequest y cerrarlo en el método EndRequest (o mejor aún, cargar de forma diferida en caso de que la solicitud específica no requiera un ISession).

La interfaz ITransaction es otra pieza del rompecabezas que es creada llamando el método BeginTransaction en un ISession. Es común para los desarrolladores de .NET ignorar la necesidad de usar transacciones dentro de sus aplicaciones. Esto es desafortunado ya que puede llevarnos a estados inestables e incluso irrecuperables de los datos. Un ITransaction es usado para mantener el rastro de la unidad de trabajo - rastrear que cambio, o que ha sido agregado o borrado, averiguar qué y cómo aplicarlo a la base de datos, y proveer la capacidad de deshacer en caso de que un paso individual falle.

RelacionesEn nuestro sistema, es importante que rastreemos las ventas – específicamente con relación a la fuerza de ventas, de tal forma que podamos proveer algunos reportes básicos. Se nos ha dicho que una venta solo puede pertenecer a un vendedor, y así establecer una relación uno-a-muchos esto es, un agente de ventas puede tener múltiples ventas, y una venta solo puede pertenecer a un único vendedor. En nuestra base de datos, la relación está representada como una columna SalesPersonId en la tabla Sales (llave foránea). En nuestro dominio, la clase SalesPerson tiene una colección de Sales y la clase Sales tiene una propiedad SalesPerson (Referencia).

Ambos extremos de la relación necesitan ser configurados en el archivo de definición de conversiones apropiado, En el extremo de Sales, que se relaciona con una propiedad única, usamos un elemento property glorificado llamado many-to-one:

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

52

Page 53: Fundamentos de Programación Traducido

Capítulo 6 - Object Relational Mappers

...<many-to-one name="SalesPerson" class="SalesPerson" column="SalesPersonId" not-null="true"/>...

Estamos especificando el nombre de la propiedad, el tipo/clase, y el nombre de la columna que es la llave foránea. También estamos especificando una limitante extra, que es, que cuando agregamos un nuevo objeto Sales, la propiedad SalesPerson no puede ser nula.

El otro lado de la relación, la colección de ventas (Sales) que tiene un vendedor (SalesPerson), es un poco más complicada – básicamente porque la terminología de NHibernate no pertenece a la jerga estándar usada en .NET. Para configurar una colección usamos un elemento de tipo set, list, map, bag o array. Tu primera inclinación puede ser usar una lista, pero NHibernate requiere que tengas una columna que especifique el índice. En otras palabras, el equipo de NHibernate ve una lista como una colección donde el índice es importante, y por lo tanto debe ser especificado. Lo que la mayoría de los desarrolladores de .NET entienden como un list, NHibernate lo llama una bolsa (Bag). Siendo algo confuso tal vez, tanto si utilizas un elemento list o bag, tu tipo de dominio debe ser un IList (o su equivalente genérico IList<T>). Esto es debido a que .NET no cuenta con un objeto IBag, En resumen, para las colecciones que uses comúnmente, utiliza el elemento bag y haz tu propiedad del tipo IList.La otra opción interesante de colección es el set (conjunto). Un conjunto es una colección que no puede contener duplicados- un escenario común para una aplicación empresarial (aunque rara vez se afirma explícitamente). Extrañamente, .NET no tiene una colección de tipo conjunto, así que NHibernate usa la interfaz Iesi.Collection.ISet. Existen 4 implementaciones específicas, ListSet que es realmente rápida para colecciones muy pequeñas (10 elementos o menos), SortedSet que puede ser ordenada, HashSet la cual es rápida para colecciones más grandes y HybridSet la cual usa inicialmente un ListSet y automáticamente se cambia así misma a un HashSet conforme crece tu colección.

Para nuestro sistema usaremos un objeto bag (aún cuando no podemos tener ventas duplicadas, es mucho más sencillo por el momento), así que declaramos nuestra colección de Sales como un IList:

private IList<Sale> _sales;public IList<Sale> Sales{ get { return _sales;}}

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

53

Con la liberación de .NET 3.5 finalmente se ha agregado una colección HashSet al marco de trabajo. Idealmente, versiones futuras agregaran otro tipo de conjuntos con un OrderedSet. Los conjuntos son colecciones muy útiles y eficientes, ¡así que considera agregarlos a tu arsenal de herramientas! Puedes aprender más leyendo el artículo en el que

Jason Smith describe los conjuntos.

Page 54: Fundamentos de Programación Traducido

Capítulo 6 - Object Relational Mappers

Y agregamos nuestro elemento <bag> al archivo de definición de conversiones para SalesPersons:

<bag name="Sales" access="field.lowercase-underscore" table="Sales" inverse="true" cascade="all"> <key column="SalesPersonId" /> <one-to-many class="Sale" /></bag>

De nuevo, si observas a cada elemento/atributo, no es tan complicado como podría verse al principio. Identificamos el nombre de nuestra propiedad, especifica la estrategia de acceso (no tenemos un configurador, así que hay que indicarle que use el campo con nuestra convención de nombres), la tabla y la columna que contienen la llave foránea, y el tipo/clase de los elementos en la colección.

También hemos configurado el atributo cascade a all lo que significa que cuando nosotros invoquemos la actualización (update) en un objeto del tipo SalesPerson, cualquier cambio que sea hecho a su colección de ventas (Sales) (adiciones, remociones, cambios a las ventas existentes) será automáticamente persistido. La actualización en cascada puede ser un buen ahorrador de tiempo conforme tu sistema crece en complejidad.

ConsultasNHibernate soporta dos diferentes tipos de esquemas para realizar consultas: Hibernate Query Language (HQL) y Consultas de Criterios (también puedes realizar consultas en SQL convencional, pero perderá portabilidad al hacerlo). HQL es la forma más sencilla de las 2 ya que se parece mucho a SQL – usas From, where, aggregates, order by, group by, etc. Sin embargo en vez de consultar directamente contra tus tablas, escribes consultas contra tu dominio - lo que significa que HQL Soporta los principios de la orientación a objetos tales como la herencia y el polimorfismo. Ambos métodos de consulta son abstracciones arriba de SQL, lo que significa que obtienes portabilidad total – lo único que necesitas hacer para dirigirse a una base de datos diferente es cambiar la configuración de su dialecto.

HQL funciona a partir de la interfaz IQuery, que se crea con la invocación del método CreateQuery en tu sesión. Con IQuery puedes regresar entidades individuales, colecciones, parámetros substitutos y más. Aquí hay algunos ejemplos:

string lastName = "allen";ISession session = _sessionFactory.OpenSession();

//Obtener un agente de ventas por el apellidoIQuery query = s.CreateQuery("from SalesPerson p where p.LastName = 'allen'");SalesPerson p = query.UniqueResult<SalesPerson>();

//Lo mismo que lo anterior, pero en una línea y el apellido como variableSalesPerson p = session.CreateQuery("from SalesPerson p where p.LastName = ?").SetString(0, lastName).UniqueResult<SalesPerson>();

//gente con pocas ventas IList<SalesPerson> slackers = session.CreateQuery("from SalesPerson person where size(person.Sales) < 5").List<SalesPerson>();

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

54

Page 55: Fundamentos de Programación Traducido

Capítulo 6 - Object Relational Mappers

Esta es solo una muestra de lo que se puede hacer con HQL (el ejemplo que puede descargarse tiene algunos ejemplos ligeramente más complicados).

Carga diferidaCuando cargamos un vendedor, haciendo algo como esto: SalesPerson person = session.Get<SalesPerson>(1); la colección Sales no será cargada. Esto es porque, por defecto, las colecciones son cargadas de manera diferida. Esto es, que no tocaremos la base de datos hasta que la información sea específicamente solicitada (ej. Podemos acceder a la propiedad Sales). Podemos invalidar este comportamiento cambiando la configuración lazy=”false” en el elemento bag.

La otra, más interesante, estrategia de carga implementada por NHibernate está en las entidades en sí mismas. Frecuentemente querrás agregar una referencia a un objeto sin tener que cargar el objeto real desde la base de datos. Por ejemplo, cuando agregamos una venta (Sales) a un vendedor (SalesPerson), necesitamos especificar el modelo (Model), pero no queremos cargar cada propiedad / lo único que queremos es obtener el Id para poder guardarlo en la columna ModelId de la tabla Sales. Cuando usas session.Load<T> (id) NHibernate cargara un proxy del objeto actual (a menos de que especifiques lazy=”false” en el elemento clase). Hasta donde puede importarte, el proxy se comporta exactamente igual que el objeto real, pero ningún dato será extraído de la base de datos hasta la primera vez que lo solicitas. Esto hace posible escribir el siguiente código:

Sale sale = new Sale(session.Load<Model>(1), DateTime.Now, 46000.00);salesPerson.AddSales(sale);session.SaveOrUpdate(salesPerson);

Sin tener que tocar siquiera la base de datos para cargar el objeto Model.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

55

Page 56: Fundamentos de Programación Traducido

Capítulo 6 - Object Relational Mappers

DescargaPuedes descargar un proyecto con más ejemplos del uso de NHibernate en:

http://codebetter.com/files/folders/codebetter_downloads/entry172562.aspx. El código esta extensamente documentado para explicar varios aspectos del uso de NHibernate. (Si el vínculo de arriba no funciona, puedes tratar de descargarlo de esta dirección alterna: http://openmymind.net/CodeBetter.Foundations.zip).

En este capítuloSolo hemos tocado un poco de lo que puedes hacer con NHibernate. No hemos visto las consultas por criterio (que es una API de consulta más íntimamente ligada con tu dominio) sus capacidades de cache, filtrado de colecciones, optimización del rendimiento, registro de bitácoras, o capacidades nativas de SQL. Más allá de la herramienta de NHibernate, afortunadamente es muy probable que hayas aprendido más acerca de cómo definir relaciones entre objetos y estructuras relacionales, y soluciones alternativas de la limitada cesta incluida dentro de .NET. Es difícil abandonar el SQL escrito a mano, ir más allá de lo que es cómodo, es imposible ignorar los beneficios de las definiciones de conversión entre objetos y estructuras relacionales.

¡Estas mas allá de la mitad del camino! Espero que estés disfrutando y aprendiendo mucho. Este puede ser un buen momento para tomar un descanso de la lectura y poner manos a la obra con la aplicación gratuita de aprendizaje Canvas Learning Application.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

56

Page 57: Fundamentos de Programación Traducido

Capítulo 7 – De regreso a las bases: Memoria

De regreso a las bases: Memoria

NO ‘ENTENDER’ ÁLGEBRA NO ES ACEPTABLE PARA UN MATEMÁTICO, Y NO ‘ENTENDER’ APUNTADORES NO ES ACEPTABLE PARA PROGRAMADORES. ES DEMASIADO FUNDAMENTAL. - WARD CUNNINGHAM

or más que se intente, los lenguajes modernos de programación no pueden abstraer completamente los aspectos fundamentales de los sistemas computacionales. Por ejemplo, podemos asumir que usted se ha encontrado con las siguientes excepciones .NET:

NullReferneceException, OutOfMemoryException, StackOverflowException y ThreadAbortException. Tan importante como es para desarrolladores adopter varios patrones y técnicas de alto nivel, es igualmente importante comprender el ecosistema en el cual su programa se ejecuta. Mirando por encima de las capas provistas por el compilar de C# (o VB.NET), el CLR y el sistema operativo, nos encontramos con la memoria. Todos los programas hacen uso extensivo de la memoria del sistema e interaccionan con ella en maravillas maneras, es difícil ser un buen programador sin comprender esta interacción fundamental.

P

Mucha de la confusión sobre la memoria nace del hecho de que tanto C# y VB.NET son lenguajes administrados y que el CLR provee la recolección automática de basura. Esto ha causado que muchos desarrolladores asuman erróneamente que no necesitan preocuparse por la memoria.

Asignación de MemoriaEn .NET, como en muchos otros lenguajes, cada variable que se defina está almacenada en el stack3 o en el heap4. Estos son dos espacios separados asignados en la memoria de sistema que sirven un propósito distinto, aunque complementario. Lo que va donde está predeterminado: tipos de valor van en el stack, mientras que los tipos de referencia va en el heap. En otras palabras, todos los tipos de sistema, como char, int, long, byte, enum y cualquier estructura (ya sean definidas por.NET o por usted) van en el stack. La única excepción a esta regla son los tipos de valor que pertenecen a tipos de referencia – por ejemplo la propiedad Id de una clase User va en el heap junto con la instancia de la clase User misma.

El StackAunque estamos acostumbrados al mágico colector de basura, los valores en el stack son automáticamente administrados aún en un mundo sin colector de basura (como en C). Esto es porque cuando sea que entramos a un nuevo alcance (como un método o una sentencia If) los valores son empujados al stack y cuando salen del stack los valores son liberados. Esta es la razón por la que un stack es sinónimo a LIFO - last-in first-out (último en entrar primero en salir). Puede pensarlo en este modo: cuando se crea un nuevo alcance, por ejemplo un método, un marcador es puesto en el stack y los valores son añadidos como se necesiten. Cuando se deja ese alcance, todos los valores son liberados incluyendo el marcador del método. Esto funciona en cualquier nivel de anidado.

3 Pila (trad.)4 Cúmulo, montón (trad.)

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

57

7

Page 58: Fundamentos de Programación Traducido

Capítulo 7 – De regreso a las bases: Memoria

Hasta que veamos la interacción entre el heap y el stack, la única manera real de meterse en problemas con el stack es con StackOverflowException. Esto significa que ha usado todo el espacio disponible del stack. 99.9% del tiempo, esto indica una llamada recursiva interminable (una función que se llama a sí misma ad infinitum). En teoría esto puede ser causado por un muy muy mal diseño de sistema, aunque nunca he visto una llamada no recursiva usando todo el espacio del stack.

El HeapLa asignación de memoria en el heap no es tan simple como el stack. La mayoría de la asignación de memoria basada en el heap ocurre cuando creamos un objeto new. El compilador averigua cuanta memoria necesitaremos (lo cual no es tan difícil, aún con objetos con referencias anidadas), toma un apropiado montón de memoria y regresa el apuntador a la memoria asignada (más acerca de esto en un momento). El ejemplo más sencillo es una cadena, si cada carácter en una cadena toma 2 bytes, y creamos una nueva cadena con el valor de “Hola Mundo”, entonces el CLR necesitará asignar 22 bytes (11x2) más cualquier adicional necesitado.

Hablando de cadenas, seguramente ha oído que las cadenas son inmutables – esto es, una vez que ha sido declarada una cadena y asignado un valor, si se modifica esa cadena (cambiando el valor o concatenando otra cadena a ella), entonces una nueva cadena se crea. Esto realmente puede tener implicaciones de rendimiento negativas, y por ello la recomendación general es usar un StringBuilder para cualquier manipulación de cadenas significativa. La verdad es que cualquier objeto almacenado en el heap es inmutable con respecto a la asignación de tamaño, y cualquier cambio en el tamaño subyacente requerirá una nueva asignación. El StringBuilder, junto con algunas colecciones, parcialmente pueden sacar la vuelta a esto usando buffers internos. Una vez que el buffer se llena, la misma reasignación ocurre y algún tipo de algoritmo de crecimiento es usado para determinar el nuevo tamaño (el más simple siendo antiguoTamaño * 2). Siempre que sea posible es buena idea especificar la capacidad inicial de dichos objetos para evitar este tipo de reasignación (el constructor para tanto el StringBuilder y el ArrayList (entre muchas otras colecciones) le permiten especificar capacidad inicial).

Recolectar basura del heap es una tarea no trivial. A diferencia del stack donde el último alcance puede simplemente liberarlo, los objetos del heap no son locales a un determinado alcance. En lugar de ello, la mayoría son referencias profundamente anidadas de otros objetos referenciados. En lenguajes como en C, cuando un programador causa que la memoria sea asignada al heap, debe asegurarse también de remover del heap cuando ha terminado con él. En lenguajes administrados, el motor en tiempo de ejecución se encarga de limpiar los recursos (.NET usa un Recolector de Basura Generacional que está brevemente descrito en la Wikipedia).

Hay muchos incidentes horribles que pueden molestar a los desarrolladores mientras trabajan con el heap. Fugas de memoria no solo son posibles sino muy comunes, la fragmentación de memoria puede causar todo tipo de caos, y varios problemas de rendimiento pueden generarse gracias a comportamiento de asignación extraño o interacción con código sin administrar (lo cual .NET hace mucho debajo del agua).

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

58

Page 59: Fundamentos de Programación Traducido

Capítulo 7 – De regreso a las bases: Memoria

ApuntadoresPara muchos desarrolladores, aprender sobre apuntadores en la escuela fue una experiencia dolorosa. Representan la verdaderamente real indirección que existe entre código y hardware. Muchos más desarrolladores nunca tuvieron la experiencia de aprender sobre ellos - saltaron directamente a programar en un lenguaje que no los expone directamente. La verdad sin embargo es que cualquiera que diga que C# o Java son lenguajes sin apuntadores es simplemente un error. Como los apuntadores son el mecanismo con el cual todos los lenguajes almacenan valores en el heap, es más bien tonto no entender como son usados.

Los apuntadores representan el nexus del modelo de memoria de un sistema – esto es, los apuntadores son el mecanismo donde el stack y el heap trabajan juntos para proveer el subsistema de memoria requerido por su programa. Como discutimos anteriormente, cuando instancia un objeto new, .NET asigna un bloque de memoria al heap y regresa un apuntador al inicio de este bloque de memoria. Esto es todo lo que un apuntador es: la dirección de inicio para el bloque de memoria que contiene un objeto. La dirección no es nada más que un número único, generalmente representado en formato hexadecimal. Por lo tanto, un apuntador no es nada más que un número único que le dice a .NET donde está el objeto mismo en memoria. Esta indirección es transparente en Java o .NET, pero no en C o C++ donde se puede manipular la dirección de memoria directamente con un apuntador aritmético. En C o C++ se puede tomar un apuntador y agregar 1 a él, y así arbitrariamente cambiar a donde está apuntando (y seguramente hacer tronar el programa debido a esto).

Donde se pone interesante es donde el apuntador está realmente almacenado. Ellos en realidad siguen las mismas reglas descritas arriba: como enteros son almacenados en el stack – al menos, claro, que ellos formen parte de una referencia a un objeto y entonces estarán en el heap con el resto del objeto. Puede no ser claro aún, pero esto significa que ultimadamente, todos los objetos heap están enraizados al stack (posiblemente a través de numerosos niveles de referencias). Veamos primero este ejemplo simple:

static void Main(string[] args){ int x = 5; string y = "codebetter.com";}

Del código de arriba, terminaremos con 2 valores en el stack, el entero 5 y el apuntador a nuestra cadena, así como también precisamente el valor en el heap. Aquí una representación gráfica:

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

59

Page 60: Fundamentos de Programación Traducido

Capítulo 7 – De regreso a las bases: Memoria

Cuando salimos de nuestra function main (olvidémonos del hecho de que el programa se parará), nuestro stack liberará todos los valores locales, lo que significa que tanto el valor de x como de y se perderán. Esto es significativo porque la memoria asignada en el heap todavía contiene nuestra cadena, pero hemos perdido toda referencia a ella (no hay algún apuntador apuntándola). En C o C++ esto resulta en una fuga de memoria – sin una referencia a nuestra dirección en el heap no podemos liberarla de la memoria). En C# o Java, nuestro confiable recolector de basura detectará el objeto sin referencia y lo liberará.

Veremos ejemplos más complejos, que aparte de tener más flechas apuntando, es básicamente el mismo.

public class Empleado{ private int _empleadoId; private Empleado _gerente;

public int EmpleadoId { get { return _empleadoId; } set { _empleadoId = value; } } public Empleado Gerente { get { return _gerente; } set { _gerente = value; } }

public Empleado(int empleadoId) { _empleadoId = empleadoId; }}public class Prueba{ private Empleado _subordinado;

void HacerAlgo() { Empleado jefe = new Empleado(1); _subordinado = new Empleado(2); _subordinado.Gerente = _jefe; }}

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

60

Page 61: Fundamentos de Programación Traducido

Capítulo 7 – De regreso a las bases: Memoria

Interesantemente, cuando salimos de nuestro método, la variable jefe se liberará del stack, pero el subordinado, que está definido por el alcance padre, no. Esto significa que el recolector de basura no tendrá nada que limpiar porque los dos valores del heap seguirán siendo referenciados (uno directamente del stack, y el otro indirectamente del stack a través del objeto referenciado.

Como puede ver, los apuntadores definitivamente juegan una parte importante tanto en C# como en VB.NET. Como el apuntador aritmético no está disponible en ninguno de estos lenguajes, los apuntadores son grandemente simplificados y con suerte fácilmente entendidos.

Modelo de Memoria en la PrácticaAhora veremos al real impacto que esto tiene con nuestras aplicaciones. Tome en cuenta que entender el modelo de memoria no solo ayudará a evitar problemas, pero también lo ayudará a escribir mejores aplicaciones.

BoxingEl Boxing ocurre cuando un tipo de valor (almacenado en el stack) es coaccionado en el heap. El Unboxing ocurre cuando estos tipos de valor son puestos de vuelta al stack. La manera más simple de coaccionar un tipo de valor, como un entero, en el heap es haciendo un cast 5con él:

int x = 5;object y = x;

Un escenario más común donde el boxing ocurre es cuando se provee un tipo de valor a un método que acepta un objeto. Esto es común con colecciones en .NET 1.x antes de la introducción de genéricas (generics). Las clases de colecciones no genéricas mayormente trabajan con el tipo de objeto, así que el código siguiente resulta en un boxing y unboxing:

ArrayList usuariosIds = new ArrayList(2);usuariosIds.Add(1);usuariosIds.Add(2);;int primerId = (int)usuariosIds[0];

El real beneficio de genéricas es el incremento de seguridad de tipos, pero también se encargan de la deficiencia en desempeño asociados al boxing. En muchos casos no notaría esta deficiencia, pero en algunas situaciones, como en las colecciones grandes, sí que lo notará. Sin importar si es algo que debería importarle, boxing es un ejemplo primario de cómo el sistema subyacente de memoria puede tener un impacto en su aplicación.

ByRefSin un buen entendimiento de apuntadores, es virtualmente imposible entender pasar un valor por referencia o por valor. Los desarrolladores generalmente entienden la implicación de pasar un tipo de

5 Conversión implícita

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

61

Page 62: Fundamentos de Programación Traducido

Capítulo 7 – De regreso a las bases: Memoria

valor, como un entero, por referencia, pero pocos comprenden porque pasar una referencia por referencia. ByRef y ByVal afectan la referencia y el tipo de valor por igual – entendiendo que siempre trabajan contra el valor subyacente (que en el caso de un tipo de referencia significa que trabajan contra el apuntador y no el valor). Usando ByRef es la única situación común donde .NET no resolverá automáticamente la indirección del apuntador (pasando por referencia o como un parámetro de salida no está permitido en Java).

Primero veremos como ByVal/ByRef afecta los tipos de valor. Dado el siguiente código:

public static void Main(){ int contador1 = 0; SiembraContador(contador1); Console.WriteLine(contador1);

int contador2 = 0; SiembraContador(ref contador2); Console.WriteLine(contador2);}

private static void SiembraContador(int contador){ contador = 1;}private static void SiembraContador(ref int contador){ contador = 1;}

Podemos esperar una salida de 0 precedida de 1. La primer llamada no pasa contador1 por referencia, lo que significa que una copia de contador1 es pasado a SiembraContador y los cambios hechos dentro son locales a la función. En otras palabras, estamos tomando el valor en el stack y duplicándolo a otra localidad del stack.

En el Segundo caso estamos realmente pasando el valor por referencia lo que significa que ninguna copia está siendo creada y los cambios no son localizados a la función SiembraContador.

El comportamiento de los tipos de referencia es exactamente el mismo, sin embargo no puede ser aparente al principio. Veremos dos ejemplos. El primero usa una clase AdministracionPagos para cambiar las propiedades de una Empleado. En el código de abajo vemos que tenemos dos empleados y en ambos casos estamos otorgando un aumento de $2000. La única diferencia es que uno pasa el empleado por referencia mientras que el otro lo pasa por valor. ¿Puede adivinar la salida?

public class Empleado{ private int _salario;

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

62

Page 63: Fundamentos de Programación Traducido

Capítulo 7 – De regreso a las bases: Memoria

public int Salario { get {return _salario;} set {_salario = value;} } public Empleado(int salarioInicial) { _salario = salarioInicial; }}

public class AdministracionPagos{ public static void DarAumento(Empleado empleado, int aumento) { empleado.Salario += aumento; } public static void DarAumento(ref Empleado empleado, int aumento) { empleado.Salario += aumento; }}

public static void Main(){ Empleado empleado1 = new Empleado(10000); AdministracionPagos.DarAumento(empleado1, 2000); Console.WriteLine(empleado1.Salario);

Empleado empleado2 = new Empleado(10000); AdministracionPagos.DarAumento(ref empleado2, 2000); Console.WriteLine(empleado2.Salario);}

En ambos casos, la salida es 12000. A primera vista, esto parece diferente a lo que vimos con los tipos de valor. Lo que sucede es que pasar por referencia tipos de referencia por valor en verdad pasa una copia del valor, pero no del valor en heap. En lugar de ello, pasamos una copia de nuestro apuntador. Y como un apuntador y una copia del apuntador apuntan a la misma memoria en el heap, un cambio hecho en uno se ve reflejado en el otro.

Cuando se pasa un tipo de referencia por referencia, se está pasando el apuntador mismo en lugar de una copia del apuntador. Esto hace preguntarnos, ¿cuándo realmente se pasa un tipo de referencia por referencia? La única razón para pasar por referencia es cuando se requiere modificar al apuntador mismo – es decir, a donde apunta. Esto puede resultar en desagradables efectos secundarios – por lo cual es una buena práctica que las funciones que así lo quieran deben especificar que necesitan el parámetro pasado por referencia. Veamos nuestro segundo ejemplo.

public class Empleado

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

63

Page 64: Fundamentos de Programación Traducido

Capítulo 7 – De regreso a las bases: Memoria

{ private int _salario; public int Salario { get {return _salario;} set {_salario = value;} } public Empleado(int salarioInicial) { _salario = salarioInicial; }}

public class AdministracionPagos{ public static void Terminate(Empleado empleado) { empleado = null; } public static void Terminate(ref Empleado empleado) { empleado = null; }}

public static void Main(){ Empleado empleado1 = new Empleado(10000); AdministracionPagos.Terminate(empleado1); Console.WriteLine(empleado1.Salario);

Empleado empleado2 = new Empleado(10000); AdministracionPagos.Terminate(ref empleado2); Console.WriteLine(empleado2.Salario);}

Intente averiguar qué pasará y porque. Una pista: una excepción será lanzada. Si dedujo que la llamada a empleado1.Salario resultará en 10000 mientras que la segunda provocará una NullReferenceException entonces está en lo cierto. En el primer caso estamos asignando a una copia del apuntador original a null – no tiene ningún impacto en lo que está apuntando empleado1. En el segundo caso, estamos pasando una copia pero con el mismo valor de stack usado por empleado2. Entonces asignar al empleado a null es lo mismo que escribir empleado2 = null;.

No es muy común que se quiera cambiar la dirección apuntada por una variable desde un método separado – por lo que la única vez que tal vez vea un tipo de referencia pasado por referencia es cuando quiera regresar múltiples valores desde un llamado a función (en cuyo caso sería mejor usar un parámetro out, o usar un acercamiento OO más puro). El ejemplo de arriba verdaderamente señala los peligros de jugar en un ambiente donde las reglas no son comprendidas completamente.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

64

Page 65: Fundamentos de Programación Traducido

Capítulo 7 – De regreso a las bases: Memoria

Fugas de Memoria AdministradasYa vimos un ejemplo de cómo se mira una fuga de memoria en C. Básicamente, si C# no tuviera un recolector de basura, el siguiente código tuviera una fuga:

private void HazAlgo(){ string nombre = "dunas";}

Nuestro valor en stack (un apuntador) será liberado, y con él se irá la única manera que tenemos referencia a la memoria creada para almacenar nuestra cadena. Dejándonos con ningún método de liberarla. Este no es un problema en .NET porque tiene un recolector de basura que se fija en memoria sin referencia y la libera. Sin embargo, un tipo de fuga de memoria es todavía posible si se mantienen las referencias indefinidamente. Esto es común en aplicaciones grandes con referencias profundamente anidadas. Estos pueden ser difícil de identificar pues la fuga puede ser muy pequeña y la aplicación puede que no sea ejecutada por largo tiempo.

Ultimadamente cuando su programa termina el sistema operativo reclamará toda la memoria, fugada o no. Sin embargo, si empieza a ver OutOfMemoryException y no está usando datos inusualmente grandes, hay una buena posibilidad de tener una fuga de memoria. .NET tiene herramientas para ayudarlo, pero seguramente querrá aprovechar un verificador de memoria comercial como dotTrace o ANTS Profiler. Al estar cazando fugas de memoria estará viendo por objetos fugados (lo que es muy fácil de hacer tomando dos muestras de su memoria y compararlas), rastreando a través de todos los objetos que aún mantienen una referencia a ellos y corregir el problema.

Hay una situación en específico que es conveniente mencionar como una causa común de fugas de memoria: eventos. Si, en una clase, se registra un evento, una referencia es creada a su clase. A menos que se deregistre del evento el ciclo de vida de sus objetos serán finalmente determinados por la fuente del evento. En otras palabras, si ClassA (el escucha) registra un evento de ClassB (la fuente del evento) una referencia es creada de ClassB a ClassA. Hay dos soluciones: deregistrar de los eventos cuando hayamos terminado (el patrón IDisposable es la solución idónea), o usar el Patrón WeakEvent o una versión simplificada.

FragmentaciónOtra causa del OutOfMemoryException tiene que ver con la fragmentación de memoria. Cuando la memoria es asignada en el heap siempre es un bloque continuo. Esto significa que la memoria disponible debe ser localizada para un bloque suficientemente grande. Al ejecutarse su programa, el heap se vuelve cada vez más fragmentado (como en su disco duro) y puede terminar con mucho espacio, pero repartido en una manera que lo hace inusable. Bajo circunstancias normales, el recolector de basura compactará el heap conforme vaya liberando memoria. Al compactar memoria, las direcciones de los objetos cambian y .NET se asegura de actualizar todas las referencias apropiadamente. Sin embargo, algunas veces .NET no puede mover un objeto: sobre todo cuando el objeto está fijado a una dirección de memoria específica.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

65

Page 66: Fundamentos de Programación Traducido

Capítulo 7 – De regreso a las bases: Memoria

FijamientoEl fijamiento (pinning) ocurre cuando un objeto está anclado a una dirección específica en el heap. La memoria fija no puede ser compactada por el recolector de basura resultando en fragmentación. ¿Por qué se fijan los valores? La causa más común es porque su código está interactuando con código no administrado. Cuando el recolector de basura de .NET compacta el heap, actualiza todas las referencias en el código administrado, pero no tiene manera de entrar al código no administrado y hacer lo mismo. Por lo tanto, antes de interoperar primero debe fijar objetos a la memoria. Como muchos métodos en el .NET Framework dependen de código no administrado, el fijamiento puede ocurrir sin que se sepa de ello (el escenario con el que estoy familiarizado son las clases Socket .NET que dependen de implementaciones no administradas y buffers de pins).

Una manera común de sacar la vuelta a este tipo de fijamiento es declarar objetos grandes que no causan mucha fragmentación como los más pequeños (esto es incluso más verdadero considerando que objetos grandes son puestos en un heap especial (llamado Large Object Heap (LOH) que no es compactado). Por ejemplo, en lugar de crear cientos de buffers de 4KB, puede crear un buffer grande y asignar pedazos de él usted mismo. Para un ejemplo y obtener más información de fijamiento, sugiero ver el apost avanzado de Greg Young acerca de fijamiento y sockets asíncronos.

Hay una segunda razón por la cual un objeto puede ser fijado – cuando usted explícitamente lo hace. En C# (no en VB.NET) si usted compila su ensamblado con la opción unsafe , puede fijar un objeto con la sentencia fixed. Mientras que fijamiento extensivo presurisa el sistema, el uso justo de la sentencia fixed puede grandemente mejorar el rendimiento. ¿Por qué? Porque un objeto fijado puede ser manipulado directamente con aritmética de apuntador – esto no es posible si el objeto no está fijado pues el recolector de basura puede reasignar su objeto en algún otro sitio de la memoria.

Tome como ejemplo este eficiente conversión de una cadena ASCII a entera que se ejecuta 6 veces más rápido que con int.Parse.

public unsafe static int Parse(string cadenaAConvertir){ int valor = 0; int tamanio = cadenaAConvertir.Length; fixed(char* caracteres = cadenaAConvertir) { for (int i = 0; i < tamanio; ++i) { valor = 10 * valor + (caracteres[i] - 48); } } return valor;}

A menos que haga algo anormal, no puede haber razón alguna para marcar su ensamblado como unsafe y tomar ventaja de la sentencia fixed. El código de arriba fácilmente colgarse (pase null como la

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

66

Page 67: Fundamentos de Programación Traducido

Capítulo 7 – De regreso a las bases: Memoria

cadena y vea que pasa), no es tan rico en características como int.Parse, y en la escala de cosas a considerar es extremadamente riesgoso mientras que no provee beneficios.

Asignar a nullAsí que, ¿debería asignar sus tipos de referencia a null cuando ha terminado con ellos? Por supuesto que no. Una vez que la variable sale de su alcance, se libera del stack y la referencia es removida. Si no puede esperar a que se salga del alcance, tal vez necesite refactorizar su código.

Finalización DeterminísticaA pesar de la presencia del recolector de basura, los desarrolladores deben tomar cuidado de manejar algunas de sus referencias. Esto porque algunos objetos dependen de recursos vitales o limitados, como los manejadores de archivos o conexiones de bases de datos que deben ser liberadas tan pronto como sea posible. Esto es problemático pues no sabemos cuando el recolector de basura va a ejecutarse – por naturaleza el recolector de basura solo se ejecuta cuando la memoria escasea. Para compensar, las clases que dependen de estos recursos pueden hacer uso del patrón Disposable. Todos los desarrolladores .NET puede que sean familiares con este patrón, así como con su implementación ) la interfaz IDisposable), así que no ahondaremos en lo que ya sabe. Con respecto a este capítulo, es simplemente importante que entienda el rol determinístico de la finalización. No libera memoria usada por el objeto. Libera recursos. En el caso de las conexiones con bases de datos por ejemplo, libera la conexión de regreso al pool para que pueda ser reusada.

Si se olvida de llamar al Dispose en un objeto que implementa IDisposable, el recolector de basura lo hará por usted (eventualmente). No debería confiar ciegamente en este comportamiento, pues el problema de recursos limitados es muy real (es relativamente trivial intentarlo con un ciclo que abre conexiones con una base de datos). Puede estar preguntándose porque algunos objetos exponen métodos tanto Close y Dispose, y cuál de ellos llamar. En todos los casos que he visto los dos son generalmente equivalentes – así que es realmente una cuestión de gusto. Sugeriría que aprovechara la sentencia using y se olvidara de Close. Personalmente la encuentro frustrante (e inconsistente) que ambas sean expuestas.

Finalmente, si está construyendo una clase que pueda ser beneficiada de una finalización determinística encontrará que implementar el patrón IDisposable es simple. Una guía sencilla está disponible en MSDN.

En este capítuloStacks, heaps y apuntadores pueden ser abrumadores al principio. Dentro del contexto de los lenguajes administrados, no hay mucho de ello realmente. Los beneficios de entender estos conceptos son tangibles en la programación del día a día, e invaluables cuando algún comportamiento inesperado ocurre. Puede ser el programador que causa raros NullReferenceExceptions y OutOfMemoryExceptions, o el que tiene que corregirlos.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

67

Page 68: Fundamentos de Programación Traducido

Capítulo 7 – De regreso a las bases: Memoria

.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

68

Page 69: Fundamentos de Programación Traducido

Capítulo 8 – De regreso a las bases: Excepciones

De regreso a las bases: Excepciones

FALLA RÁPIDO - JIM SHORE

as excepciones son construcciones tan poderosas que los desarrolladores se pueden sentir agobiados y un tanto a la defensiva al lidiar con ellas. Esto es desafortunado porque las excepciones realmente representan una oportunidad clave para que desarrolladores hagan sus

sistemas considerablemente más robustos. En este capítulo veremos tres distintos aspectos de las excepciones: manejo, creación y lanzamiento. Considerando que las excepciones son inamovibles usted no puede ni correr, ni esconderse de ellas, así que mejor contrólelas.

LManejando Excepciones Su estrategia para manejar excepciones debería contemplar dos reglas de oro:

1. Sólo atrape aquellas excepciones por las cuales pueda hacer algo, y 2. Usted no puede hacer nada ante la gran mayoría de las excepciones

La mayoría de los desarrolladores novatos hace exactamente lo contrario a la primera regla, y luchan desesperadamente contra la segunda. Cuando su aplicación realice algo de manera realmente excepcional y fuera de la operación normal, lo mejorar que se puede hacer es fallar en ese lugar y en ese momento. Si no lo hace, no sólo perderá información acerca de su misterioso problema, sino que se arriesga a poner su aplicación en un estado desconocido, el cual puede llevarlo a resultados de consecuencias mucho peores.

En cualquier momento usted puede encontrarse escribiendo una sentencia try/catch, y preguntarse si realmente puede hacer algo si sucede una excepción. Si su base de datos se cae, ¿realmente puede escribir código para recuperarla? ¿No sería mejor desplegar un amigable mensaje de error al usuario y notificar el problema? Es duro aceptarlo en un principio, pero algunas veces es mejor colapsar, registrar el error y terminar. Aún para sistemas de misión crítica, si se emplea una base de datos de forma normal, ¿qué puede hacer si esta se cae? Este orden de ideas no es exclusivo para bases de datos o para fallas ambientales, sino también para errores comunes. Si al convertir un valor de configuración a entero se recibe una excepción de formato, ¿tendrá sentido continuar como si todo estuviera bien? Seguramente no.

Por supuesto, si puede controlar una excepción definitivamente debe hacerlo - pero asegúrese de capturar únicamente el tipo de excepción que pueda controlar. Capturar excepciones y no controlarlas realmente se llama indigestión de excepción (prefiero llamarlo ilusiones) y es codificar mal. Un ejemplo que frecuentemente veo tiene que ver con la validación de las entradas. Por ejemplo, veamos cómo no deberíamos de tratar el IdCategoría que se pasa por una consulta en una página ASP.NET.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

69

8

Page 70: Fundamentos de Programación Traducido

Capítulo 8 – De regreso a las bases: Excepciones

int IdCategoría;try{ IdCategría = int.Parse(Request.QueryString["IdCategoría"]);}catch(Exception){ IdCategoría = 1;}

El problema con el código anterior es que independientemente del tipo de excepción que se produzca, se tratará del mismo modo. ¿Podría el valor 1 manejar una excepción de desbordamiento de memoria? En lugar del código anterior debería capturar una excepción específica:

int IdCategoría;try{ IdCategoría = int.Parse(Request.QueryString["IdCategoría"])}catch(FormatException){ IdCategoría = -1;}

(un mejor acercamiento a este problema sería utilizar la función de int.TryParse introducida en .NET 2.0 - sobre todo teniendo en cuenta que int.Parse puede producir otros dos tipos de excepciones que nos gustaría controlar de la misma manera, pero esa es otra historia).

RegistrosA pesar de que no se controlen la mayoría de la excepciones, usted debería registrar todas y cada una de ellas. Idealmente, podría centralizar su registro – un HttpModule de OnError es su mejor opción para una aplicación ASP.NET o servicio web. He visto a menudo a los desarrolladores capturar excepciones donde estas producen sólo por registrarlas y volverlas a lanzar. Esto provoca una gran cantidad de código innecesario y repetitivo – es mejor dejar que las excepciones emerjan hasta el código y registrar todas las excepciones en la frontera de su sistema. Para precisar qué implementación de registro de excepciones utilizará deberá saber que tan críticas son. Tal vez querrá notificar por correo electrónico tan pronto como se produzca una excepción, o tal vez pueda simplemente registrarla en un archivo o base de datos que revise diariamente o tal vez tenga otro proceso para enviar un resumen diario. Muchos desarrolladores aprovechan frameworks para generar registros como log4net o Microsoft´s Logging Application Block.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

70

Palabras de advertencia basadas en una mala experiencia personal: algunos tipos de excepciones tienden a multiplicarse. Si opta por enviar correos siempre que ocurre una excepción podrá fácilmente saturar su servidor de correo. Una solución más inteligente implementaría algún tipo de buffer o agregación.

Page 71: Fundamentos de Programación Traducido

Capítulo 8 – De regreso a las bases: Excepciones

LimpiezaEn el capítulo anterior hablamos de la finalización determinista con respecto a la naturaleza perezosa del recolector de basura. Las excepciones añaden complejidad a esta situación ya que su naturaleza abrupta puede causar que el método Dispose no sea llamado. Un error al llamar a la base de datos es un ejemplo clásico:

SqlConnection conexión = new SqlConnection(FROM_CONFIGURATION)SqlCommand comando = new SqlCommand("AlgúnSQL", conexión);conexión.Open();comando.ExecuteNonQuery();comando.Dispose();conexión.Dispose();

Si la instrucción ExecuteNonQuery lanza una excepción, ni el comando ni la conexión serán eliminados con Dispose. La solución es utilizar Try/Finally:

SqlConnection conexión;SqlCommand comando;try{ conexión = new SqlConnection(FROM_CONFIGURATION) comando = new SqlCommand("AlgúnSQL", conexión); conexión.Open(); comando.ExecuteNonQuery();}finally{ if (comando != null) { comando.Dispose(); } if (conexión != null) { conexión.Dispose(); }}

o la sentencia sintácticamente más apropiada using (que compila lo mismo):

using (SqlConnection conexión = new SqlConnection(FROM_CONFIGURATION))using (SqlCommand comando = new SqlCommand("AlgúnSQL", conexión)){ conexión.Open(); comando.ExecuteNonQuery();}

El punto es que incluso si usted no puede controlar una excepción, y debe centralizar todos su registro, es necesario ser conscientes de dónde pueden surgir las excepciones - especialmente en lo que se refiere a las clases que implementan IDiposable.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

71

Page 72: Fundamentos de Programación Traducido

Capítulo 8 – De regreso a las bases: Excepciones

Lanzar ExcepcionesNo hay una regla mágica para generar excepciones ni la hay para capturarlas (de nuevo, la regla es “no capture excepciones a menos que realmente las pueda controlar”). Sin embargo, generar excepciones, sean o no las suyas (lo que trataremos a continuación), aún es bastante sencillo. Primero analizaremos la mecánica real de generar excepciones, que se basa en la instrucción throw. A continuación examinaremos cuándo y por qué es realmente deseable producir excepciones.

Mecanismo para generarlas Usted puede tanto lanzar una nueva excepción como redirigir una excepción capturada. Para lanzar una excepción nueva, simplemente cree una nueva excepción y láncela.

throw new Exception("Sucede algo malo!");

//o

Exception ex = new Exception("Sucede algo malo!");throw ex;

Agregué el segundo ejemplo porque algunos desarrolladores piensan que las excepciones son algo especial y único - pero la verdad es que son igual que cualquier otro objeto (excepto que heredan de System.Exception, que a su vez hereda de System.Object). De hecho, el crea una nueva excepción no significa que se tenga que lanzarla - aunque probablemente siempre lo hará.

En ocasiones necesitará redirigir una excepción porque, aunque no pueda controlar la excepción, necesita ejecutar código en cuanto se produzca la excepción. El ejemplo más común es tener que deshacer una transacción cuando algo falla:

ITransaction transacción = null;try{ transacción = session.BeginTransaction(); // Hacer algo transaction.Commit();}catch{ if (transacción != null) { transaction.Rollback(); } throw;}finally{ //Limpiar}

En el ejemplo anterior el sentido de la sentencia throw es hacer nuestras capturas transparente. Es decir, un controlador de la cadena de la ejecución no tendrá ninguna indicación de que capturamos una excepción. En la mayoría de los casos, esto es lo que queremos – el revertir nuestra transacción no es de

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

72

Page 73: Fundamentos de Programación Traducido

Capítulo 8 – De regreso a las bases: Excepciones

ayuda para nadie. Sin embargo, hay una manera de redirigir una excepción de forma que parezca que se produjo desde nuestro código:

catch (HibernateException ex){ if (transaction != null) { transaction.Rollback(); } throw ex;}

Al redirigir explícitamente la excepción, el seguimiento de la pila se modifica de forma que la línea de re direccionamiento parece ser la fuente. Esto casi siempre es una mala idea, pues se pierde información vital. Por lo que debe tener cuidado al redirigir excepciones - la diferencia es sutil pero importante.

Si usted encuentra en una situación donde cree que desea redirigir una excepción con el controlador como la fuente, un mejor enfoque consiste en utilizar una excepción anidada:

catch (HibernateException ex){ if (transaction != null) { transaction.Rollback(); } throw new Exception("Correo electronico en uso", ex);}

De esta forma la traza de la pila original es accesible mediante la propiedad InnerException expuesta por todas las excepciones.

Cuándo Lanzar ExcepcionesEs importante conocer cómo lanzar excepciones. Un aspecto más interesante es saber cuándo y porque debería enviarlas. Tener código de alguien más que hizo cosas indebidas y que tiren la aplicación es una cosa. Escribir tu propio código que haga lo mismo parece tonto. Sin embargo, un buen desarrollador no teme emplear inteligentemente las excepciones.

Existen realmente dos niveles de reflexión sobre cómo se deben utilizar excepciones. El primer nivel, que es universalmente aceptado, es que usted no debería dudar en plantear una excepción cuando se produce una situación realmente excepcional. Mi ejemplo favorito es el análisis de archivos de configuración. Muchos desarrolladores utilizan generosamente valores predeterminados para cualquier entrada no válida. Esto está bien en algunos casos, pero en otros puede poner el sistema en un estado poco fiable o inesperado. Otro ejemplo podría ser una aplicación de Facebook que obtiene un resultado inesperado de una llamada a la API. Usted puede pasar por alto el error, o podría generar una excepción, registrarla (de modo que pueda corregir, ya que podría haber cambiado la API) y presentar un mensaje útil para los usuarios.

La otra creencia es que las excepciones no deberían reservarse para situaciones excepcionales, sino que deberían utilizarse en cualquier situación en la que no se puede ejecutar el comportamiento esperado. Este enfoque está relacionado con el diseño por contrato - una metodología que estoy adoptando más y

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

73

Page 74: Fundamentos de Programación Traducido

Capítulo 8 – De regreso a las bases: Excepciones

más cada día. Esencialmente, si el método de GuardarUsuario no es capaz de Guardar el usuario, debe producir una excepción.

En lenguajes como C#, VB.NET y Java, que no son compatibles con los mecanismo de diseño por contrato, este enfoque puede tener resultados diversos. Una tabla Hash devuelve null cuando no se encuentra una clave, pero un diccionario produce una excepción - el comportamiento impredecible no ayuda (si siente curiosidad de saber por qué ellos funcionan de forma diferente revise la publicación de Brad Abrams en su blog). Existe también una línea que divide lo que constituye el flujo de control y lo que se considera una excepción. Las excepciones no deberían utilizarse para controlar una lógica como if/else, pero si es tan grande el papel que desempeñan en una biblioteca, probablemente los programadores deberían utilizarlas como tal (el método de int.Parse es un buen ejemplo de esto).

En general, me resulta fácil decidir qué debería y no debería generar una excepción. Generalmente me hago preguntas como:

• Es esto excepcional,• Es esto lo esperado, • Puedo continuar haciendo algo significativo en este momento y• Esto es algo que debería realizarse consciente de modo que yo puedo arreglarlo, o al menos

darle una revisión.

Quizás lo más importante por hacer cuando se generan excepciones, o al trabajar con excepciones en general, es pensar en el usuario. La gran mayoría de los usuarios son ingenuos en comparación con los programadores y pueden fácilmente caer en pánico cuando se presentan mensajes de error. Jeff Atwood recientemente publicó en su blog la importancia de estrellarse responsablemente.

• ¡NO ES RESPONSABILIDAD DEL USUARIO AVISARLE DE LOS ERRORES!

• NO EXPONGA A LOS USUARIOS A LA PANTALLA AZUL DEL SISTEMA.

• MANTENGA PÚBLICO EL REGISTRO DETALLADO DE LOS ERRORES DE SU APLICACIÓN.

Los usuarios no deberían estar expuestos a la pantalla azul de Windows (no se piense que dado que la vara está en una posición baja está bien ser perezoso).

Creando Excepciones PersonalizadasUno de los aspectos más olvidados del diseño dirigido por dominio son las excepciones personalizadas. Las excepciones desempeñan un papel importante en cualquier dominio empresarial, por lo que cualquier intento serio de modelar un dominio empresarial debe incluir la codificación de excepciones personalizadas. Esto es especialmente cierto si cree que debe utilizar excepciones siempre que un método no hace lo que dice que hará. Si un estado de flujo de trabajo no es válido tiene sentido iniciar su propia excepción personalizada de WorkflowException e incluso adjuntar información específica que

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

74

Page 75: Fundamentos de Programación Traducido

Capítulo 8 – De regreso a las bases: Excepciones

podría no sólo ayudarle a identificar un error potencial, sino que también podría ser de utilidad para presentar información significativa al usuario.

Muchas de las excepciones que creo son nada más excepciones de marcado - es decir, amplían la clase base de System.Exception y no proporcionan nada adicional. Comparo estas interfaces de marcador (o atributos de marcador), tales como la interfaz INamingContainer. Son particularmente útiles porque permiten evitar la indigestión de excepciones. Tomemos el siguiente código como ejemplo. Si el método Guardar() no inicia una excepción personalizada cuando se produce un error en la validación, no tendremos más remedio que tragarnos todas las excepciones:

try{ usuario.Guardar();}catch{ Error.Text = usuario.ObtenerErrores(); Error.Visible = true;}

//versus

try{ usuario.Guardar();}catch(ValidationException ex){ Error.Text = ex.GetValidationMessage(); Error.Visible = true;}

En el ejemplo anterior también se muestra cómo podemos extender las excepciones para proporcionar comportamientos más personalizado y específicamente relacionados con nuestras excepciones. Esto puede ser tan simple como un ErrorCode, para información más compleja y como un PermissionException que expone el permiso del usuario y el permiso necesario que falta.

Por supuesto, no todas las excepciones están vinculadas al dominio. Es común ver más excepciones orientadas a la operación. Si confía en un servicio web que devuelve un código de error, es muy posible que deba hacer ajustes en sus excepciones personalizada para detener la ejecución (Recuerde, falle rápido) y aproveche su infraestructura de registro.

Crear una excepción personalizada es realmente un proceso de dos pasos. Primero (y técnicamente esto es todo lo que se necesita) crear una clase, con un nombre significativo, que hereda de System.Exception.

public class UpgradeException : Exception

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

75

Page 76: Fundamentos de Programación Traducido

Capítulo 8 – De regreso a las bases: Excepciones

{}

El paso extra que puede aplicar es marcar su clase con SerializeAttribute y siempre ofrecer al menos cuatro constructores:

public LaExcepción() public LaExcepción(string mensaje) public LaExcepción(string mensaje, Exception ExcepciónReferida) protected LaExcepción(SerializationInfo información, StreamingContext contexto)

Los tres primeros permiten que la excepción se utilice en una forma esperada. El cuarto se utiliza para admitir la serialización ya que .NET requiere serializar las excepciones - lo que significa que también debería implementar el método GetObjectData. El propósito de la serialización es que en caso de tener propiedades personalizadas, que desee sobrevivan a la ejecución las pueda serializar y deserializar. A continuación se muestra el ejemplo completo:

[Serializable]public class ExcepciónMejorada : Exception{ private int _IdMejora;

public int IdMejora { get { return _IdMejora; } }

public ExcepciónMejorada (int IdMejora) { _IdMejora = IdMejora; } public ExcepciónMejorada (int IdMejora, string mensaje, Exception inner) : base(mensaje, inner) { _IdMejora = IdMejora; } public ExcepciónMejorada (int IdMejora, string mensaje) : base(mensaje) { _IdMejora = IdMejora; } protected ExcepciónMejorada (SerializationInfo info, StreamingContext c) : base(info, c) { if (info != null) { _IdMejora = info.GetInt32("IdMejora"); } } public override void GetObjectData(SerializationInfo i, StreamingContext c) { if (i != null) { i.AddValue("IdMejora", _IdMejora); }

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

76

Page 77: Fundamentos de Programación Traducido

Capítulo 8 – De regreso a las bases: Excepciones

base.GetObjectData(i, c) }}

En este Capítulo Puede tardar bastante un cambio fundamental en la perspectiva para apreciar todo lo que las excepciones pueden ofrecer. Las excepciones no son algo que deba ser temido o de lo que debamos protegernos, sino más bien deben ser tratadas como información vital sobre la salud del sistema. Se debe evitar la indigestión por excepciones. No se deben atrapar una excepción a menos que realmente la pueda controlar. Es igualmente importante hacer uso de las excepciones incorporadas y el de las excepciones propias cuando algo inesperado ocurre dentro del código. Incluso se puede expandir este patrón para cualquier método que no hace lo que dice que hará. Por último, las excepciones son una parte del modelado del negocio. Como tal, las excepciones no sólo son útiles para fines operacionales sino también deberían formar parte del modelado de su dominio general.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

77

Page 78: Fundamentos de Programación Traducido

Capítulo 9 – De regreso a las bases: Proxy esto y Proxy aquello

De regreso a las bases: Proxy Esto y Proxy Aquello

UNA DE LAS BELLEZAS DE LA PROGRAMACIÓN ORIENTADA A OBJETOS ES LA REUTILIZACIÓN DE CÓDIGO A TRAVÉS DE LA HERENCIA, PERO PARA LOGRARLA LOS PROGRAMADORES DEBEN REALMENTE DE DEJAR A OTROS PROGRAMADORES REUTILIZAR EL CÓDIGO. - GARY SHORT

ocas palabras clave son tan simples pero tan sorprendentemente poderosas como virtual en C# (overridable en VB.NET). Cuando se marca un método como virtual se está permitiendo que una clase que hereda de otra pueda cambiar su comportamiento. Sin esta funcionalidad, la

herencia y el polimorfismo no serían de mucha utilidad. Un ejemplo simple, ligeramente modificado y extraído de Programming Ruby (ISBN: 978-0-9745140-5-5), cambia el comportamiento al método to_s de la clase Song (ToString) que a su vez hereda de la clase KaraokeSong,se muestra a continuación

Pclass Song def to_s return sprintf("Song: %s, %s (%d)", @name, @artist, @duration) endend

class KaraokeSong < Song def to_s return super + " - " @lyrics endend

El código anteriormente mostrado ejemplifica la manera en la cual clase KaraokeSong es capaz de cambiar el comportamiento por defecto de su clase base. La especialización no se trata exclusivamente de datos sino también de comportamiento.

Quizás te hayas dado cuenta en ese mismo código, que el método base to_s no está marcado como virtual. Esto es porque en muchos lenguajes, incluyendo Java, los métodos son virtuales por defecto. Esto representa una diferencia de opinión fundamental entre los diseñadores de lenguaje Java y los diseñadores de C#/VB.NET. En C# los métodos son finales por defecto y los desarrolladores explícitamente deben permitir el cambio de comportamiento (override mediante la palabra clave virtual). En Java los métodos son virtuales por defecto y los desarrolladores explícitamente deben de prohibir el cambio de comportamiento u override mediante la palabra clave final)

Típicamente los métodos virtuales son discutidos en el contexto de la herencia de los modelos de dominio, por ejemplo: Una CanciónKaraoke hereda de Canción o un Perro hereda de Mascota.

Estos conceptos son muy importantes pero ya están bien documentados por lo tanto examinaremos los métodos virtuales mediante un acercamiento más técnico: Los proxies

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

78

9

Page 79: Fundamentos de Programación Traducido

Capítulo 9 – De regreso a las bases: Proxy esto y Proxy aquello

Patrón del Dominio del ProxyUn proxy es algo que se comporta como otra cosa. Si lo pusiéramos en términos legales, un proxy es alguien a quien se le da la autoridad de votar o actuar a nombre de alguien más. Por lo tanto el proxy tiene los mismos derechos y se comporta muy parecido a la persona a la cual está representando. En el mundo de hardware un servidor proxy se coloca entre el usuario y el servidor al que realmente se está accediendo. El servidor proxy transparentemente se comporta como el servidor real pero sin la funcionalidad adicional que tiene el servidor base que puede ser el rastreo, el monitoreo o el filtrado. En el mundo de software el patrón de diseño de proxy es una clase que se comporta como otra clase. Por ejemplo si estuviéramos construyendo un sistema para hacer el rastreo de actividades podríamos decidir utilizar un proxy para aplicar transparentemente las autorizaciones a un objeto denominado Task.

public class Task{ public static Task FindById(int id) { return TaskRepository.Create().FindById(id); }

public virtual void Delete() { TaskRepository.Create().Delete(this); }}public class TaskProxy : Task{ public override void Delete() { if (User.Current.CanDeleteTask()) { base.Delete(); } else { throw new PermissionException(...); } }}

Gracias al polimorfismo FindById puede regresar una Task o una TaskProxy. El cliente no tiene que saber cual fue regresado, de hecho tampoco necesita conocer que el objeto TaskProxy existe. Solo se programa a través de la API pública.

Debido a que el proxy es solamente una subclase que implementa comportamiento adicional quizás te podías preguntar si un Perro es un proxy de Mascota. Los proxies tienden a implementar funciones más técnicas como el monitoreo, el caching, la autorización etc. de una manera transparente. En otras

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

79

Page 80: Fundamentos de Programación Traducido

Capítulo 9 – De regreso a las bases: Proxy esto y Proxy aquello

palabras no se debería declarar una variable como TaskProxy, si no que más bien se declararía una variable del tipo Perro. Como consecuencia un proxy no añadiría más miembros toda vez sé que tú no estás programando en contra de su API, de la misma manera que si una clase Perro implementara un método Ladrar

InterceptionLa razón por la cual estamos explorando los temas de la herencia de una manera más técnica es porque las dos herramientas que se han tratado en el contexto del libro RhinoMocks e NHibernate hacen uso extensivo de los proxies aunque no sea notorio a primera vista.

RhinoMocks usa un proxy es para soportar su funcionalidad base mientras que NHibernate utiliza proxies para explotar sus capacidades de carga perezosa (lazy loading). En este capítulo veremos cómo funciona NHibernate toda vez que es más fácil entender qué es lo que sucede tras bambalinas con esta tecnología sin embargo el mismo nivel de abstracción se aplica con RhinoMocks

(Nota sobre NHibernate: Es un sistema de mapeo entidad relación transparente o sin consecuencias debido a que no es necesario modificar las clases del dominio para que funcione. Sin embargo para poder habilitar el mecanismo de carga perezosa todos los miembros deben de ser virtuales. Esto todavía se puede considerar sin consecuencias o transparente debido a que el programador no añade elementos específicos de NHibernate a tus clases, como heredar de una clase base o agregar atributos por todas partes)

Cuando se utiliza NHibernate existen dos maneras diferentes para proveer el mecanismo de carga perezosa. El primero y el más obvio es cuando utilizamos colecciones hijas por ejemplo: Es probable que en algunas ocasiones no se quiera cargar Model's Upgrades hasta que son necesitadas realmente. El archivo de mapeo se vería como el siguiente:

<class name="Model" table="Models"> <id name="Id" column="Id" type="int"> <generator class="native" /> </id> ... <bag name="Upgrades" table="Upgrades" lazy="true" > <key column="ModelId" /> <one-to-many class="Upgrade" /> </bag> </class>

Al asignar el atributo lazy a true en nuestro elemento bag, le estamos especificando a NHibrernate a que cargue perezosamente la colección Upgrades. NHibernate puede hacerlo sencillamente debido a que regresa sus propios tipos de Colección (Todos de los cuales implementan interfaces por defecto como IList, por lo que para el programador resulta transparente)

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

80

Page 81: Fundamentos de Programación Traducido

Capítulo 9 – De regreso a las bases: Proxy esto y Proxy aquello

El segundo, y por mucho, más interesante, se utiliza la carga perezosa para los objetos individuales de dominio. La idea en concreto es que algunas veces será necesario que los objetos enteros sean cargados diferidamente. ¿Por qué? Bueno, supongamos entonces que se ha realizado una venta (Sale). Las ventas están asociadas con una persona de ventas (SalesPerson) y un modelo de carro.

Sale sale = new Sale();sale.SalesPerson = session.Get<SalesPerson>(1);sale.Model = session.Get<Model>(2);sale.Price = 25000;session.Save(sale);

Desafortunadamente tuvimos que hacer una conexión a la base de datos dos veces para los objetos SalesPerson y Model aunque realmente no son usados. Realmente lo único que necesitamos es su ID (toda vez que es lo que se inserta en la base de datos), que ya tenemos.

Al momento de crear un proxy NHibernate nos permite cargar a un objeto de una manera perezosa solamente para este tipo de circunstancias. Lo primero que se debe hacer es cambiar nuestro mapeo y permitir la carga perezosa para los modelos Models y SalesPeoples

<class name="Model" table="Models" lazy="true" proxy="Model">...</class>

<class name="SalesPerson" table="SalesPeople" lazy="true" proxy="SalesPerson ">...</class>

El atributo proxy le dice a NHibernate qué tipo debe de ser utilizado con el proxy. Este tipo puede ser la clase misma a la cual se están mapeando, o una interface implementada por la clase

Dado que estamos usando la misma clase que nuestra interface de proxy, necesitamos asegurarnos de que todos los miembros están marcados como virtuales. Si alguno no lo está NHibernate una excepción con una lista de métodos no virtuales. Una vez dicho esto, estamos listos:

Sale sale = new Sale();sale.SalesPerson = session.Load <SalesPerson>(1);sale.Model = session.Load<Model>(2);sale.Price = 25000;session.Save(sale);

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

81

Page 82: Fundamentos de Programación Traducido

Capítulo 9 – De regreso a las bases: Proxy esto y Proxy aquello

En el código anterior podemos ver que estamos utilizando Load en lugar de Get. La diferencia entre ambos es que al utilizar una clase que soportar carga perezosa el método Load obtendrá el proxy mientras que el método Get obtendrá el objeto real. con este código ya no estamos yendo hacia la base de datos para cargas los IDs. En su lugar el llamar Session.Load<Model>(2)regresa un proxy dinámicamente generado por NHibernate.

El proxy tendrá un ID de 2 debido a que ya se le proporcionó el valor y todas las demás propiedades están sin y inicializar

Cualquier llamada a otro miembro de nuestro proxy como sale.Model.Name será transparentemente interceptado y el objeto será cargado en tiempo de ejecución desde la base de datos

Una nota adicional es que la capacidad de carga perezosa NHibernate puede ser difícil de monitorear al momento de hacer una depuración dentro de Visual Studio, esto es porque su funcionalidad de realmente inspecciona los valores de los objetos al ser inspeccionados. La mejor manera de examinar qué es lo que está sucediendo es añadir un par de puntos de interrupción al momento de ejecutar el código fuente y revisar qué es lo que está sucediendo dentro de la base de datos a través de la herramienta SQL Profiler o el log de NHibernate.

Esperamos también que te puedas imaginar cómo funcionan los proxies utilizados por RhinoMocks para grabar datos y para generar interacciones al momento de que se crea un objeto realmente está creando un Proxy. Un objeto real es del proxy intercepta todas las llamadas y dependiendo en qué estado se encuentre as de su propia funcionalidad realiza su tarea. Desde luego para que esto funcione se debe de generar una interface prototipo y miembros virtuales de las clases

En éste capítuloEn el capítulo 6 cubrimos brevemente las capacidades de carga perezosa NHibernate. En este capítulo expandimos la discusión para llegar a más a las implementaciones a reales. El uso de proxies es tan común que no solamente te encontrarás con ellos sino que es muy probable que tengas una buena razón para implementarlos tú mismo. Aun me encuentro impresionado con la funcionalidad que se proveen en RhinoMock e NHibernate gracias al patrón de diseño del proxy.

Desde luego que todo se basa en permitirles cambiar o inyectar su comportamiento en las clases de tu dominio.

Esperamos que este capítulo te haga ahondar en la discusión de que métodos deberían de ser virtuales y cuáles no.

Se recomienda fuertemente que visites las ligas que se han provisto en la primera página de este capítulo para que puedas entender los pros y los contras de los métodos virtuales y finales.

.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

82

Page 83: Fundamentos de Programación Traducido

Resumiendo

Resumiendo

TENDEMOS A LIGAR NUESTRA CONFIANZA EN NOSOTROS MISMOS FUERTEMENTE CON LA CALIDAD DEL PRODUCTO QUE PRODUCIMOS – NO LA CANTIDAD DEL PRODUCTO, PERO LA CALIDAD - TOM DEMARCO & TIMOTHY LISTER (PEOPLEWARE)

ara muchos, programar es un trabajo de retos y disfrutable que paga los recibos. Sin embargo, dado que leído a lo largo de todo esto, hay una posibilidad de que, como yo, programar es algo más importante para usted. Es una labor artesanal, y lo que crea significa más para usted que lo

que cualquiera no programador pueda entender. Tomo con mucho orgullo y placer que construir algo que sobresale a mi nivel de calidad y aprender de las partes que necesitan ser mejoradas.

PNo es fácil construir software de calidad. Nuevas características aquí o un malentendido allá, y nuestro trabajo duro empieza, aunque levemente, a mostrar debilidades. Es por eso que es importante tener un entendimiento sólido de los fundamentos de buen diseño de software. Logramos cubrir muchos de los detalles reales de implementación, pero a un nivel alto, aquí están mis principios esenciales de una buena ingeniería de software:

La solución más flexible es la más simple. He trabajado en proyectos cuya flexibilidad está construida desde el principio en el sistema. Siempre ha sido un desastre. Para muestra de este entendimiento está en YAGNI, DRY, KISS y claridad.

El acoplamiento no es evitable, pero debe ser minimizado. Lo más dependiente que una clase sea de otra clase, o una capa de otra capa, será más difícil que su código cambie. Creo fuertemente que la ruta para dominar bajo acoplamiento es por las pruebas unitarias. Código mal acoplado será imposible de probar y será llanamente obvio.

La noción de que los desarrolladores no deberían probar ha sido la anti bala de plata de nuestro tiempo. Usted es responsable (y de ser posible el que rinda cuentas) del código que escribe, y pensar que un método o clase hace lo que se supone no es suficiente. La búsqueda de la perfección debe ser suficiente razón para escribir pruebas, pero, hay mejores razones para hacerlo. Probar ayuda a identificar malos acoplamientos. Probar ayuda a encontrar cosas raras en su API. Probar ayuda a documentar comportamiento y expectativas. Probar permite hacer cambios pequeños pero radicales con mayor confianza y mucho menos riesgo.

Es posible construir software exitoso sin ser Ágil – pero no será con tanta certeza y mucho menos divertido. Mis gozos serían de poco tiempo sin una colaboración del cliente constante. Renunciaría a esta carrera sin desarrollos iterativos. Viviría en un sueño si requiriera especificaciones firmadas antes de empezar a desarrollar.

Cuestione el status quo y siempre esté al acecho de alternativas. Tome buena parte de su tiempo aprendiendo. Aprenda diferentes lenguajes y diferentes marcos de trabajo. Aprendiendo Ruby y Rails me ha hecho mucho mejor programador. Puedo identificar el principio de mi camino en ser mejor programador a hace unos años atrás cuando estaba muy envuelto en código fuente para un proyecto open source, tratando de hallar sentido en él.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

83

Page 84: Fundamentos de Programación Traducido

Resumiendo

Mi último consejo es no se dé por vencido. Soy muy lento para aprender, y mientras más aprendo, más me doy cuenta de lo poco que sé. Y todavía no entiendo la mitad de lo que mis pares bloggean en CodeBetter.com (en serio). Si está dispuesto a tomar su tiempo y tratarlo, verá el progreso. Escoja un simple proyecto para invertirle un fin de semana construyéndolo usando las nuevas herramientas y principios (sólo elija uno o dos a la vez). Más importantemente, si no está divirtiéndose, no lo haga. Y si usted tiene la oportunidad de aprender de un mentor o de un proyecto, tómela – aunque usted aprenda más de equivocaciones que de éxitos.

Sinceramente espero que encuentre algo valioso aquí. Si lo desea, puede agradecerme haciendo algo bueno para alguien especial, algún gesto amable a un extraño, o algo significativo por el medio ambiente.

Recuerde descargar gratuitamente la Canvas Learning Application para una mirada más a detalle de las ideas y herramientas representadas en este libro.

Fundamentos de Programación Copyright © Karl Seguin www.codebetter.com

84