I noticed myself doing something last night that I have done hundreds or thousands of times before, following a habit that I've unconsciously adopted. It worked - again - and I decided to formalize it, to make the practice easier to apply deliberately in the future, to better be able to determine appropriate situations for its application, and to share with others.
What I was doing was as follows. I had four little triangles to draw around the outside of circles forming an irregular shape. I wrote a routine to calculate the points of one of the triangles, with values for all of the coordinates that would only work for the first case. I then duplicated this routine, and reworked it for a second triangle. I then generalized the code, writing a routine that could handle any arbitrary triangle-outside-a-circle (within the context of my application), replaced the original two routines with calls to this one, and wrote the routines for the remaining triangles.
Here's the recipe:
- Write a specific case;
- Derive a second specific case from the first;
- Write a general solution;
- Apply the general solution to the first two cases;
- Use the general solution as needed.
and the rationale:
Write a specific case; This lets you solve the problem once without getting caught up in the abstractions. It gives you something simple to test, and a good motivation: you need this first case to work. You also need the other cases to work, but the motivation for the general case is more abstract - "softer", if you will. You can mess with values as much as you need, and it won't be messing up any other cases.
Derive a second specific case from the first; This exposes your first implementation to some stress-testing. It lets you see which parts are stable under different circumstances, and which parts need to be flexible. It gives you a second, more reflective look at the problem - because it is much easier than solving the problem in the first case, you have more of a chance to think about the problem without getting lost in the details.
Write a general solution; The bits you didn't need to change in (2) should form the basis for a common solution, with the bits you did need to change representing parameters to the routine. Apply the general solution to the first two cases; If nothing else, this gets rid of embarrassing code bloat - but of course there is much else: you get a chance to test against already-working cases, and identify and resolve discrepancies.
Use the general solution as needed. Now that you've got it, put it to work. There may be a few remaining things to fix in the general solutions - a sample size of two is, after all, unlikely to be representative of most problem populations - but these should be small, and the main work of the routine is already behind you.
Note that at every step, except (4), you have something new to test. That's good - you don't save up all the bugs for the final step. It is also a fairly even distribution of thoughtload, so you're never bored or overwhelmed.