lunes, 30 de junio de 2008

Las 9 reglas para un mejor diseño de software

Hace algunos días, por recomendación de un amigo (Karel para los que lo conocen), lei algunos artículos de un libro llamado The ThoughtWorks Anthology (tengo el libro en pdf para el que le interese), y me llamo la atención un articulo llamado “Object Calisthenics”, acerca de un conjunto de reglas, por cierto bien restrictivas, para lograr según el autor Jeff Bay, (un ThoughtWorker, es decir trabaja en ThoughtWorks) un mejor diseño del software. Aquí abajo enunciare en español, las 9 reglas, que muchas de ellas a primera vista les parecerán un disparate, y difícil de seguir al pie de la letra. El autor para probar que puede usarse en cualquier proyecto de software, acaba de terminar uno con más de 100 000 líneas de código, usando las 9 reglas.

Pues sin mas, aqui van:

1- Usa solo un nivel de indentación por método (Use only one level of indentation per method).

2- No uses el “else” dentro de una condicional (Don’t use the else keyword).

3- Usa Wrappers para los tipos primitivos y las cadenas (Wrap all primitives and strings)

4- Solo usa un punto por línea (Use only one dot per line).

5- No abrevies (Don’t abbreviate).

6- Mantén las clases pequeñas (Keep all entities small)

7- No utilices una clase que tenga mas de 2 variables de instancia.(Don’t use any classes with more than two instance variables)

8- Una clase que contenga una colección no debe contener otras variables de instancia (Use first-class collections).

9- No use setters y getters (Don’t use any getters/setters/properties).

La explicacion de cada una de las reglas, y las razones del autor estan en el ensayo que forma parte del libro The ThoughtWorks Anthology ,y los interesados que me escriban y pondre el libro en alguna parte. No se han hecho esperar las reacciones (el libro salio Marzo 2008), pongan Object Calisthenics en google y veran pros y contra.

Se embullan a hacer la prueba? Ya yo lo estoy intentando y en un proximo post, les dire.

23 comentarios:

Karel dijo...

The idea behind these 9 rules is to force us programmers to think in good Object Oriented practices.

This is just an exercise that he suggests we run for a 1000 line project and then reflex on how different our program is.

Though they can be very restrictive and the use of certain frameworks in Java like Hibernate, Struts 2, and other JavaBean based frameworks will attempt against following these 9 rules, it's a good exercise and practice to force yourself to follow them, and then, if impossible, relax them, once you've come to understand basic OO principles and practices enforced by these rules.

You can find more information related to the ideas suggested by this essay at:
1- GRASP
2- Bad Smells in Code extract(From Martin's Fowler Refactoring book).
3- Smells to Refactoring

This is basic material that even seasoned developers should always review to stay in tune with good Object Oriented design principles and practices.

Anónimo dijo...

Erne,
No estoy de acuerdo con algunos de los puntos. Otros sí los considero muy válidos. Sin embargo, me parece que esos consejos no son para un mejor diseño de software en sí, sino más bien una metodología para la codificación de software. Por otro lado, la experiencia que he tenido en la vida real me demuestra que, en ambientes de desarrollo en grupos no se debe ser tan restrictivos a la hora de imponer metodologías.

Me quedé con la curiosidad. Explícame ¿por qué no se deberían tener en clases que son colecciones, otras variables miembros? - Me parece una limitación auto impuesta bastante grande.

Saludos.

Anónimo dijo...

Erne,
No estoy de acuerdo con algunos de los puntos. Otros sí los considero muy válidos. Sin embargo, me parece que esos consejos no son para un mejor diseño de software en sí, sino más bien una metodología para la codificación de software. Por otro lado, la experiencia que he tenido en la vida real me demuestra que, en ambientes de desarrollo en grupos no se debe ser tan restrictivos a la hora de imponer metodologías.

Me quedé con la curiosidad. Explícame ¿por qué no se deberían tener en clases que son colecciones, otras variables miembros? - Me parece una limitación auto impuesta bastante grande.

Saludos.

Anónimo dijo...

Imagina este escenario...

[Serializable]
public class PlayerTeam:
List< Player >
{
private String teamName;

public PlayerTeam( string teamName, params Player[] players )
{
this.TeamName = teamName;
AddRange( players );
}

[XmlElement]
public String TeamName
{
get
{
return teamName;
}
set
{
if ( ( value == null ) || ( value.Length == 0 ) )
throw new ArgumentException("Invalid value");

teamName = value;
}
}
}


Erne, me quedé con la duda de por qué, según estos principios, no se debería tener variables miembros dentro de una colección. No es por ripostar la idea; más bien me asalta la curiosidad. Poderosas razones tendrán para plantear algo así.

Ernesto dijo...

Humbe, me alegro que hallas escrito en los comentarios, porque eres un programador con varias millas ya (muchos annos programando, y quien mejor que tu :) ), y por supuesto que estas cosas chocan. Esto es solo un enfoque de la experiencia del autor, y digamos que son algunas recetas fruto de su trabajo en una empresa que afronta grandes proyectos como ThroughtWorks.Pero bueno dejemos al autor que lo explique mejor eh ? y bueno por supuesto yo tambien tengo mis inquietudes, en seguir este modelo.
Te mando el URL donde puedes bajar el libro, solo pondre online 2 dias, asi que trata de hacerlo rapido.

