· Apr 12, 2017 5m read

Bug killing development tips

Does anyone NOT use a debugger? I can't remember the last time I did. It's not because I don't dislike them, I just don't need to use them. The main reason for this is because I have a certain development methodology that either produces less bugs, catches them at a unit test level, or makes tracking them down much easier.

Here are my tips...

1. Write your own COS cheat-sheet.

This is mainly for anyone that is new to COS. One of the biggest bug hazards is not knowing how certain commands and functions work. Take the time to play with a language and try out all of its variances. The process of then documenting your own cheat-sheet will cement the knowledge and provide a quick reference review tool. Not only will you become more productive, but you will certainly write less of those avoidable bugs.

2. Switch on track variables in Studio

If this is not already on then go to studio, tools, options, environment and check the track variables tick box. If you use a variable that has not been set first then the variable will now have a squiggly line under it. This always saves me from silly typos.

You will also see there is an "option explicit". I don't find this makes any difference, but you might find it useful. It forces all variables to have a #dim declaration first.

3. Don't switch off Syntax Checking

This should be on by default, in particular underline errors. I have seen some developers switch this off complaining about performance. There are no excuses, get a faster machine. This is also in the studio options settings.

4. Always use auto complete

This might sound like a silly one, but I never fully type out a property or method name on an object, EVER. I always start off with a few characters and then explicitly select it from the auto complete suggestions. Do this and you will never have an instance member typo bug. You will also catch deeper problems such as not using the class you thought you were using.

If your object is not auto completing then make sure you add a #dim into your code, not only will it then auto complete, but you will also help other developers read your code.

5. Write less code

This is obvious, less code means less opportunities for bugs. I watch some developers and they start bashing out hundreds of lines of code when they could have done it in half or less. I'm not talking about compacting code, just carefully curating it. Yes it might take you longer initially, but you will be using time the other person lost debugging code they wrote last week. Less haste, more speed.

6. Ensure code lives in the right place

If you have an entity class such as patient, then make sure all things that affect patient live on that class. It might sound an obvious one to seasoned OO developers, but I still see far too many developers embed this type of code in their non entity methods.

Moving this code will encourage re-usability of battle tested code, and it will also make your non entity methods smaller and easier to debug.

7. Handle ALL status codes

This is an essential strategy that I do automatically. If anything returns a status code, then I validate it before moving to the next line. If its an error always quit with that status code to a level where you handle errors.

This is probably the biggest problem that I see other developers not doing. If you are not doing this then you should.

If your developing in Ensemble then there is a useful macro that is used everywhere in the Ensemble libs...

I don't like macro soup, but this one is great, it makes code less verbose so that I can read the important stuff.

Also, if you use try catch, then bubble up the catch as a status code from that method.

8. Write modular reusable code

This probably falls under the same heading as write less code, but its specific to writing methods that are as small and functional as possible. Each method should have a single purpose. If you can split the method out into several smaller methods that have a defined input and output, then you can...


After doing all of the above, this is the single next biggest thing that will weed out errors long before I would ever need to fire up a debugger. By running unit tests and having them fail enables me to concentrate on weeding out that error from that small block of code. I don't need to debug & step around code looking for this problem, I know exactly where it is.

I have a really nice in-house unit test tool that works from a browser. No complex command line set up nonsense. If anyone is interested then I will open source it. It's particularly good for unit testing Ensemble transformations.

10. And finally, when all else fails...

What would I do without globals so that I can debug code without having to step through it. I will do something along the lines of...

  Set ^debug($zh,"foovar")=foovar
And then just zwrite ^debug from the command line, or put a for loop watch on it.

This will track down my problems much quicker than having to step through lines of code, particularly with async processes.

11. One more thing, face palm moments

This is a bit of a throw away, but I do it all the time. If you are having problems that you can't track down, then take a walk and think outside the box. When you get back you will be able to see the obvious errors that you could not see because you were looking too closely. My classic is being in the wrong namespace - DOH!

Interested to hear what other tips are out there for writing bug-less code.


12. Ruber Ducking

13. Code Reviews

Very important suggestion, often missed out. I think style guides would be a good supplement to this.

14. Logging

Another good suggestion. Ensemble has a great set of tools to log and view information about your code that can highlight errors that might have gone silent, as well as tracking down intermittent problems that are hard to find.

Perhaps a community logging solution would be a good idea for Caché developers?


Discussion (17)2
Log in or sign up to continue

This is a great list!

For (10), I always use a global starting with ^mtemp rather than ^debug (for example). By default, ^mtemp is mapped to CACHETEMP. This is handy for a few reasons:

  • Debugging global sets from all namespaces go to the same database.
  • This database is reinitialized each time the system starts, so it's a great place to put data you don't care about losing. (No need to fill/expand databases you do care about with debugging information, especially for the off chance that you accidentally forget to remove the debugging code.)
  • Since CACHETEMP isn't journaled, if the debugging information is logged in a transaction, those global sets won't be rolled back even if/when the transaction is.
  • Since CACHETEMP isn't journaled, if debug code sets a lot of globals, there won't be the extra space taken up by journals. (I once crashed a shared development environment when a process running overnight filled up the disk with journal files from setting the same debug global millions/billions of times.)

I find TDD most useful for cases where there's a simple input and output to/from complex server-side processing. It'd be great to see the unit test tool you built on GitHub/etc.!

One thing missing from your list is code review. This is particularly helpful for making sure you're *actually* doing #5-8. smiley

Of course if your debugger is as bad as Cache Studio's than you have to find a way to live without wink

After so many year with Microsoft Visual Studio the debugger is the thing I use every day. Not because my code is so bad that I have to hunt the bugs all the time. I like to step through each line of new code with debugger to ensure that it not only works well but also "smells" well (names are reasonable, log texts are reasonable etc).

In Cache I use debugger to run my unit tests using my very simple unit test framework - if a test fails, you can very quickly step it through without any zen meditations.

Many thanks for the tip about track variables option,  I never knew it. Very useful. After c++ the lack of basic syntax checking drives me mad sometimes.

About (10) in your list. I would say each app must have a diagnostic log with configurable level of details. This is the way to get ideas about events/names/ids involved. Not stone-age temporary traces in the source code.