Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

Fundamentos de Programación Traducido

Descargar como docx, pdf o txt
Descargar como docx, pdf o txt
Está en la página 1de 84

Fundamentos de

Programación
Construyendo Mejor Software

Por Karl Seguin

WWW.CODEBETTER.COM
Fundamentos de Programación 4

Página intencionalmente dejada en blanco

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


Fundamentos de Programación 4

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


Fundamentos de Programación 4

Tabla of Contenido
Acerca del autor..........................................................................................................................................6
ALT.NET.......................................................................................................................................................7
Objetivos.................................................................................................................................................8
Simplicidad..............................................................................................................................................8
YAGNI (You aren’t going to Need It – No vas a necesitarlo)....................................................................8
Ultimo momento de responsabilidad......................................................................................................9
DRY..........................................................................................................................................................9
Explicitud y cohesión...............................................................................................................................9
Acoplamiento..........................................................................................................................................9
Pruebas unitarias e integración continúa..............................................................................................10
En este capitulo.....................................................................................................................................10
.................................................................................................................................................................. 10
Diseño dirigido por dominios DDD............................................................................................................11
Diseño dirigido por Dominios/Datos......................................................................................................11
Usuarios, Clientes e Inversionistas.........................................................................................................12
El objeto de dominio.............................................................................................................................13
Interfaz de Usuario (IU).........................................................................................................................16
Trucos y pistas.......................................................................................................................................17
Patrones de fábrica............................................................................................................................17
Modificadores de acceso...................................................................................................................18
Interfaces...........................................................................................................................................18
Ocultar información y Encapsular......................................................................................................19
En este capítulo.....................................................................................................................................19
.................................................................................................................................................................. 20
Persistencia...............................................................................................................................................21
La brecha...............................................................................................................................................21
DataMapper..........................................................................................................................................22
Tenemos un problema.......................................................................................................................24
Limitaciones.......................................................................................................................................26
En este capítulo.....................................................................................................................................27
.................................................................................................................................................................. 27
Inyección de dependencias.......................................................................................................................28
No evites el acoplamiento como si fuera una plaga..............................................................................30
Inyección de dependencias....................................................................................................................31
Constructor de inyección...................................................................................................................31
Marcos de referencia.........................................................................................................................33
Una última mejora.............................................................................................................................34
En este capítulo.....................................................................................................................................35
.................................................................................................................................................................. 35
Pruebas de Unidad....................................................................................................................................36

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


Fundamentos de Programación 4

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


Las Herramientas...................................................................................................................................38
nUnit..................................................................................................................................................38
¿Qué es una prueba de unidad?............................................................................................................40
Mocking.................................................................................................................................................40
Más de nUnit y RhinoMocks..................................................................................................................44
Pruebas de la interfaz de usuario y la base de datos.............................................................................44
En este capítulo.....................................................................................................................................45
.................................................................................................................................................................. 45
Object Relational Mappers........................................................................................................................46
El debate del infame SQL en línea vs. Los procedimientos almacenados..............................................46
NHibernate............................................................................................................................................49
Configuración........................................................................................................................................50
Relaciones..............................................................................................................................................52
Consultas...............................................................................................................................................54
Carga diferida........................................................................................................................................54
Descarga................................................................................................................................................55
En este capítulo.....................................................................................................................................55
De regreso a las bases: Memoria.............................................................................................................56
Asignación de Memoria.........................................................................................................................56
El Stack..............................................................................................................................................56
El Heap..............................................................................................................................................57
Apuntadores......................................................................................................................................58
Modelo de Memoria en la Práctica........................................................................................................60
Boxing................................................................................................................................................60
ByRef.................................................................................................................................................61
Fugas de Memoria Administradas.....................................................................................................64
Fragmentación...................................................................................................................................64
Fijamiento..........................................................................................................................................65
Asignar a null.....................................................................................................................................66
Finalización Determinística....................................................................................................................66
En este capítulo.....................................................................................................................................67
.................................................................................................................................................................. 67
De regreso a las bases: Excepciones.........................................................................................................68
Manejando Excepciones........................................................................................................................68
Registros................................................................................................................................................69
Limpieza.................................................................................................................................................70
Lanzar Excepciones................................................................................................................................71
Mecanismo para generarlas..................................................................................................................71
Cuándo Lanzar Excepciones...................................................................................................................72
Creando Excepciones Personalizadas....................................................................................................73
En este Capítulo.....................................................................................................................................76

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


Fundamentos de Programación 4

De regreso a las bases: Proxy Esto y Proxy Aquello.................................................................................77