Jazz dijo...

yo tampoco entiendo el sentido de no tener variables de instancia en una clase que contenga una coleccion. aunque si entiendo lo de un solo punto por linea. me parece que la idea es tener que declarar variables intermedias que van a terminar aclarando el codigo un poco mas.

por ejemplo:

playerTeam.player[index].country.flag;

es menos claro que

selectedPlayer = playerTeam.player[index];
playerCountry = selectedPlayer.country;
playerFlag = playerCountry.flag;

de cualquier manera concuerdo con humberto a que esto no tiene mucho que ver con la teoria de la OO sino mas bien con la codificacion.

vaya es que hasta yo escribo un libro con mas reglas. a ver a ver.

1 - no uses los meñiques para teclear.
2 - no programes con una luz amarilla en la habitacion.
3 - razcate siempre la cabeza con la mano derecha si la linea en la que estas es par.
4 - no tocar el switch del display directamente, utilizar un palito o algo para apagarlo/encenderlo.

etc.. etc.. etc.. jejejeje

saludos a todos,

piloto

Anónimo dijo...

El Inclusivo,

Estoy 100% de acuerdo contigo. Mira esto, usando tu mismo ejemplo:

playerTeam .
player[index] .
country .
flag;

Ahí hay un punto en cada línea. No estoy "violando" esa regla. El autor ni siquiera establece bien estas reglas, y el código sigue siendo poco claro (no difiere del original excepto en que lo rompí en líneas).

Tampoco entiendo el por qué no se deben usar elses dentro de una condicional. ¿?

Este mundo de la programación en que vivimos está lleno de mentes iluminadas, aprendices de dictadores que intentan sentar cátedra imponiendo tendencias en el desarrollo del software.

Yo digo que, siempre, todas las buenas ideas y que tengan sentido se pueden aprovechar (Clases pequeñas, No abreviaciones, etc).

Pero a estos genios no se les debe seguir nunca hasta las últimas consecuencias, porque al final te embarcan, o viene otro genio y se impone con otras tendencias.

Por poner un ejemplo, hace siete u ocho años predominaba la notación húngara como práctica generalmente aceptada como "buena" en el mundo de Windows, y hoy en día te dicen Fó si la aplicas.

Saludos para ti.

Jazz dijo...

Le he estado dando vueltas a este tema del "else" y la unica explicacion posible que se me ocurre es nuevamente la legibilidad.

Cuando leemos un texto, es mas facil entender un lenguaje sencillo y directo que un lenguaje enredado y condicional. Los escritores tienen reglas para esto como por ejemplo evitar el uso de la palabra "que".

Especificamente cuando leemos un "else" en un fragmento de codigo, nos vemos obligados a regresar mentalmente a la condicional principal para entender que el codigo siguiente se ejecuta cuando esta no se cumple.

Es un proceso mental mas sencillo si leemos explicitamente las condicionales. Tomemos de ejemplo la siguiente condicional:

