The wrong level of abstraction, YAGNI, LOLA, and the fallacy of re-use.
Almost as an aside, in his recent post "And get rid of those pesky programmers", Phil Haack spins out a link to Udi Dahan's "The fallacy of ReUse". Coupled with the fact that I'd just read Jeff Atwood's "The Wrong Level of Abstraction" and that this subject is a hobby horse of mine, well I have to write something, eh?
It's all about judgement, isn't it? I mean - the kind of judgement that you expect people to acquire as they gain experience in software development. When do you re-use something, and when do you just re-do the job. As a web content management (WCM) specialist, my own work often involves creating templates that generate web pages based on content entered into other parts of the system by authors and editors. My weapon of choice in this endeavour is Tridion. As with other WCM tools, the templating facilities provide you with the ability to write out the HTML of the page design, and inject specific items of data where you want. In my (not so?) humble opinion, this is exactly the right level of abstraction for dealing with the specified problem, viz. that of creating a web page.
Some people, given the same task and tools, will immediately factor every single thing into a reusable library of functions. I don't do this, and sometimes a new colleague will be surprised at this, or even say they think it's wrong. My first response is usually to tell them that as and when there is a second occasion when I need the same functionality, I will either factor it out to something more abstract, or maybe just wait for the third occasion. As the agilists will have it, You ain't gonna need it (YAGNI). The problem goes further, however. Often I am faced with a choice between writing a few lines of code locally, very closely focussed on the specific output I need, or using/creating some generic function that will give me the output I need, if only I can figure out exactly how to invoke it. Maybe it will almost do what I need. <blink>No problem, I can always tweak it up a bit and add a parameter or two. Right?</blink>
I recently came across a function whose purpose was to create hyperlinks. It would accept about ten parameters, and could produce perhaps 20 variations on the theme of a hyperlink. In order to use this function, you have to open it up and read it, otherwise you don't know how its going to behave. (I'd defy anyone to thoroughly document the consequences of combining the parameters in different ways.) So a clear and present danger in creating an abstraction is that you create aleaky abstraction. (With more than a nod in the direction of Joel Spolsky's splendid article on the Law of Leaky Abstractions, or LOLA to her friends!)
So wrapping up this trivial stuff into an abstraction just made it non-trivial to work with. Result! Actually - all the function really did was dispatch to (hopefully) the correct code snippet based on the parameters. Having the code snippets locally in the templates would have been much easier to read and less error prone. The API provided by the vendor was already at the right level of abstraction, and further abstracting it was probably a mistake.
Of course, it's not as simple as that - otherwise we would never have started down this road in the first place. There is benefit in creating library functions, but mostly it's not about saving you from writing the same three lines of code in a bunch of places. It's often the case that what you want is for any change in the behaviour of your function to be propagated automatically to all the places it's called from. The only trouble is, you can't tell after the fact whether all the people you called your code knew that this was your intention. Udi Dahan notes that the characteristic property of code which you want to reuse is that it is generic. He also notes that re-use introduces dependencies, and that dependencies are what cause all the pain and grief of software maintenance.
Jeff Atwood's problem was the other way round. His choice had been to programme at a lower level of abstraction, when perhaps choosing to use a higher level library written by domain experts would have saved him from mistakes caused by him not understanding the low-level implementation details well enough.
So when you think about moving some code into a library, think about why you're doing it. If your purpose is to capture expertise that your client programmers don't have, you are probably doing a good thing. In this case, your library will probably have a very clear, tightly focussed purpose. There must be no possible ambiguity (or leaks, if you prefer) in the API you expose. When I invoke YAGNI against prematurely factoring code out into libraries, I can hope that this introduces a shakedown period between on the one hand, needing some code, and on the other hand, needing to introduce a layer of abstraction. Without this shakedown period, it's quite unlikely that my decision to abstract will be conscious enough to encompass those questions:
- Will my library be generic?
- Is it a genuine (watertight?) abstraction, in that a naive programmer can simply call it with the assurance that the domain experts have done their job within?
- Am I happy that whatever updates and bugfixes the library gets will be appropriate for all its clients?
- Will I have problems updating the library because I don't dare break its clients?
Of course, the "domain expert" might be the programmer who writes the client. Even so, getting the interface right is just as important for your own peace of mind. Then you can afford to forget some of the detail of the inner workings.
Coming back to re-use: we are often faced with managers who assume that re-use is always good. It is up to us as technicians to make the right judgements about what should be re-used, and then if necessary inform and explain. As Udi implies, building for re-use is often synonymous with lock-in to maintenance hell. Sometimes, the right decision is to build disposable code on top of cleanly factored abstractions. The HTML templating I began with is a good example. If the site design changes, it probably makes sense just to throw the old templates away and start again. Because the underlying API gives me clean access to the various fields in the content, I can put together another page template very quickly - probably quicker than figuring out all the dependencies under the old design. Jeff's example of JQuery is another good one. Because the library is so powerful and tightly engineered, it's not going to take you long to simply re-implement your desired behaviour when the design changes.
Re-use is unlikely to be successful unless the code being re-used was explicitly designed with that in mind. Simply factoring some code out to keep it tidy isn't the same thing as building a library. Be careful that you don't accidentally give the impression that your code is intended, designed and implemented for re-use if it isn't. It's OK to write disposable code too, especially if you know the difference between the two.