Patrón del Dominio del Proxy................................................................................................................78
Interception...........................................................................................................................................79
En éste capítulo.....................................................................................................................................81
.................................................................................................................................................................. 81
Resumiendo...............................................................................................................................................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


Capítulo 1 – ALT.NET 10

ALT.NET

SI HAY UNA INCONFORMIDAD CON ELSTATUS QUO, BIEN. SI HAY ALBOROTO,


1
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

H 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.

Mi 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


Capítulo 1 – ALT.NET 10

Objetivos
Aunque 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.

Simplicidad
La 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


Capítulo 1 – ALT.NET 10

“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 responsabilidad


La 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.

DRY
La 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ón
Suena 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


Capítulo 1 – ALT.NET 10

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ítulo
Aunque 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


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

Diseño dirigido por dominios DDD


2
¿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

E 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.

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
que el dirigido a datos – esto simplemente hace al dirigido a dominios mejor que al dirigido a datos en

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


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

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 En el pasado, frecuentemente


Algo que tomo muy en serio del desarrollo ágil es la hubiera querido emborracharme
interacción cercana que el equipo de trabajo mantiene con mis clientes. Ellos eran
con el cliente y los usuarios. De hecho, en tanto me es molestos, no sabía lo que querían y
posible, yo no veo un equipo de desarrollo y clientes, siempre realizaban elecciones
sino una sola entidad: el equipo. Ya sea que tenga la erróneas.
fortuna o no de estar en tal situación (algunas veces los
abogados se interponen, algunas veces los clientes no Sin embargo me puse a pensar, y
están disponibles para acuerdos, etc.) Es importante me di cuenta de que yo no era tan
entender lo que le corresponde a cada quien. El cliente inteligente como lo creía. El cliente
es quien paga y como tal, debe realizar las decisiones sabía mucho más acerca de su
finales acerca de características y prioridades. Los negocio que yo. No sólo eso, el
usuarios, ciertamente, utilizan el sistema. Los clientes, comprometía su dinero y mi trabajo
en ocasiones, son usuarios, pero solo en extrañas
era permitirle obtener el máximo
ocasiones son los únicos usuarios. Un sitio Web, por
provecho de él.
ejemplo, podría tener usuarios anónimos, usuarios
registrados, moderadores y administradores. Por Así que cambiamos la relación por
último, los inversionistas es cualquier persona que una más activa, colaborativa y
invierte el sistema. El mismo sitio web podría tener un positiva y no sólo se obtuvieron
sitio padre o hermano, patrocinadores o expertos en el
resultados en gran medida mejores,
dominio.
sino que la programación se
Los clientes tienen un trabajo muy duro. Ellos deben convirtió divertida nuevamente.
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


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

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 Applied 1” 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


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

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


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

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


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

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


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

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


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

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.

Interfaces
Las 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


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

{
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ítulo
La 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


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

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


Capítulo 3 - Persistencia 27

Persistencia
3
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

E 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.

Escribiremos 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 brecha
Como 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)

DataMapper
Para 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


Capítulo 3 - Persistencia 27

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


Capítulo 3 - Persistencia 27

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


Capítulo 3 - Persistencia 27

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 problema
A 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


Capítulo 3 - Persistencia 27

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


Capítulo 3 - Persistencia 27

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.

Limitaciones
Aunque 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


Capítulo 3 - Persistencia 27

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ítulo
Al 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


Capítulo 4 – Inyección de dependencias 35

Inyección de dependencias.

YO DIRÍA QUE LA INGENIERÍA DE SOFTWARE MODERNA ES EL REFINAMIENTO EN 4


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

E 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)

Yo 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


Capítulo 4 – Inyección de dependencias 35

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 unitarias


Hablando 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


Capítulo 4 – Inyección de dependencias 35

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, y
3. 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


Como se hizo mención de Juval al
plaga
principio de este capítulo, yo estoy
En caso de que hayas olvidado el Capítulo 1, acoplamiento
tentado a dar mi voto a favor del es simplemente lo que llamamos cuando una clase
desacoplamiento como la requiere otra clase para poder funcionar. Es
necesidad más grande para las esencialmente una dependencia. Todo, incluso las más
aplicaciones modernas. pequeñas líneas de código tienen dependencias con otras
clases. Asi es que si escribes string site =
De hecho, en adición a los
“MejorCódigo”, estas acoplando la clase
beneficios citados de las pruebas
System.String – Si esta cambia es muy probable que
unitarias, me he dado cuenta que la
tu código deje de funcionar. Por supuesto lo primero que
ventaja más inmediata esta en tienes que saber es que en la gran mayoría de los casos,
ayudar a los desarrolladores a incluso un ejemplo tan tonto como el de la cadena de
aprender los patrones del buen y texto el acoplamiento no es algo malo. No queremos crear
mal acoplamiento. 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