if (X > Y)
{
X = 0;
}
/*
aqui tenemos que mentalmente regresar a la condicional (X > Y) y convertirla en (X <= Y)
*/
else
{
X = 1;
}

del otro modo el codigo quedaria:

if (X > Y)
{
X = 0;
}

if (X <= Y)
{
X = 1;
}

Este ultimo codigo es mas explicito, lo podemos leer mas rapido porque no invertimos tiempo ni esfuerzo en entenderlo.

No cabe duda de que estas tecnicas son mas y mas efectivas mientras mas grande sea el codigo que estamos consultando.

Saludos a todos.

Piloto.

Ernesto dijo...

Piloto y Humbe

No creo que estas reglas hallan sido lanzadas al azar por un loco o genio o como sea, creo que son fruto de la experiencia del autor.Ya se que humbe tiene el libro, espero que halla leido las reflexiones que hace el mismo sobre las reglas (por supuesto no es un dictador que quiere que apliquen sus reglas), es solo consideraciones que se pueden analizar a la hora de programar, y muy bien por Uds que han debatido sobre esto, y dado su punto de vista, Piloto tienes el libro ? Dile a humbe que te lo mande o me dices para darte la direccion de donde descargarlo y puedas leer el articulo por si no lo has hecho.

Ernesto dijo...

Sobre la regla de un solo punto por línea, luego de revisar el comentario del autor, y el hecho de que afirman (Piloto y Humbe) de que son reglas mas bien para codear y no para un buen diseño de OO. Cojamos el ejemplo de piloto (ojo no quiere decir que no puedas tener 2 puntos en una línea :), creo que no hay que exagerar, además el mismo autor lo comenta)

playerTeam.player[index].country.flag;

lo primero que veo aquí es que se viola el principio de encapsulacion (no soy un purista OO), solo que aqui damos el detalle de que representamos el conjunto de jugadores como un arreglo, que pasa si cambiamos en un futuro (replace por todo el código?), luego quizás seria mejor

playerTeam.getPlayer(index) <- todavía el index da una idea de estamos usando una coleccion donde los jugadores tienen una posición dentro de la estructura de datos que lo soporta, pero bueno no vamos a entrar en mas detalles.

Entonces el resultado de esto seria un jugador no ¿?, luego el jugador conoce a que pais pertence, pero además la bandera de su país no ¿?

Luego pudiera quedarse como

selectedPlayer = playerTeam.getPlayer(index)
countryFlag = selectedPlayer.getCountryFlag()

Bueno el uso de la regla, me obligo a repensar mi diseño (de los miembros de la clase), claro teniendo algún conocimiento de OO, quizás estoy errado, espero comentarios.

Anónimo dijo...

Erne, a lo que me refería inicialmente es a que, cuando se plantea "Reglas para diseño de software" yo pienso en cosas como, cómo organizar los layers y los tiers, cuál es la interrelación de las estructuras de datos, cómo se deberá organizar la capa de persistencia, qué servicios se le ofrecerán a un cliente fuera del sistema, etc etc etc. Pienso mucho más en estas cosas que en la manera en que se escribe el código.

Desde luego que no se debe ser nunca dogmáticos, aún no me he leído el libro. Sobre la interpretación que Piloto hace del porqué de la regla de los NO-else, creo que tiene cierta validez. Yo añadiría que generalmente un método bien programado no debe tener muchas instrucciones. Mi otra objeción a este punto es que si aplicamos esa regla y la condición implica la ejecución de un código, éste estará repetido dos veces innecesariamente, todo esto en aras de presentarle a otro programador una lógica más masticada.

Para casos como estos existen herramientas de refactoring; Microsoft tiene algunas muy buenas que te extraen secciones largas de un pedazo de código y te lo aíslan en un llamado a otro método.

Saludos a todos.

Jazz dijo...

Si. Para refactorizar yo uso el Resharper 3.0

Lo que tiene que ser en una buena maquina con bastante RAM porque consume muchos recursos. Pero es un batazo.

Oye ernesto, no me descuarejingues asi mi inocente linea de codigo, jejeje. Fue lo mejor que se me ocurrio para ilustrar mi idea.

