Thursday, December 19, 2013

Five Code Upgrade Snares and How to Avoid Them

Here are five upgrade snares that have slowed progress or even stalled attempts by well meaning competent developers who have tried to migrate large code-bases up to new Delphi versions.  

1. The "I must not rewrite anything" snare.


The person who can not decide that something needs to be fixed will never upgrade anything. The person who values the patch he made 10 years ago to an ancient version of TMainMenu will never upgrade.  This is the first snare, it's a bear trap, and it's got your leg.

To avoid it, remember that your goal is to arrive at code that builds in both Delphi 6/7/2007 and XE5.
Don't panic.  Be calm.   Now continue.

2. The "I must rewrite everything" snare.


Having escaped the first snare, the hapless code-upgrader stumbles a little further along, is heartened by having survived the first trap, and decides to rewrite almost everything.   He was last seen stumbling into the Mojave desert.  His current whereabouts are unknown.

To avoid this snare, just as the point above, concentrate on making the code build, and pour your new code efforts into increasing unit test coverage.

3.  The "Failure to Recognize that We are in Boston, rather than Chicago" snare.


In Delphi 6, or Delphi 3 or Delphi 7, there were some features that are no longer current recommended Delphi features.  The BDE is one of them. If you think you can upgrade from Delphi 6 to Delphi XE5, and continue to use the BDE for another 10 years you're going to have a bad time.  Not just the BDE, but a whole host of things have gone to "should not use" status.  Also there are new VCL framework features including TForm.PopupParent which should be used to replace a lot of your Z-Order hacks that you invested huge time in in Delphi 6.  You can stage your changes, and you should stage them. Stage 1 might be "it compiles and passes unit tests, but uses BDE". Stage 2 might be "goodbye BDE". Stage 3 might be "it properly reads and writes the unicode data formats I want it to read, such as XML".

The second half of this trap is you assume that porting means a series of mechanical changes that you don't quite understand.  Don't make a change you do not precisely understand. Unicode porting usually leads to a lot of naieve things being done in your early days. For example, on your first day of porting, if you decide you'll change "All my Strings will be AnsiStrings" you're going to have a bad time. There is no "100% always do this" law, you must know what you're doing. But you should always choose AnsiString when you need a single byte string, and you should always use String (UnicodeString) everywhere else.  Read the excellent Unicode porting materials provided by Marco Cantu and Nick Hodges.  To avoid this trap, read these guides, and follow them.

4.  The "Hacks and Customizations" snare.


One day in 2001 you decided to modify TButton. Then in 2002, you decided to modify TForm, and in 2003, you decided to modify TDataset.   You had to, at the time there was no other sane choice.   But you will now have extra work to port your not-quite-VCL-or-RTL-based codebase up to your new Delphi version.  My suggestion is to make the code BUILD without VCL or RTL customizations, and function well enough to pass unit tests.  Then investigate new fixes for your previously required workarounds that are possible with the new framework.

I have also been trapped by this one, in the form of heavily customized Developer Express commercial third-party components that I then had to port changes from the 2004 version to a modern version.   Due to the nature and extent of my changes, this took me months.  I would have perhaps attempted to do more with subclassing, rather than modifying the component itself in the future.  However, the problem is that you can only do so much with subclassing. Sometimes you're really stuck and you do have to modify the original code.

Other than not getting into this trap in the first place, the way out is with care, with unit testing, and with proper version control tagging.  Attempts to remove customizations should be accompanied by tests, automated or manual.  Preferably both.

5.   The "Wandering in the Darkness, Surrounded by Grues" snare.


If you do not have unit tests, you will not know if you broke things.

When the code compiles and passes unit tests, you are on the path.

When the code compiles but passes no unit tests, you may still be on the path. But you don't know. The light is dim.

When you have spent several days in the dark, and continue making changes to code that does not compile, you are certainly not making progress.

You have been eaten by a grue. You have died.



Avoiding this one deserves more detail in a future blog post.  But it is mostly about unit tests, live use of your app after each change so you know if you broke the app's ability to run and do fundamental things, and the same sort of education and preparation steps as the previous point.  I have some more specific ideas on this one, that will be explored in a part 2 post.


If you've got another snare that we can all learn about then avoid, please post it in the comments!


2 comments:

  1. From Jeroen Pluimers:

    "Some snares:

    - using a code generator, then modify the generated code so you cannot regenerate

    - thinking "this technology will help me" without investigating the technology and if it will help you (multi-threading as a technology quickly comes to mind)

    - using Application.ProcessMessages to solve response issues (you get reentrancy issues in return)

    - by standard working at the boundaries of language, framework and IDE and being surprised when things break in the future (you should try to solve at least 80% of your problems by staying far away from edge cases)

    This apart from the standard things I come across all the time: global variables, everything public, nothing virtual, non-existing conventions, libraries without source code, no version control, no backups, etc."

    ReplyDelete
  2. Some snares:

    - using a code generator, then modify the generated code so you cannot regenerate

    - thinking "this technology will help me" without investigating the technology and if it will help you (multi-threading as a technology quickly comes to mind)

    - using Application.ProcessMessages to solve response issues (you get reentrancy issues in return)

    - by standard working at the boundaries of language, framework and IDE and being surprised when things break in the future (you should try to solve at least 80% of your problems by staying far away from edge cases)

    This apart from the standard things I come across all the time: global variables, everything public, nothing virtual, non-existing conventions, libraries without source code, no version control, no backups, etc.

    ReplyDelete