Capítulo 4 – Inyección de dependencias 35

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ón
La 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


Capítulo 4 – Inyección de dependencias 35

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


Capítulo 4 – Inyección de dependencias 35

Marcos de referencia
Hacer 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


Capítulo 4 – Inyección de dependencias 35

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 mejora


Con 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


Capítulo 4 – Inyección de dependencias 35

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ítulo
Reducir 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


Capítulo 5 – Pruebas de Unidad 45

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

A 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.

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


Capítulo 5 – Pruebas de Unidad 45

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


Capítulo 5 – Pruebas de Unidad 45

Las Herramientas
Con 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.

nUnit
Lo 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


Capítulo 5 – Pruebas de Unidad 45

[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


Capítulo 5 – Pruebas de Unidad 45

¿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.

Mocking
Para 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


Capítulo 5 – Pruebas de Unidad 45

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


Capítulo 5 – Pruebas de Unidad 45

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


Capítulo 5 – Pruebas de Unidad 45

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


Capítulo 5 – Pruebas de Unidad 45

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 RhinoMocks


Hasta 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 datos


Añ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


Capítulo 5 – Pruebas de Unidad 45

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ítulo
Hacer 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


Capítulo 6 - Object Relational Mappers 56

Object Relational Mappers


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

E 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.

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 almacenados


A 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


Capítulo 6 - Object Relational Mappers 56

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 Como he dicho antes, creo que es
datos con los permisos apropiados. generalmente mejor errar en el
Los procedimientos almacenados proveen abstracción al lado de la simplicidad, siempre que
esquema subyacente sea posible. Escribir un montón de
tontos procedimientos
Sin importar si estas usando SQL en línea o almacenados para realizar cada
procedimientos almacenados, la pequeña porción de
operación de base de datos que
abstracción que puedes poner en una sentencia de
creas que vas a necesitar,
SELECT es la misma. Si algún cambio substancial es
definitivamente no es a lo que yo
realizado, tus procedimientos almacenados dejarán de
funcionar y lo más probable es que tendrás que cambiar considero simple. Ciertamente no
el código que los invoca para resolver este problema. intento descartar el uso de los
Esencialmente, es el mismo código, solamente que procedimientos almacenados,
reside en un lugar diferente, por lo tanto no puede ¿pero comenzar con
proveer un grado más alto de abstracción. Las procedimientos? Parece un caso
definiciones de conversión por otro lado, generalmente bastante extremo de optimización
proveen una mejor abstracción ya que son configurables prematuro para mí. -Jeff Atwood,
e implementan su propio lenguaje de consulta. codinghorror.com
Si hago un cambio, no tengo que recompilar el código

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

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


Capítulo 6 - Object Relational Mappers 56

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,

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

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


Capítulo 6 - Object Relational Mappers 56

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.

NHibernate
De 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
Recuerda que nuestro objetivo es hace posible que tu capa de dominio pueda ser sometida a
ampliar nuestra base de pruebas unitarias – si estas usando un mecanismo diferente de
conocimientos viendo diferentes persistencia, digamos datasets con tipos definidos, el nivel de
formas de construir sistemas con el acoplamiento tan estrecho entre el dominio y los datos hace muy
difícil sino es que imposible el efectuar apropiadamente las
fin de proveer un mayor valor a
pruebas unitarias.
nuestros clientes. Mientras que
podríamos hacerlo hablando A muy groso modo, configuras Nhibernate diciéndole como tu
específicamente de NHibernate, el base de datos (tablas y columnas) hacen referencia con tus
objetos de dominio, usa la API de Nhibernate y el lenguaje de
objetivo es introducir el concepto
consulta (HQL – Hibernate Query Language) para comunicarte
de Definiciones de conversión con tu base de datos, y deja que el haga el trabajo de bajo nivel
Entidad relacional / Objeto, y tratar con ADO.NET y SQL. Esto no solo provee separación entre la
de corregir la fe ciega que tienen estructura de tu tabla y los objetos de dominio, sino que también
puesta los desarrolladores de .NET desacopla tu código de la implementación para una base de
datos específica.
en los procedimientos almacenados
y ADO.NET 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
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.

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


Capítulo 6 - Object Relational Mappers 56

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
solicites, así como salvarlo. La convención general es tener un archivo de definición de conversión por

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


Capítulo 6 - Object Relational Mappers 56

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 = ?");
Model model = query.SetString(0, "X149").UniqueResult<Model>();

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


Capítulo 6 - Object Relational Mappers 56

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.

Relaciones
En 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:

...
<many-to-one name="SalesPerson"

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


Capítulo 6 - Object Relational Mappers 56

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 Con la liberación de .NET 3.5
tiene un vendedor (SalesPerson), es un poco más
finalmente se ha agregado una
complicada – básicamente porque la terminología de
NHibernate no pertenece a la jerga estándar usada en .NET. colección HashSet al marco de
Para configurar una colección usamos un elemento de tipo set, trabajo. Idealmente, versiones
list, map, bag o array. Tu primera inclinación puede ser futuras agregaran otro tipo de
usar una lista, pero NHibernate requiere que tengas una conjuntos con un OrderedSet. Los
columna que especifique el índice. En otras palabras, el equipo
conjuntos son colecciones muy
de NHibernate ve una lista como una colección donde el índice
es importante, y por lo tanto debe ser especificado. Lo que la útiles y eficientes, ¡así que
mayoría de los desarrolladores de .NET entienden como un considera agregarlos a tu arsenal
list, NHibernate lo llama una bolsa (Bag). Siendo algo de herramientas! Puedes aprender
confuso tal vez, tanto si utilizas un elemento list o bag, tu más leyendo el artículo en el que
tipo de dominio debe ser un IList (o su equivalente genérico
Jason Smith describe los conjuntos.
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;}
}

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

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