No tengo el libro. Me mandas el link?

Saludos a todos!

Piloto.

Karel dijo...

He seguido los comentarios y creo necesario hacer algunas aclaraciones sobre este ensayo.

Aunque es muy controversial y excesivamente restrictivo (dicho por el mismo autor), el ejercicio (no metodologia :-D), nos fuerza a pensar en buenas practicas de programacion orientada a objetos.

Ideas como low coupling (pocas dependencias), high cohesion (alta cohesion y clases enfocadas), complejidad ciclomatica y otras mas, estan detras de cada una de las reglas propuestas por este ejercicio.

La idea de usar solo un punto (one dot) en una invocacion no es precisamente legibilidad del codigo, sino encapsulacion de la informacion. Tomando un ejemplo de los comentarios:

por ejemplo:

playerTeam.player[index].country.flag;

es menos claro que

selectedPlayer = playerTeam.player[index];
playerCountry = selectedPlayer.country;
playerFlag = playerCountry.flag;

En este caso la clase que usa la instancia de PlayerTeam tiene que conocer que PlayerTeam tiene una coleccion de players y mas aun, representada en un arreglo :-o, ademas, que tiene una instancia de Country that also has a flag.

De modo que la clase que hace uso de PlayerTeam "sabe mucho" de otras clases e incluso como estan implementadas. Algo distinto hubiera sido:

Flag flag = playerTeam.getContryFlag(player)

De este modo la clase que hace uso de playerTeam no tiene que saber que tiene una coleccion de players ni mucho menos que esta representado en un arreglo, ni las otras instancias que este contiene.

Este es el principio basico de "information hiding" que es uno de los mas importantes en OO programming.

Karel dijo...

Otra regla que propone Jeff Bay es tener solo una clase con una coleccion.

class School
{
List students;

String name;
String address;
......
}

La idea es mover la lista de esutidantes a una clase ella sola, digamos: Students

class School {
Students students;

.....
}

Esta es una idea altamente restrictiva, que tiene que ser violada cuando usas JavaBeans frameworks como Struts, Hibernate, y quiza es aplicable igual a .NET.

El objetivo es que cualquier metodo para iterar, obetener ciertos tipos de estudiantes, cualquier operacion en la coleccion en general se mueve a la clase Students y se elimina de la clase School.

Ademas, cualquier otra clase que trabaje con students tiene que arrastrar la implementacion de la coleccion de students como una lista en vez de trabajar con la clase Students. Moviendo la coleccion a una sola clase no solo las operaciones sobre la coleccion se mueven a la nueva clase, Students, sino que la implementacion ya sea en arreglo, lista, etc, queda oculta no solo a School sino a cualquier otra lcase en el modelo que use una coleccion de estudiantes.

Alta cohesion (high cohesion) quiere decir tener clases enfocadas en hacer una sola cosa, no mas de una. Tener una alta cohesion permite tener un codigo mas legible y facil de entender.

Baja cohesion generalmente ocurre cuando una clase tiene metodos largos (los metodos tambien deben tener una alta cohesion) o un numero grande de instancias de clases, donde la clase con baja cohesion se encarga de multiple responsabilidades, haciendola mas compleja y poco legible.

Un desafio bien grande seria implementar estas relgas en frameworks como Hibernate y Struts o cualquier otro JavaBean based framework en Java, aunque estas ideas son independientes de cualquier lenguaje.

Karel dijo...

Sobre el if/then/else:

Aunque todos sabemos usar el if/then/else, no quiere decir que el uso indiscriminado de esta estructura de control sea valido. Pregunten por un codigo famoso conocido como "Mickel's Auto Service". Quiza pronto bloguee al respecto y le mande una copia al autor.

Estructuras condicionales anidadas producen codigos ilegibles, que propician errores frecuentemente y aumentan la complejidad cyclomatica del codigo. Muchos especialistas de OO como Fowler, Kent Beck, the gang of four (OO design patterns) han identificado los problemas que generan tener estructuras condicionales largas y anidadas en el codigo.

Patrones de disen~o como "Strategy" design pattern, "Template Method" design pattern, "State" and "Command" design patterns, ayudan aeliminar estructuras condicionales complejas en el codigo.

