sábado, abril 16, 2005

Las raíces de la Orientación a Objetos

H.S. Lahman dedica en su Weblog, algunos interesantes párrafos a las raíces que condujeron a la visión Orientada a Objetos, y a las etapas de evolución de la construcción del software. No es la primera vez que menciono a Lahman, uno de los constructores de MDA, pero sólo ahora caigo en la cuenta de que es un verdadero pionero (primera aplicación en 1957!). Por lo tanto, participante de toda la evolución del desarrollo de software, y voz competente para hacer una mirada de valoración. Lahman destaca las ventajas, y lo revolucionario que resultó, la aparición del paradigma de análisis, diseño y programación estructurada:

The first systemmatic attempt to eliminate hackers appeared in the form of Structured Programming that provided a collection of good practices for writing 3GL code. That was quickly followed by Structured Design and Structured Analysis, both of which introduced more abstract graphical representations of programs. The dominant design technique became top-down functional decomposition where the solution was started with a very simple and general statement of the problem solution and then one successively decomposed that solution into more detailed levels. Each statement of functionality was collected as a node in an inverted "tree" whose lowest leaves were logically indivisible.

The impact of SA/SD/SP was enormous. Defect rates dropped from 150/KLOC to 5/KLOC. In addition, productivity for large projects where multiple programmers had to coordiante efforts improved greatly. Instead of 1000 programmers working for 10 years to produce 1 MLOC, 200 programmers could do the same job in 2-5 years

Lahman describe a la vez cuáles fueron los puntos débiles del diseño estructurado, indicando especialmente dos, vinculados al mantenimiento del código estructurado: la modificación de variables de estado en puntos no determinados del código, y las dependencias jerárquicas:

There were a lot of problems that led to the Maintainability Gap but they could be broadly categorized as having two root causes: uncontrolled access to state variables and hierarchical implementation dependencies. State variable access was primarily a defect problem as data was modified in unexpected ways at unexpected times during execution. That resulted in additional test and repair cycle time when one modified existing code because it was difficult to predict how changes would affect untouched code that happened to access the same data.

Hierarchical implementation dependencies resulted in the legnedary "spaghetti code". That was because the leaf nodes in the functional decpomposition tree were at a very fine level of abstraction -- essentially arithmetic or logical operators in the 3GL. It was simply too tedious to cobble together lengthy sequences of such atomic operations to do complex tasks. However, the higher-level nodes in the functional decomposition tree quite conveniently captured such sequences as descendants. Since this nodes were systemmatically derived they had defined functional semantics. That allowed them to be reused (i.e., accessed by "clients" in different parts of the application that happened to need the same sequence of leaf oeprations).

That sort of reuse through accessing higher-level functions was a boon to developers and led to the notion of "procedural development" because it made excellent use of the core characteristic of 3GLs, block structuring around procedures. The problem, though, was that the functional decomposition "tree" now became a lattice where each node potentially had multiple ancestors (clients) as well as multiple descendants. It was that fanout of dependency that led to spaghetti code.

The dependencies existed because in top-down functional decomposition the lower-level functions are extensions of their parent higher-level function. That is, the specification of the higher-level function included the specifcation of the lower-level functions. Thus any contract between the client and the higher-level function dependend upon the specification of the entire descedant tree of functions. So if one changed the specification of a lower-level function, the specification of all of its higher-level ancestors was also changed.

That was no problem so long as the access structure was a pure tree. That's because the change was probably triggered by a need to change the specification of a higher-level function and implementing the fix in the lower-level function was simply the easiest place to do it. However, when one has a lattice, the higher-level functions have multiple clients. If only one client wants the change, the other clients may be broken by the change. Worse, there can be a client at any level of ancestry in the tree, so the change may break clients that are not even direct clients of the original higher-level function. The result was a disaster for maintainability because every change for one client could potentially break a host of other clients. Fixing things to keep all clients happy often resulted in major surgery to the tree or very complex parameterization that complicated the functions.

Podríamos decir que aún hoy estas observaciones son ampliamente verificables, sin hablar de que (lo puedo decir por Argentina y Chile) podemos encontrar con cierta frecuencia sistemas construídos bajo éste paradigma, o documentados con el método. Si me extendiera un poco más, encontraría que existen centros de estudio que aún lo enseñan como método a aplicar...

No hay comentarios.: