Archive for December, 2009
OOP: Behavioural and Structural constraints
A few months ago I wrote a post describing how we should test the behaviour of code rather than the implementation whereby we would write tests against the public API of an object rather than exposing other internal data of the object and testing against that directly.
While I still think this is a useful way of testing code I didn't really have a good definition for what makes that a test of an object's behaviour.
I've been reading through James Odell's 'Advanced Object-Oriented Analysis and Design Using UML' and he describes it like so:
Behavioural constraints limit the way object state changes may occur
In Meilir Page-Jones language I think this would describe informative and imperative messages:
- Informative – a message telling an object about something that happened in the past.
- Imperative – a message telling an object to take some action on itself.
Both of these types of messages change the state of the object so in C# or Java these would be the public methods on an object that clients interact with.
That seems to describe the way that we would test the object. These would be the methods that we'd call in our test.
Odell goes on to describe structural constraints:
Structural constraints limit the way objects associate with each other, that is, they restrict object state.
This seems close to an interrogative message:
- Interrogative – a message asking an object to reveal something about itself.
This would seem closer to the way that we would verify whether the object's state changed as expected. We're querying the object through the structural constraints that have been setup.
I can think of two main reasons why this approach is more effective than just testing directly against the internals of an object:
- It ensures we're testing something useful otherwise we might be writing tests on our code for a scenario that will never happen.
- We have a better idea of when we've finished writing our tests since we know when we've tested all the behaviour.
Roy Osherove's TDD Kata: My first attempt
I recently came across Roy Osherove's commentary on Corey Haines' attempt at Roy's TDD Kata so I thought I'd try it out in C#.
Andrew Woodward has recorded his version of the kata where he avoids using the mouse for the whole exercise so I tried to avoid using the mouse as well and it was surprisingly difficult!
I've only done the first part of the exercise so far which is as follows:
- Create a simple String calculator with a method int Add(string numbers)
- The method can take 0, 1 or 2 numbers, and will return their sum (for an empty string it will return 0) for example "" or "1" or "1,2"
- Start with the simplest test case of an empty string and move to 1 and two numbers
- Remember to solve things as simply as possible so that you force yourself to write tests you did not think about
- Remember to refactor after each passing test
- Allow the Add method to handle an unknown amount of numbers
- Allow the Add method to handle new lines between numbers (instead of commas).
- the following input is ok: "1\n2,3" (will equal 6)
- the following input is NOT ok: "1,\n"
- Make sure you only test for correct inputs. there is no need to test for invalid inputs for these katas
- Allow the Add method to handle a different delimiter:
- to change a delimiter, the beginning of the string will contain a separate line that looks like this: "//[delimiter]\n[numbers…]" for example "//;\n1;2" should return three where the default delimiter is ';' .
- the first line is optional. all existing scenarios should still be supported
- Calling Add with a negative number will throw an exception "negatives not allowed" – and the negative that was passed.if there are multiple negatives, show all of them in the
Mouseless coding
I know a lot of the Resharper shortcuts but I found myself using the mouse mostly to switch to the solution explorer and run the tests.
These are some of the shortcuts that have become more obvious to me from trying not to use the mouse:
- I'm using a Mac and VMWare so I followed the instructions on Chris Chew's blog to setup the key binding for 'Alt-Insert'. I also setup a key binding for 'Ctrl-~' to map to 'Menu' to allow me to right click on the solution explorer menu to create my unit tests project, to add references and so on. I found that I needed to use VMWare 2.0 to get those key bindings setup – I couldn't work out how to do it with the earlier versions.
- I found that I had to use 'Ctrl-Tab' to get to the various menus such as Solution Explorer and the Unit Test Runner. 'Ctrl-E' also became useful for switching between the different code files.
Simplest thing possible
The first run through of the exercise I made use of a guard block for the empty string case and then went straight to 'String.Split' to get each of the numbers and then add them together.
It annoyed me that there had to be a special case for the empty string so I changed my solution to make use of a regular expression instead:
Regex.Matches(numbers, "\\d").Cast<Match>().Select(x => int.Parse(x.Value)).Aggregate(0, (acc, num) => acc + num);
That works for nearly all of the cases provided but it's not incremental at all and it doesn't even care if there are delimeters between each of the numbers or not, it just gets the numbers!
It eventually came unstuck when trying to work out if there were negative numbers or not. I considered trying to work out how to do that with a regular expression but it did feel as if I'd totally missed the point of the exercise:
Remember to solve things as simply as possible so that you force yourself to write tests you did not think about
I decided to watch Corey's video to see how he'd achieved this and I realised he was doing much smaller steps than me.
I started again following his lead and found it interesting that I wasn't naturally seeing the smallest step but more often than not the more general solution to a problem.
For example the first part of the problem is to add together two numbers separated by a comma.
Given an input of "1,2″ we should get a result of 3.
I really wanted to write this code to do that:
if(number == "") return 0; return number.Split(',').Aggregate(0, (acc, num) => acc + int.Parse(num));
But a simpler version would be this (assuming that we've already written the code for handling a single number):
if (number == "") return 0; if (number.Length == 1) return int.Parse(number); return int.Parse(number.SubString(0,1)) + int.Parse(number.SubString(2, 1));
After writing a few more examples we do eventually end up at something closer to that first solution.
Describing the relationships in code
I'm normally a fan of doing simple incremental steps but for me the first solution expresses the intent of our solution much more than the second one does and the step from using 'SubString' to using 'Split' doesn't seem that incremental to me. It's a bit of a leap.
This exercise reminds me a bit of a post by Reg Braithwaite where he talks about programming golf. In this post he makes the following statement:
The goal is readable code that expresses the underlying relationships.
In the second version of this we're describing the relationship very specifically and then we'll generalise that relationship later when we have an example which forces us to do that. I think that's a good thing that the incremental approach encourages.
Programming in the large/medium/small
In this exercise I found that the biggest benefit of only coding what you needed was that the code was easier to change when a slightly different requirement was added. If we've already generalised our solution then it can be quite difficult to add that new requirement.
I recently read a post by Matt Podwysocki where he talks about three different types of programming:
- Programming in the large: a high level that affects as well as crosscuts multiple classes and functions
- Programming in the medium: a single API or group of related APIs in such things as classes, interfaces, modules
- Programming in the small: individual function/method bodies
From my experience generalising code prematurely hurts us the most when we're programming in the large/medium and it's really difficult to recover once we've done that.
I'm not so sure where the line is when programming in the small. I feel like generalising code inside small functions is not such a bad thing although based on this experience perhaps that's me just trying to justify my currently favoured approach!
Debug It: Book Review
David Agans' 'Debugging' is the best debugging book that I've read so I was intrigued to see that there was another book being written on the subject.
Paul Butcher offered me a copy of the book to review so I was keen to see whether it was more like 'Debugging' or 'Release It' as Ted Neward suggests.
The Book
Debug It by Paul Butcher
The Review
Much like Krzysztof Kozmic I found that a lot of the ideas early on in the book were similar to what I've been taught by my ThoughtWorks colleagues over the last 3 1/2 years.
I do think it's really good seeing these ideas in words though because it's quite easy to forget about the best way to approach problems in the heat of the moment and the approaches suggested by Paul certainly aren't done everywhere in my experience.
These were some of my favourite parts of the book:
- When chasing a bug Butcher suggests that a useful technique to use is to try and disprove your theory of why the problem has happened. Too often we come up with a theory and just adapt any data to fit our thinking. This is also known as confirmation bias.
In his talk 'Pimp my architecture' Dan North suggests a similar approach more generally when working out how to tackle any problem. Each person has to take the other person's argument and then fight for that to be used instead. I quite like this idea – certainly something to try out.
- When discussing the need to refactor code as we go along, the author points out that if the code we want to change doesn't have any tests around it then we need to write some to provide us with a safety net.
Remember, however, that refactoring crucially depends upon the support of an extensive suite of automated tests. Without tests, you’re not refactoring. You’re hacking.
Hamlet D'Arcy makes a similar point but perhaps more forcibly in a really good blog post and Michael Feathers' 'Working Effectively With Legacy Code' covers the topic in much more detail.
- One tip which seems obvious but is still one I've tripped up on many times is to go through the list of changes that we've made before checking in! It's incredibly easy to forget about some seemingly insignificant change that we made before checking it in and perhaps breaking our application unexpectedly.
Somewhat tied in with this is the idea of checking in small changes more frequently and only changing one thing at a time which I wrote about previously.
- I like that Butcher puts a lot of emphasis on ensuring that we actually know what's going wrong before we attempt to fix anything.
Without first understanding the true root cause of the bug, we are outside the realms of software engineering and delving instead into voodoo programming or programming by coincidence.
This is particularly true when addressing performance problems where he rightly suggests that we should look to profile the code before making a premature optimisation.
He also suggests using the debugger so that we can get a good idea about what the code is actually doing when it's running. While I think this is useful I feel that the need to use the debugger in this way frequently might suggest that our code is difficult to reason about which could well be something to address.
- A couple of other cool suggestions are to call on team mates to help us out if we're getting stuck trying to fix a bug and if that's not possible then to either write out the problem or talk to the rubber duck.
If you don’t have someone to play the role of cardboard cutout, all is not necessarily lost. Try scribbling down a narrative of the problem on paper or perhaps composing an email to a friend. The trick is not to censor yourself — just like a writer would.
I don't think the importance of communicating with team mates can be underestimated and Butcher points out that if we notice a bad pattern in the code than it's no good just going through and changing it everywhere. We need to talk with the rest of the team to decide whether we can get an agreement on the way we'll develop code going forwards.
- The only idea I disagreed with is that of putting assertions into the code which I feel adds clutter to our code even though it makes it fail faster than would otherwise be the case. From my experience if we write good enough unit tests and have good logging in our code then the assertions aren't needed.
In Summary
The book is pretty quick to read at around 200 pages and packs a lot of useful tips into that space. I'd say it's a pretty useful book to keep by your desk to refer to now and then.
Duke Nukem Forever & Reworking code
Cosmin Stejerean linked to a really interesting article on wired.com which tells the story of how Duke Nukem failed over 12 years to ship their latest game, eventually giving up.
Phil has written a post about the article from the angle of his experience working with these types of companies and working out how to get something into production but as I read this article it seemed to have some relation to reworking code and why/how we approach this.
It can be reworking of code through either rewriting or refactoring, but the general idea is that it's not directly contributing to getting something released.
One particular bit of the article stood out to me as being particularly interesting – it describes how they decided to change from the Quake II game engine to the Unreal one:
One evening just after E3, while the team sat together, a programmer threw out a bombshell: Maybe they should switch to Unreal? “The room got quiet for a moment,” Broussard recalled. Switching engines again seemed insane — it would cost another massive wad of money and require them to scrap much of the work they’d done.
But Broussard decided to make the change. Only weeks after he showed off Duke Nukem Forever, he stunned the gaming industry by announcing the shift to the Unreal engine. “It was effectively a reboot of the project in many respects,”
What they effectively did here is to rip out a core bit of the architecture and totally change it which would be quite a difficult decision to make if you knew you had to deliver by a certain date.
We've made that decision on projects that I've worked on but you have need to come up with a very compelling argument to do so i.e. typically that productivity will be much improved by making the change.
Whenever we talked about refactoring code during our technical book club Dave always pointed out that refactoring for the sake of doing so is pointless which to an extent explains why it makes most sense to refactor either around the code that we're currently working on or in the areas that are causing us most pain.
For me the goal of refactoring code is to make it easier to work with or easier to change but it's useful to remember that we're refactoring to help us reach another goal.
An idea which I quite like (suggested by Danilo) but haven't tried yet is running technical retrospectives more frequently so that we can work out which areas of the code we need to work out for our current release and then make use of Stuart Caborn's bowling card idea to keep track of how much effort we've spent on these problems.
It seems like any refactorings we decide to do need to be linked to a common vision which we're trying to achieve and that seems to be where Duke Nukem went wrong. The vision of what was actually required for the game to be successful was lost and as many features as possible were added in.
The Duke Nukem approach seems quite similar to going through the code and making refactorings just to make the code 'better' even though we might not see any return from doing so.
In Debug It Paul Butcher suggests that we need to approach bugs in software with pragmatic zero tolerance by realising that while our aim is to have no bugs we need to keep sight of our ultimate goal while doing so.
I think we can apply the same rule when reworking code. We should look to write good code which is well factored and easy to change but realise that we'll never be able to write perfect code and we shouldn't beat ourselves up about it.
One change at a time
I'm reading through Paul Butcher's 'Debug It' book and one of his suggestions when trying to diagnose a problem in our code is to only change one thing at a time.
In a way this might seem fairly obvious but I've certainly fallen into the trap of making multiple changes at the same time in the misled belief that it'll lead to the problem being solved more quickly.
When making changes to code Butcher has the following piece of advice which I quite like:
Once you see a change in behavior, undo whatever apparently caused it, and verify that the behavior retur ns to what it was before-hand. This is a very power ful indication that you’re looking at cause and effect rather than serendipity.
I noticed this while debugging my F# word count application. As I mentioned, I thought that the problem was that I was storing the text from all the files in memory so instead of doing that I made that part of the application lazy so that the text would only be loaded when required.
When I first did this the program still didn't work but it failed later on than it had previously.
I thought that had shown where the problem was so I put the code back to how it was previously to check.
To my surprise it still failed in the same place which meant that the change in how it executed had been coincidental rather than related to any code change I'd made.
I think this idea is more widely applicable though as I've noticed that it works quite well when refactoring as well. If we can be really certain about which changes work and which don't to a very fine grained level then we have more chance of successfully refactoring our code while ensuring that it still functions correctly.
The goal seems to be the same in both of these situations – take small steps and then get feedback quickly on how successful that small step was.
F#: Word Count using a Dictionary
Having spent some time unsuccessfully trying to make my F# attempt at the word count problem work I decided to follow the lead of the other examples I've read and make use of a Dictionary to keep count of the words.
I originally thought that I might be having a problem with the downloading of the files and storing of those strings in memory so I tried to change that bit of code to be lazily evaluated:
let downloadFile path = lazy(use streamReader = new StreamReader(File.OpenRead path) streamReader.ReadToEnd())
That didn't seem to make much difference though and it seemed like the StackOverflowException was happening on the 'List.fold' line:
let wordCount = files >> List.map downloadFile >> List.map words >> List.fold (fun acc x -> Seq.append acc x) Seq.empty >> Seq.groupBy (fun x -> x) >> Seq.map (fun (value, sequence) -> (value, Seq.length sequence))
I couldn't see a way of changing the solution such that the current approach wouldn't need that line so I rewrote part of it ending up with the following solution:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | #light #r "FSharp.PowerPack" open System open System.IO open System.Text.RegularExpressions open System.Collections.Generic let (|File|Directory|) path = if(Directory.Exists path) then Directory(path) else File(path) let getFileSystemEntries path = Directory.GetFileSystemEntries path |> Array.to_list let files path = let rec inner fileSystemEntries files = match fileSystemEntries with | [] -> files | File path :: rest -> inner rest (path :: files) | Directory path :: rest -> inner (List.append rest (getFileSystemEntries path)) files inner (getFileSystemEntries path) [] let download path = using (new StreamReader(File.OpenRead path)) (fun reader -> reader.ReadToEnd()) let writeTo (path:string) f = using (new StreamWriter(path)) (fun writer -> f writer) let words input = Regex.Matches(input, "\w+") |> Seq.cast |> Seq.map (fun (x:Match) -> x.Value.ToLower()) let apply (dict:Dictionary<string,int>) key f = if(dict.ContainsKey(key)) then dict.[key] <- f dict.[key] else dict.[key] <- f 0 let startTime = DateTime.Now let dict = new Dictionary<string, int>() files "Z:\\20_newsgroups" |> List.iter (fun file -> download file |> words |> Seq.iter (fun word -> apply dict word ((+) 1) )) printfn "Writing counts in alphabetical order" writeTo "C:\\results\\counts-alphabetical-fsharp.txt" (fun out -> dict |> Seq.sortBy (fun x -> x.Key) |> Seq.iter (fun entry -> out.WriteLine(entry.Key + " " + entry.Value.ToString()))) printfn "Writing counts in descending order" writeTo "C:\\results\\counts-descending-fsharp.txt" (fun out -> dict |> Seq.sortBy (fun x -> x.Value * -1) |> Seq.iter (fun entry -> out.WriteLine(entry.Key + " " + entry.Value.ToString()))) let endTime = DateTime.Now printfn "Finished in: %d seconds" (endTime - startTime ).Seconds |
As I wrote about previously I found out that I could use the 'using' function instead of the 'use' keyword with the 'StreamWriter' and 'StreamReader' so those bits of code are a bit simplified.
The way of interacting with dictionaries in F# doesn't seem as nice as in Ruby so I've ended up with the somewhat verbose bit of code on line 23. Is there a cleaner way of doing that?
By using a Dictionary I think it's now more difficult to parallelise the counting up of the words which was something I thought might be possible when I first came across the problem.
This seems like the perfect problem for the MapReduce approach although I'm not quite sure about the implementation details.
My thinking is that we'd have a Dictionary for each node/actor and it would sum up the words in its file before passing the result to another actor which would be responsible for taking all the dictionaries and accumulating the word counts?
This was an interesting problem for showing what happens if you try to store too much data in memory and it's something that I've not come across before because I don't typically work with data sets that are this big.
Book Club: Working Effectively With Legacy Code – Chapters 12 & 13 (Michael Feathers)
In the last Sydney book club that I attended before I moved back to the UK we discussed Chapters 12 and 13 of Michael Feathers' 'Working Effectively With Legacy Code'
Liz has taken over the summarising of the book club now that I'm not there so if you want to keep on reading about the book club Liz's blog is the place to go!
Chapter 12 – I Need to Make Many Changes in One Area. Do I Have to Break Dependencies for All the Class Involved?
One of the ideas suggested in this chapter is that when writing tests we should try and write these as close to the change point as possible. Ideally we want to write a test directly against the method that we're changing but sometimes that isn't possible.
In this case Feathers suggests writing a test at the closest interception point (place to detect effects) and then changing the code at the change point to ensure that the test fails as expected. Tony Pitluga describes this approach in more detail in his post about gaining confidence before refactoring.
When working out where we need to write our tests, Feathers suggests that we need to look for pinch points i.e. methods from which we can detect changes in other methods in a class.
Feathers has a nice analogy where he compares this approach to 'walking several steps into a forest and drawing a line, saying "I own all of this area".'
We can then work with the code in that area of the code base with some degree of safety until we've got the code into a better state at which point we might decide the pinch point tests are no longer needed.
Chapter 13 – I Need to Make a Change, but I Don't Know What Tests to Write
Feather suggests writing characterisation tests – tests to document what the system currently does – for the parts of the code base that we're currently working on.
This is the approach that Dave encouraged on the last project I worked on – trying to write tests for code that we're not currently working on is a bit risky since we're not really sure what the behaviour is supposed to be. In addition we don't get much benefit from them since we're not changing anything in that area.
Feathers also points out that we shouldn't try to fix any 'bugs' that we come across while writing these tests – we should instead raise them and see if anything needs to be done. I remember watching an Uncle Bob presentation where he described how he had 'fixed a bug' which actually broke all dependent systems which relied on the bug being there. This is the situation we're trying to avoid!
Another approach suggested if we're having difficulty testing a large chunk of code is to refactor it into smaller methods and then test directly against those instead. I think this works well as a short term approach until we can test more easily from elsewhere.
F#: The use keyword and using function
While I was playing around with the little F# script that I wrote to try and solve the word count problem I noticed that in a couple of places I had used the 'use' keyword when dealing with resources that needed to be released when they'd been used.
Using the 'use' keyword means that the 'Dispose' method will be called on the resource when it goes out of scope.
The two examples were 'StreamWriter' and 'StreamReader':
let writeTo (path:string) f = use writer = new StreamWriter(path) f writer
let download path = use streamReader = new StreamReader(File.OpenRead path) streamReader.ReadToEnd()
I found it quite annoying that those bits of code needed to take up three lines despite the fact I don't really need to have the class construction assigned.
Luckily there is a 'using' function available which allows us to make these bits of code more concise.
'using' takes in 2 arguments – the object to be created and a function which takes in that object and does something with it.
If we make use of that function instead of the 'use' keyword we end up with the following function definitions:
let writeTo (path:string) f = using (new StreamWriter(path)) (fun writer -> f writer)
let download path = using (new StreamReader(File.OpenRead path)) (fun reader -> reader.ReadToEnd())
When we're just doing one thing with the resource, as I am here, then I think this reads better. If we're using it for multiple different operations then perhaps the use keyword is more appropriate.
You and Your Research – Richard Hamming
Another paper that I read on my Sydney to London flight was one titled 'You and Your Research' by Richard Hamming.
It's a transcript of a talk that Richard Hamming gave to Bellcore employees at the Morris Research and Engineering Centre in 1986.
The talk is aimed at computer science researchers and Hamming describes ways for them to do the best research that they can. I think several of the ideas in the talk relate to software development as well.
These were some of the bits I found interesting:
-
Hamming makes an observation about working conditions which I thought was quite interesting:
So ideal working conditions are very strange. The ones you want aren't always the best ones for you.
From what I've noticed this is true in software development too.
As a contrived example, the 'ideal' software project would have unlimited time and money but that's never the case so we have to find ways to deal with the fact that we have to work within that framework. One of the benefits of this is that we try only to develop the features which are the most important rather than absolutely everything which might be the case if we didn't have that constraint.
I'm inclined to believe that when we're working in a difficult environment we have more freedom to try things out because what's currently being tried is probably not working anyway. This seems to be the type of situation where innovation can happen.
- Hamming seems to touch on the idea of 10,000 hours of practice to achieve expertise that J. Anders Ericcsson has researched and Malcolm Gladwell popularised:
On this matter of drive Edison says, "Genius is 99% perspiration and 1% inspiration." He may have been exaggerating, but the idea is that solid work, steadily applied, gets you surprisingly far. The steady application of effort with a little bit more work, intelligently applied is what does it. That's the trouble; drive, misapplied, doesn't get you anywhere…the misapplication of effort is a very serious matter. Just hard work is not enough – it must be applied sensibly.
This seems to cover the same type of ground that Geoff Colvin covers with the idea of deliberate practice where we should always look to practice something which is a bit beyond our current ability. That way we're always improving.
- I really liked the following part of the article:
But most great scientists are well aware of why their theories are true and they are also well aware of some slight misfits which don't quite fit and they don't forget it. Darwin writes in his autobiography that he found it necessary to write down every piece of evidence which appeared to contradict his beliefs because otherwise they would disappear from his mind.
I often forget that there are other ways of solving problems than the ways I currently use and it's always good to read about people being successful with different approaches. That way we can keep questioning what we're doing rather than just blindly doing so.
- He also talks about the idea of bouncing ideas of other people because they will get you to think about the idea in different ways:
What you want to do is get that critical mass in action; "Yes, that reminds me of so and so," or, "Have you thought about that or this?" When you talk to other people, you want to get rid of those sound absorbers who are nice people but merely say, "Oh yes," and to find those who will stimulate you right back.
I think this is where something like a technical book club can be invaluable. On every single paper or chapter we read in the ThoughtWorks Sydney book club others had different/better ideas than I did and it was really useful for showing me perspectives that I hadn't even thought of.
In 'Pragmatic Learning and Thinking' Andy Hunt suggests that whenever we read something we should try and explain the idea to a peer to see how well we understand the material and to get other ideas that we hadn't thought of.
I'd really recommend this approach.
- There's also some interesting advice about giving presentations:
The technical person wants to give a highly limited technical talk…the audience wants a broad general talk and wants much more background than the speaker is willing to give. As a result, many talks are ineffective.
…
You should paint a general picture to say why it's important, and then slowly give a sketch of what was done…the tendency is to give a highly restricted, safe talk; this is usually ineffective. Furthermore, many talks are filled with far too much information. So I say this idea of selling is obvious.
This is very similar to the advice Dan North gives. He suggests that three ideas/new things in a talk is all that people will be able to remember and anything more than this is a bit overwhelming.
There's certainly other interesting ideas in this paper but those were some of the bits that stood out for me. Worth reading.
Coding: An outside in observation
I've been reading Michl Henning's post on API design and one thing which he points out is that it's important to drive the design of an API based on the way that it will be used by its clients:
A great way to get usable APIs is to let the customer (namely, the caller) write the function signature, and to give that signature to a programmer to implement. This step alone eliminates at least half of poor APIs: too often, the implementers of APIs never use their own creations, with disastrous consequences for usability
This is similar to Michael Feathers' Golden Rule of API Design:
It's not enough to write tests for an API you develop, you have to write unit tests for code that uses your API.
When you do, you learn first-hand the hurdles that your users will have to overcome when they try to test their code independently
When we don't do this we start guessing how we think things might be used and we end up with generic solutions which solve many potential use cases in an average to poor way and none of them in a good way.
Henning goes on to say:
There are many ways to "pass the buck" when designing an API. A favorite way is to be afraid of setting policy: "Well, the caller might want to do this or that, and I can't be sure which, so I'll make it configurable." The typical outcome of this approach is functions that take five or 10 parameters. Because the designer does not have the spine to set policy and be clear about what the API should and should not do, the API ends up with far more complexity than necessary.
What I found interesting while thinking about this is that the underlying idea is to drive the design from the outside in which is quite a popular approach in several different areas of software development.
Consumer Driven Contracts
My colleague Ian Robinson came up with the idea of consumer driven contracts for describing an effective way for service providers and consumers to work together.
The idea here is that since the consumer is going to be using the service they should have more say on its design i.e. the contract is designed with the end use in sight rather than the provider just coming up with something and throwing it over the fence to its consumers who then have to deal with whatever they've been given.
Test Driven Development/Behaviour Driven Development
While not the only way that we can drive design from the outside in, test driven development is quite a useful way of achieving this when we do it well.
We might typically start out by writing a functional test to describe the functionality that we're about to create and then move down to smaller more targeted unit tests to drive out the functionality.
This approach helps ensure that we only write the code that we need to write to satisfy the bit of functionality being added and from my experience the APIs tend to be more usable than when we just write code without considering how it will be used.
This is similar to what Liz Keogh describes in her 'Pixie driven development' post where she describes how to use a behaviour driven approach to implement functionality.
Functional Programming
I've been playing around with a couple of functional languages for about a year now and I nearly always find that I start out with the high level function and then work out which other functions I need to help me solve the problem at hand.
I can't think of a better way to write functional code than this. On a few occasions I've started writing functions without keeping the end in sight and whenever I do this I seem to end up throwing away the functions because they don't contribute to the overall problem I'm trying to solve.