Jeff fue un poco mas lejos y dijo "no al else", como una medida restrictiva en el ejercicio que el propuso para movernos a pensar mas en el uso de estos patrones al no poder seguir nuestra tendencia de llenar el codigo de if and elses.

Yo por ejemplo, aunque si uso el else en un simple if/then/else y muchas veces no lo uso, siempre que encuentro una condicional pienso si tiene la posibilidad de que en el futuro crezca, y si es asi hago refactoring al patron mas adecuado.

Karel dijo...

Sobre refactoring:

Aunque tanto Java como .NET tienen herramientas que ya hacen refactoring, casi todas han implementado los refactorings, algunos muy simples pero efectivos como el "extract method" del libro seminal de Martin Fowler "Refactoring" cuyo catalogo contiene refactorings que mas alla de tener una herramienta que nos ayude (lo cual es esencial e importante), nosotros como programadores debemos tener conciencia de que intension subyace detras de los mismos.

Karel dijo...

Sobre TeamPlayer design y "tener variables miembros dentro de una coleccion"

Queria hacer un comentario sobre la duda en una de los comentarios que ponia este ejemplo:

[Serializable]
public class PlayerTeam:
List< Player >
{
private String teamName;

public PlayerTeam( string teamName, params Player[] players )
{
this.TeamName = teamName;
AddRange( players );
}

[XmlElement]
public String TeamName
{
get
{
return teamName;
}
set
{
if ( ( value == null ) || ( value.Length == 0 ) )
throw new ArgumentException("Invalid value");

teamName = value;
}
}
}

Primero (y esta es mi opinion personal), yo llamaria la clase "Team" solamente, porque dentro del contexto del dominio, se deduce que es un team de players. A no ser que pudiera haber teams de otras cosas, pero me estoy alejando del tema.

Segundo, habria que cuestionarse el disen~o de hacer que Team herede de List< Player > que nos lleva a una pregunta clasica en OO programming: es team una collecion o Team contiene una coleccion?

IMHO (In my humble opinion), pienso que Team no es una coleccion, sino que contiene una coleccion de players (asociacion por composicion).

El hecho que Team herede de List< Players > ata completamente Team a una implementacion y haria que todas las clases que usan Team arrastren con esa decision.

Pero mas alla de esa consideracion, es situar Team en el concepto del dominio correcto: Team no es una collecion, sino que esta compuesto por una coleccion de players. De modo que yo modelaria Team:

class Team {
List< Player > players;

.......
}

o

class Team {
Players players;

......
}

Unknown dijo...

[Uno mas metiendo la cuchareta...]

Estoy de acuerdo con Karel respecto a su ultimo comentario

seria su clase Team, que contiene una lista de Player, semanticamente es lo correcto.
A veces no es solo mirar la gramatica, hay que ver que la semantica de los Objetos tenga sentido en el Dominio en que se estan representando.

saludos... (;-p)

OffTopic: Creo que ahora que empece de nuevo a programar voy a poder, ademas de aprender de sus aportes y comentarios, aportar algo tambien. Gracias...

Jazz dijo...

Si. Muy buenos todos tus apuntes Karel. Me queda mucha mas clara la idea ahora porque al final me baje el libro pero no lo he ni mirado.

Muy clara la idea de la coleccion por clase y tambien la del "information hiding". De cierta forma me fue muy familiar entenderlo una vez que lo expusiste porque yo estuve trabajando un tiempo usando "Domain Driven Design" y una de las estructuras principales (los agregados) que propone este metodo se basan fundamentalmente en este principio de la OOP.

El DDD es una solucion muy buena cuando se tiene un dominio de negocio muy complejo. Les sugiero le echen un vistazo y si ya lo han hecho que habramos otro hilo donde discutamos los aspectos mas interesantes.

Saludos a todos.

Piloto.

Karel dijo...

Tambien debia aclarar que el titulo del Blog entry no debia ser "Las 9 reglas para un mejor diseño del software". No son nueve reglas a seguir, son simplemente 9 reglas que imponen restricciones impracticables en algunos casos reales en aras de mejorar nuestro disen~o de sistemas OO.

