Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw
 
PerlMonks  

Why and how to re-use code to make better applications and you a better programmer. (discussion)

by deprecated (Priest)
on Jun 29, 2001 at 18:24 UTC ( [id://92632]=perlmeditation: print w/replies, xml ) Need Help??

My good friend Carl Manaster sent me this a couple days ago, and I was really taken with just cool it was, and how much I wished other programmers were as diligent as he is (and I try to be).

This is his work, I have permission.


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:

  1. Write a specific case;
  2. Derive a second specific case from the first;
  3. Write a general solution;
  4. Apply the general solution to the first two cases;
  5. Use the general solution as needed.

and the rationale:

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

--
Laziness, Impatience, Hubris, and Generosity.

  • Comment on Why and how to re-use code to make better applications and you a better programmer. (discussion)

Replies are listed 'Best First'.
Re: Why and how to re-use code to make better applications and you a better programmer. (discussion)
by bikeNomad (Priest) on Jun 29, 2001 at 18:45 UTC
    It gets even better after you've tried to apply your "general" solution a third time and found that it wasn't easy to re-apply. So you go back over the first two.

    I've worked with several large OO frameworks. The most successful of these started out as successful applications, then gradually became frameworks as people copied the applications and re-used the code.

    At some point (like after the first three applications), a re-factoring was done that expressed the common code into a simple framework. Capabilities were only added to the framework as it could be proven that they were needed, and only after considerable thought.

    I've also seen cases where people tried to write general frameworks up front, and ended up writing lots and lots of code to support functionality that no one actually needed in practice. This is a waste of time and a source of bugs.

    So I'd modify the recommendations given above to add step

    6: iterate back over 4 and 5 with the next few new applications, refactoring as needed, and being willing to touch existing applications to bring them in line with the newest version of the module, library, or framework.

    It's a lot better to have several working applications that use the same shared code. New users of the shared code can then look at the applications and see how to do it without being distracted by historical remnants.

Re: Why and how to re-use code to make better applications and you a better programmer. (discussion)
by Sherlock (Deacon) on Jun 29, 2001 at 19:02 UTC
    I like the way this is laid out. I'd ++ Carl, if I could. ;)

    This is almost exactly the process I use when I'm attempting to come up with an algorithm for something. Start with something concrete, then move to something abstract and test it against your concrete example that you started with.

    There is one point that Carl seems to allude to, but never really seems to put his finger on that I'd like to address; Once you believe you have created an abstract case that is functional, you need to test that case against not only the concrete cases you've already generated, but also against any other cases you can think of - especially extreme cases.

    Let me set up this quick example (probably more related to C++, but this is something I recently did, so it's fresh in my mind). I needed to write a simple function to add an element to a home-rolled linked-list type structure. To accomplish this, I first created a test list and a test element and simply inserted it into the list to make sure that it worked. I then inserted another element into the list in another position to see what had changed. This would be equivalent to Carl's steps 1 & 2, I believe. Next, I wrote a general solution (a function) that would insert an element for me and I used that function to insert the two elements that I had already inserted in the previous two steps. (Carl's steps 3 & 4) Once that was working, I was reasonably sure that I now had a working solution.

    What I still had left to do was to test my solution against all possible situations. I'm sure you all know what an impossible task that is. The goal here is to partition your inputs into "domains." The idea behind these domains is to test as much of your function domain as possible in as few test cases as possible (Classic laziness). The goal is to have a number of "domains" of input in which, if you select a set of inputs from one of your domains, most of the other inputs that you could have selected from that domain will either fail or succeed, just as the one you selected. Again, let me refer to my example.

    My inputs really only consisted of one thing - the position at which I wanted to insert the element into the list. The "domains" I came up with consisted of: front of the list (index == 0), whithin the list (index > 0 && index < numItems - 1), end of list (index == numItems - 1). I tested my solution three times (this is in addition to against the concrete examples I had already utilized), once from each domain. So, simply put, I tested my solution by inserting an element into the front, middle, and end of the list - as dictated by my domains.

    Separating your inputs into domains is seldom such a trivial process as it was in this case. The more inputs you add, the more test cases you'll have to utilize (just another benefit to small, concise functions). This step is somewhat of an art and it takes practice to master (I surely haven't done it yet), but I feel that this is one of the most important steps in making reusable code.

    So what I'm really getting at is that I'd like to ammend Carl's step 5 to include greater testing of the algorithm. I think he treated that just a little too lightly. He seemed to hint at it, "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 I think this step needs to be stressed a little more.

    That's just my opinion, though. This is, however, and excellent formalization of the steps taken to create an algorithm. Thanks.

    Addendum: While previewing this post, I noticed a flaw in my domain partitions. Can you see it? You can view the source to see a further explanation.

    - Sherlock

    Skepticism is the source of knowledge as much as knowledge is the source of skepticism.
Re: Why and how to re-use code to make better applications and you a better programmer. (discussion)
by PsychoSpunk (Hermit) on Jun 29, 2001 at 19:26 UTC
    Brother dep, your friend Carl has essentially recreated the steps to proof by induction. This is by no means a slam on the steps outlined, but in fact praise that you were kind enough to share the "recipe". I think that a lot of developers (myself included at times) forgo the science part inherent in code development. That's why information like this needs to be disseminated to us as a reminder, or a slap on the face as the case may be, to help teach good coding style. Pass the ++ on up to your friend.

    ALL HAIL BRAK!!!

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://92632]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others studying the Monastery: (5)
As of 2024-04-20 00:05 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found