Why is it, that after having been educated in object-oriented methods; the associated advancements like aspect-oriented methods and tools; the intellectual evolutions and clarifications of practice related to the separation of concerns; and the demonstrated effectiveness of these methodologies in producing well-organized, performant, high-functioning, easy-to-use, easy-to-maintain, and easy-to-integrate-with systems that there are still people defending bug-prone, conceptually interlaced and redundant [explicitly] procedural approaches to solving complex system engineering problems? It's not rocket science, just [basic] computer science...
For more info on...
Aspect-Oriented Methods: http://en.wikipedia.org/wiki/Aspect-oriented_software_development
Separation of Concerns: http://en.wikipedia.org/wiki/Separation_of_concerns
This isn't about being academic and pure. As engineers, our job is to create domain models (collections of related concepts) and to integrate disparate domain models. That just means define concepts and how they relate to one another. It's a lot like creating the parts of a car engine. While the engine is physical our models are conceptual (bear with me...) they are both composed of assembled parts that interact with one another in well defined ways to effect changes in one another across multiple levels of assigned purpose. Because of the physical pieces in an engine, noone is troubled by the problem of confusing what components belong in the engine, the exhaust system, the audio system, the steering assembly, etc... They are not tempted to co-locate unrelated groupings of the subsystems and components. They have no problem with organizing those components in an orderly fashion. On the other hand, people are constantly interlacing the concepts (which are much like the parts of our systems) of their models; they don't concern themselves with ensuring they are organized in an orderly and clearly understandable manner. Let me give a simple example that proves my point and that I shall beat like a dead horse (<sarcasm>all the while feeling justified, because, hey, my example proves I'm right. Right?</sarcasm>):
public class Foo {
public List<Bar> Bars = null;
public void MagicGetBarsMethod();
}
public class Bar {
public void DoYourThing();
}
public class MyApp1 {
public int Main(string[] args) {
Foo foo = new Foo();
foo.MagicGetBarsMethod();
if(foo.Bars != null) {
foreach(Bar bar in foo.Bars) {
bar.DoYourThing();
}
}
}
}
Please ignore the lack of encapsulation, the poor naming, no synchronization, etc. That wouldn't be good in a real system, but as written the stripped down example is sufficient for the discussion. Its sufficiency is that there's a conceptual leak here: the conditional evaluating foo's Bars property for null. This is an example of the leakage of the Foo concept into the domain of the "application". It's not yet a severe leakage, but this is a clear distribution of the Foo object's semantics into the general context. The consequence of this is that every piece of code which uses the Bars property now needs to contain that code. Now every instance of the use of that property has to be kept in sync. The programmer been a lazy and selfish. the programmer has experienced a failure to fully encapsulate the semantics of the Foo object has resulted in N units of extra work (N being the number of times the Foo type is used) and less readable code to boot. So there's a complaint from a work and resource point of view, but what happens after the following is written?
public class MyApp2 {
public int Main(string[] args) {
Foo foo = new Foo();
foo.MagicGetBarsMethod();
if(foo.Bars != null) {
foreach(Bar bar in foo.Bars) {
bar.DoYourThing();
}
}
else {
throw new Exception("Bars was bad.");
}
}
}
Yeah, sure, I'm dramatizing... but then again I've seen this sort of thing in much more hefty situations. What has just entered into the conceptual landscape, by the action of the external parties is a divergence in the semantics of the Foo. In the one case, there's an encoded belief that Bar's being null is a normal occurance to be ignored as standard. In the other, there's the encoded belief that Bar's being null is an exception to the norm. This conceptual divergence is problematic in isolation but much more destructive in the large. Combinatorily, these small divergences create a great number of semantic combinations... you remember combinatorics... you're unlikely to find the factorial consequences in a real world system, but even the early stages are bad enough. Is there malice involved with these sorts of occurrences? Probably not; more often than not, it is the development of what had been a simple concept into a more complex or nuanced concept that is what causes such changes to be introduced. The problem is not that the concepts needed to be enriched, but only that the enrichment was performed outside of the concept that was expanding.
Being extreme? Yeah, that's me... There's an easy and excellent counter argument here: it's not the semantics of Foo that leaked and in fact there was no leakage at all. To the contrary, what we're seeing is the semantics of the two applications and how they regard and interact with the Foo concept. To state it in the other terms, the conceptual expansion didn't occur in the Foo concept, but instead occurred only within the App2 concept. For App1, seeing a null Bar isn't significant and means there's nothing to do while for the second, it's an expectation to the expectation that Bar always has something to give, that is one of the preconditions of the second app and the consumer of the application's asserted responsibility (that is, to only execute main when there are one or more Bars to be had). That would be appropriate. Still though, there's no reason for Bar to ever be null; if it's an exceptional situation for app 2 to get ahold of a Foo that produces an empty Bar, it can check the count of items in the collection and throw if the condition is violated. This allows for cleaner code and better separation of the concerns and responsibilities of the items involved:
public class Foo {
public List<Bar> Bars = new List<Bar>();
public void MagicGetBarsMethod();
}
public class Bar {
public void DoYourThing();
}
public class MyApp1 {
public int Main(string[] args) {
Foo foo = new Foo();
foo.MagicGetBarsMethod();
foreach(Bar bar in foo.Bars) {
bar.DoYourThing();
}
}
}
public class MyApp2 {
public int Main(string[] args) {
Foo foo = new Foo();
foo.MagicGetBarsMethod();
if(foo.Bars.Count == 0) {
throw new Exception("There were no Bars.");
}
foreach(Bar bar in foo.Bars) {
bar.DoYourThing();
}
}
}
My next post will discuss my miserable failure to engineer the Foo object with quality.