Ahora bien, alguien decia en los comentarios que le parecia mas bien que estas reglas no era para una mejor codificacion y no para un mejor disen~o del software.

Yo no estoy particularmente de acuerdo: el codigo es el principal artifact para un buen disen~o. No hay buen disen~o con mal codigo, sin embargo, un codigo mantenible, modificable y legible siempre responde a un buen disen~o.

De hecho yo he trabajado en compan~ias donde es obligatorio comentar hasta lo que es obvio intuitivamente. Martin Fowler dice que si uno tiene que comentar el codigo para hacer entender un mal disen~o, es mejor hacer refactoring. Un metodo altamente enfocado, con 5 - 10 lines maximos, trabajando al mismo nivel de abstraccion y con un nombre correcto, no necesita comentarios.

Ahora un metodo de 100 lineas (hay muchos por ahi) o tiene un nombre enredado o hace mucho mas que lo que dice su signature.

Ademas, Jeff Bay sugiere tener clases con una o dos instancias de clases, 50 lineas de maximo y paquete con un maximo de 10 clases.

En este sentido toca la arquitectura en un nivel mas global pues los paquetes son subsistemas que tambien deben estar altamente enfocado, concentrarse en resolver un conjunto de funcionalidad bien especifico. Es por eso que el ejercicio sugiere tener como maximo 10 files per package.

Espero que todos estos comentarios hayan aclarado un poco que estas no son reglas, sino un ensayo que propone un ejercicio de un proyecto de 1000 lineas donde se apliquen 9 reglas (del ejercicio, pero basadas en experiencias practicas de muy buenos programadores en la industria) para forzarnos a pensar en un buen disen~o orientado a objeto.

Otra de las reglas que no se ha hablado aqui y que lo hare en un thread aparte sera "No usar getter and setters". Continuara ....

Anónimo dijo...

Karel,

De acuerdo con tus comentarios...

PS:/ Caballeros, ¿qué les parece LINQ, han visto algo de eso?

Saludos a todos.

Nelyuska Vazquez dijo...

En este sitio web http://www.asp.net/learn/linq-videos/ hay unos videos de LINQ que les daran una idea rapida de como funciona.

A mi me parece muy prometedor. Yo estoy desarrollando un proyecto en .NET usando TypedDatasets y ahi las queries se escriben como texto. Hay una validacion cuando las creas pero cuando hacemos cambios en la base de datos siempre tengo que correr los tests de comprobacion, para ver que todo esta bien, pero si alguien hizo una query y no le hizo test nos enteraremos del error en tiempo de ejecucion.

Con LINQ la idea es escribir las queries como parte del codigo, eso me encanta. Y asi cuando hay cambios en la base de datos tu proyecto no compila mas por lo que uno puede detectar muy rapido el impacto de cada cambio.

Aqui les pongo un ejemplo:

NorthwindDataContext db =
  new NorthwindDataContext();

var products =
 from p in db.Products
 where p.OrderDetails.Count > 2
 select new
 {
  ID = p.ProductID,
  Name = p.ProductName,
  NumOrders = p.OrderDetails.Count,
  Revenue = p.OrderDetails.Sum(o => o.UnitPrice * o.Quantity)
 };

 gridView1.DataSource = products.
  Skip(startRow).Take(pageSize);
 gridView1.DataBind();

Pero bueno todo esto en teoria pues en realidad no lo he usado. Tenemos planificado usarlo en el proximo proyecto asi que para entonces tendre mejores comentarios.

Anónimo dijo...

Yo lo estoy usando también, bastante. LINQ está genial. Los lambda expressions están super fabulosos. Casi todo se basa en métodos de extensiones.

Y ahora voy a empezar a usarlo un poco más en un proyecto web usando las WPF (de la cual no tengo la más p... idea), pero hasta donde he visto, son fáciles de trabajar y los conceptos muy similares a los de Winforms.

Para web (ASP.NET) también hay componentes visuales a los que tú solamente le asignas el entity, y leyendo las propiedades por medio de reflection te muestran todos los datos, actualizándote luego el contexto, si lo cambias.

Está fuera de liga.

Saludos.