Capítulo 6 - Object Relational Mappers 56

<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.

Consultas
NHibernate 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 apellido


IQuery 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 variable
SalesPerson 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


Capítulo 6 - Object Relational Mappers 56

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 diferida
Cuando 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


Capítulo 6 - Object Relational Mappers 56

Descarga
Puedes 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ítulo
Solo 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


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

De regreso a las bases: Memoria


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

P 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
ThreadAbortException. Tan importante como es para desarrolladores adopter varios patrones y
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.

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 Memoria
En .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 Stack
Aunque 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


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

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 Heap
La 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


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

Apuntadores
Para 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


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

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


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

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áctica


Ahora 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.

Boxing
El 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.

ByRef
Sin 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


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

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


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

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


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

{
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


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

Fugas de Memoria Administradas


Ya 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ón
Otra 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


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

Fijamiento
El 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


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

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 null
Así 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ística
A 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ítulo
Stacks, 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


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

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


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

De regreso a las bases: Excepciones


8
FALLA RÁPIDO - JIM SHORE

L 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.

Manejando 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


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

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 Palabras de advertencia basadas en
gustaría controlar de la misma manera, pero esa es otra una mala experiencia personal:
historia). algunos tipos de excepciones
tienden a multiplicarse. Si opta por
Registros
A pesar de que no se controlen la mayoría de la
enviar correos siempre que ocurre
excepciones, usted debería registrar todas y cada una una excepción podrá fácilmente
de ellas. Idealmente, podría centralizar su registro – un saturar su servidor de correo. Una
HttpModule de OnError es su mejor opción para una solución más inteligente
aplicación ASP.NET o servicio web. He visto a menudo a implementaría algún tipo de buffer
los desarrolladores capturar excepciones donde estas o agregación.
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

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


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

proceso para enviar un resumen diario. Muchos desarrolladores aprovechan frameworks para generar
registros como log4net o Microsoft´s Logging Application Block.

Limpieza
En 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();
}

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


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

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.

Lanzar Excepciones
No 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

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


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

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
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 Excepciones


Es 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

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


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

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
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).

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


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

Creando Excepciones Personalizadas


Uno 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
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

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


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

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


{
}

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)

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


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

{
if (info != null)
{
_IdMejora = info.GetInt32("IdMejora");
}
}
public override void GetObjectData(SerializationInfo i, StreamingContext c)
{
if (i != null)
{
i.AddValue("IdMejora", _IdMejora);
}
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


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

De regreso a las bases: Proxy Esto y Proxy Aquello


9
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

P 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

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

class KaraokeSong < Song


def to_s
return super + " - " @lyrics
end
end

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


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

Patrón del Dominio del Proxy


Un 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


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

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

Interception
La 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


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

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


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

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ítulo
En 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


Resumiendo 84

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)

P 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.

No 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

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


Resumiendo 84

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.

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

También podría gustarte