in reply to Rewriting some code

I have decided to rewrite a badly written program of mine ... The main reason it was so badly written was that I did not fully plan out how the program would work.

Although I greatly support your desire to rewrite, I wouldn't beat yourself up too much on this. In fact I'd probably argue that this isn't why your program ended up badly written.

It's intensely diffiuclt to properly plan out software. If you're lucky enough to have a detailed specification of exactly what's required, excellent. But even then, are you sure it'll never change. This applies whether you're writing for your employer or for yourself. Requirements always change. So don't worry too much about planning for that.

Just develop what you need, when you need it. The important thing is to never hack yourself into a corner. Any time you're developing and you think "If only this piece of code had been more abstract / less tightly coupled / whatever", resist the temptation to hack around it. If you do that, then very soon you have unwieldly code that'll be a nightmare to maintain.

Instead, refactor that piece of code so that it works for what you had, and also for what you now need. (Good tests are essential). If you do this relentlessly at each stage then you always have a 'good enough' code base.

Does anybody know of some good techniques to plan out code? Which ones work, and which don't?

I'd argue that any technique to plan out code doesn't work! Do what you need as you need it, and keep improving your code as you go. That's all there is.

Tony

Replies are listed 'Best First'.
Re: Re: Rewriting some code
by Fingo (Monk) on Feb 18, 2001 at 21:40 UTC
    Well since I designed the code while I wrote it some of the most important parts did not fully work with each other since I did not plan out their interaction fully. Also I have about 4 pairs of subs where the diffirence between the pair is in one symbol(+ or -)! There is also a subroutine that has to find the integer componenent of a quotent, I did not kow I could do this in one line, so I subtracted the rmainder I would get from the divident and then divided it by the diviser!I figured this code is beyond saving anyway

      Well since I designed the code while I wrote it some of the most important parts did not fully work with each other since I did not plan out their interaction fully.

      I have to admit I find this difficult to believe. Why did you need to plan out their interaction? If you coded their interaction for one thing, and it didn't work properly, then that's not a planning issue, it's a coding issue. So why would it be any different for other interactions?

      If you need to add another interaction, and the previous code doesn't fully allow that, then rewrite the previous code to continue doing what it used to do, whilst also allowing what you now need to. This is the essence of refactoring.

      Also I have about 4 pairs of subs where the diffirence between the pair is in one symbol(+ or -)! I figured this code is beyond saving anyway

      Not at all. The standard way of dealing with these pairings (say sub do_a and sub do_b) is to create sub do_c which is the 'more correct' version that will cope with both versions, taking whatever parameters you need to supply to ensure it does the right thing.

      Then, once that is tested, you change sub do_a and sub do_b to delegate to sub do_c in the correct manner.

      Then, once you've tested this, you can step through the remainder of your codebase, either now or over any period of time you like, changing all instances of do_a and do_b to the relevant do_c calls.

      When you think you've them all cleared out you can change do_a and do_b to issue warnings, or even die, if they get called, just to make sure, and eventually remove the subs.

      The speed at which all this can happen depends on the size of your codebase and the time you've got to do all the tidying up after yourself. But stages 1 and 2 are fairly straightforward, and they're all that are required to ensure that your code still runs correctly. Everything after that is just tidying up.

      Sometimes it's definitely good to start from scratch ("build one to throw away", and all that), but there's very little code that's beyond saving, and the skills you can develop through saving unsaveable code can come in handy when you need to maintain code that you can't just start again from scatch!

      Tony

        I am sorry, but I have a lot of sympathy for the original poster. It can be easy to write an API that exposes enough about your implementation that it is very hard to later on change it. This can lead to much pain down the road.

        If you want to have code which can be refactored, you want to from the beginning expose as little in your interfaces as you can. Your components should be very simple black boxes that are easy to understand, easy to code to, and which leave you with the flexibility to change your mind later about how that section works when (not if) you find out about your original implementation mistakes.

        For more on this, pick up a good book on programming technique (my usual recommendation, Code Complete, would be a good choice) and read up on information hiding. For a couple of more data points, one of the best points that was made in The Mythical Man-Month is that the hardest class of bugs - and the one that causes the most trouble in large systems - occurs at interfaces. Also see Theo's comments on how poorly understood interfaces contribute to bugs and security holes.

        The moral? Regard every interface you expose as a chain around your neck. Avoid dependencies. And the fact that you can reach into a library and abuse some piece of knowledge about the implementation does not change the fact that doing this is generally a horrible idea.

        FIxing all of these and other other errors would mean scrapping almost everything anfd it still woulden'tr work too well.