Cry Me A River
published: Wed, 18-Jan-2006 | updated: Sun, 23-Jul-2006
A couple of Sundays ago I was in this Starbucks in the mountains, ordering a latte. Behind me in the queue were these two couples, early forties maybe. While I waited to be served, I couldn't avoid overhearing some of their conversation. One of the wives was looking at the mugs for sale and decided she wanted one, possibly as a memento of their visit here. Her husband laughed at her and said no way. She protested, to which he replied "Cry me a river".
My immediate reaction was to label him as a complete jerk. Later on, I got to thinking about my reaction. I didn't know these people. Maybe his response was a jest, an inside joke for her. I wasn't looking at them: maybe she smiled at his response having teased him by the original request and this was one of their little games between them, one that made reference perhaps to one of their early romantic days together. But I admit I assumed the worst: that he was a boor of a husband, made even more doltish by his reply.
(Actually, a friend who was with me at the time, assured me later that their body language indicated that my reaction was correct, but that's not the point of this post.)
At work, I've assumed the mantle of design evangelist arguing for better object-oriented design and programming (OOD and OOP). I'm the public face of the effort to teach people to design and implement better domain models for our product (admittedly my friend and fellow architect Maxx helps as well). I'm in the middle of a series of lectures on how to implement the standard design patterns in C#.
One problem with the word evangelist, as Robert Scoble points out, is what it evokes in people's minds: there's a religious overtone to it that doesn't sit well.
But, it's what the word evokes in the wearer of the epithet: he becomes this fire-breathing, half-mad, man on the mountain, convinced that he is completely right all the time and hectoring those who will listen to him about the benefits of his chosen way.
A boor. A dolt. Sigh.
All right, I exaggerate a little. But that's what a blog post is for right? An attempt to aggrandize something in order to make a point. I'm not a journalist by any means, so I don't have to balance my posts by showing the other side. Hence, dear reader, do watch out for exaggeration!
The thing is, I hear and read people who just don't know how to design and write a class model describe the code they've written and it has basic flaws and they laugh it off. It makes me mad because in a way it belittles what I've learned about OOD and what I can do with OOP. I turn into this fire-breathing, etc, etc.
It's bloody hard. Really it is. I've read David West's Object Thinking and consider myself reasonably intelligent but I still struggle to get what he means. I lurk and follow along on the Test-Driven Development (TDD) forum and read what Martin Fowler and Ron Jeffries have to say there, and they have problems finding the right class model too. They have doubts, but heck they can certainly express their reservations better than I can mine.
Look, there are several principles to designing a good class model. One of the most important is the Single Responsibility Principle, generally reduced to "a class should only do one thing" although you could say the same of a method, for example. If it does one thing, you have a better chance of understanding what it does, what it doesn't do, and you have a much better chance to compose it into a larger model and use it successfully.
So when I see code like this:
function GetTextFileContents(aFilename: string): string; var SR: StreamReader; begin Result := System.String.Empty; if not &File.Exists(aFilename) then Exit; SR := StreamReader.Create(aFilename); try Result := SR.ReadToEnd; finally SR.Free; end; end;
It just makes me worry about the writer. The method is badly named
since there's no attempt to think in terms of text. It's a bit of a
hack since it makes no attempt to worry about that lil' ol' gigabyte
file you have on the system. But most of all the routine does two
things, one of which is a tumor on a fairly simple routine. So, what's
so friggin' special about a file that's not there? Why not make a file
to which the user has no read rights special too? What about if the
file name passed in is a URL? Since this routine would give you an
empty string, you'd have to write a
routine, I suppose, duplicating the majority of the code in this
So I get all uppity and have to lie in a darkened room to calm down.
There are lots of other design principles out there too. Everybody knows the Design Patterns book by the Gang of Four (Gamma, etc). Who knows the principles that are defined in the first chapter before they go on to describe the actual patterns? (Hint: there are two that are explicitly called out, "Prefer object composition over class inheritance", and "Program to an interface, not an implementation".) We were having a chat about Design Patterns the other day and one of the participants had obviously not heard of the first because he was expounding on an inheritance class model when all I could see was an interface-based one, and the model was an example of the Strategy pattern.
I have to find the darkened room again.
What about decoupling and dependency management? Most people don't give a damn. (That code up there has a dependency, by the way; one you can't avoid as I showed.) Their attitude is just write the code to work and that's it. The version 2.0 developer is left high and dry, swinging in the wind (to allow me to wildly mix metaphors): he's got to unravel all this coupling and dependency in order to have a chance of getting his job done.
At work, I just completed an exercise in gathering requirements for the next generation of our product's middle tier. It was a very interesting project and I enjoyed doing it and presenting my findings. I tried hard not to bias my interviewees to what I thought about the middle tier, but instead tried to give them an opportunity to talk about what they thought ought to be done. The biggest thing they mentioned: the next generation middle tier has to be much more modular, it's too monolithic and coupled as it stands.
The darkened room now has my name on the door.
But, you know, bugger it. On this subject, I am going to be a boor. OOD and OOP is hard. It seems easy, but until you actually think about it and do it properly, you just don’t realize how hard it can be. TDD helps some, because you don't have to design it all up front, but it's still pretty difficult. So expect lots of articles on this subject, or even the theme appearing in other posts. Cry me a river, people <g>.
Well, it wasn't long before one of my readers took me to task (yes, yes, I know, I really should use a proper blogging tools with comment support). Barry was the man who hauled me over the coals with a really cogent argument:
The method you inserted is almost (with the exception of the odd File.Exists inclusion) the same as File.ReadAllText() in .NET 2.0. I have to take exception to what you say: the method does think in terms of text:
1) It returns a unicode string, instead of an array of bytes.
2) It uses the default string encoding to decode the bytes in the file into a string, albeit implicitly.
On gigabyte text files: The fact is that there *is* a case for loading all the text in a file into memory all at one time. It's unavoidable if you use notepad or wordpad for editing text files, for example. If you need extra advanced access, you need to work a lot harder, and the utilities in .NET are there, in the shape of FileStream and Encoding, whereas StreamReader provides median access. In reality, only log file processors and other generic text-processing utilities need to worry about multi-megabyte text files.
More than this, Julian, is that I worry that I recognize the thinking. I'm sure I'm wrong in the specifics, but maybe I'm right by analogy. The most consise expression is: the best is the enemy of the good. It's especially true in coding for performance: for example, some junior programmer might write a red-black tree implementation instead of using List<T> because of the requirement for maintaining a sorted collection, but if there are never more than a few dozen items in the collection and the collection is used only once, performance is going to be roughly worse than List<T>, and the code is going to be bigger, and there will be more cache misses, and linear access will be slower, etc.
Now, you might say: we must think of the infinitely scalable case, the gigabyte-file case, the secured-file case, etc: however, if those cases aren't intrinsically part of the goal of the application, then they should be ignored: YAGNI: You Aren't Gonna Need It.
Sorry to rag on you like this, but I felt motivated to respond by more than a hint of annoyance!
And I replied:
Thanks for the email. You're the first to respond to what was, really, me being crabby. The post was the third (fourth?) iteration of a "what the heck am I doing here" kind of article (which is one reason why there was a one month gap between it and the previous one).
You are right, I was wrong. For some reason I didn't really read the bit after the ridiculous File.Exists() code and just ragged on myself about something I should have thought more on before committing bits to web.
YAGNI. Heh, one of the things I try and instill here at work where there's a bit of a culture of over-engineering and then I bite myself with it.