Sometimes these pesky development bugs just won't go away. I stepped back from the trees to look at the forest and try to find some commonality between the multiple applications I've worked on and the cause of the bugs found. Surprisingly I found more in common than I anticipated. Here's a few reasons why bugs exist in your applications and what you can do during development to avoid them.
This is by far the number one bug I see consistently across all applications. As a programmer we are all guilty of it. When developing a process, we need to retrieve some data from some source such as a database, flat file, etc. We setup the constraints to retrieve precisely what we need and then continue developing our workflows assuming that retrieve worked and provided us what piece of pieces of data were needed for the subsequent steps.
However, in practice, this isn't always the case. Sometimes there is bad data that doesn't meet your criteria because it got corrupted or incorrectly stored upstream in the process. Or there simply isn't a record that meets your criteria for legitimate reasons that you didn't consider when you designed the process. And finally, you or some other developer at some point changed the upstream process and wasn't aware of the ramifications to this process. In any case, it happens, and it happens frequently.
Check your retrieve for empty. If it's empty, determine how to handle the exception, such as write a message to the log, show a message to the user, rollback the process, etc. If it did retrieve something continue on. This simple test will save you headaches in the future.
Improper reuse of sub-routines
I fall for this one occasionally. You start to write code that feels eerily familiar. You remember that you've written this (seemingly) exact same routine somewhere else. Best practices tell you to break that routine out into a sub-routine so that you simply call it when needed for the many other routines that need that process performed.
The problem is that sometimes you break the sub-routine in the wrong place. For example, the routine ends with a 'Close Page' because when it was originally designed you want to end the process right there. In the new use of the routine, however, you want to navigate to a different page. You create a sub-routine from the first one and call it in the second one, then add the navigate to a new page at the end. In doing so, your new routine contains an extra 'Close Page' that it doesn't need and leads to navigation issues.
When you create a sub-routine (Sub-Microflow in Mendix, for example), don't shortchange the process. consider precisely the input and output of the routine that is universal in all situations and beware of actions not germane to the routine. Usually any sort of navigation should be avoided in a sub-microflow because in almost all cases navigation is conditional. You might think the output of the sub-routine should always navigate to a specific page, but eventually you will encounter that one condition where it should go elsewhere.
Instead, strip out navigational actions from sub-routines and in the primary routine, call the sub-routine THEN create the action for navigation. The routing and lack of output variables are primarily the reason why sub-routines get used incorrectly by mistake. This concept is a bit abstract in written form so let me draw a picture of what I'm saying in that last paragraph:
Too many commits
As application features grow so does the complexity of the inter-connectivity of the routines and actions. This one speaks a bit to the previous issue because sub-routines inside of sub-routines tends to lead to commit issues, but it can happen in a single routine as well.
Broadly speaking, you don't want to commit until you absolutely have to. Extra I/O slows down your routines and is overhead that should be trimmed. When dealing with a single routine (or Microflow, Mendix folks), don't commit any changes until the very end. You might need to make multiple changes to a record based on logical condition check results and you shouldn't be committing until the end. Extra commits might kick off event handlers on the database side (if you select to commit with events) that could negatively impact what you're doing or create a bunch of extra overhead, like audit read/writes and so on. Those negative impacts to your routines will cause bugs to exist in your application so avoid those extra commits as much as possible.
Unfortunately, though, it's never quite that simple, is it? You will likely call sub-routines in your primary routine to accomplish the goal of that workflow. Those sub-routines are used elsewhere, and it might make sense for them to commit for those workflows but not your current routine. What to do?
Time to put on your Systems Engineer hat. You can't evaluate sub-routines effect on other routines unless you consider all of the other routines that call them. What I tend to find is that commits in sub-routines are generally a bad idea. Sure, when you first write a routine you make it commit and do what it is supposed to. Then you need to do that a second time so you break it out leaving the commits intact and call it as part of another routine without a second thought. This continues on until it is getting called multiple times by multiple routines, and suddenly you have a bug issue somewhere layered on top of it all.
Instead, when you make the decision to create a sub-routine out of an existing one to be called elsewhere, remove the commit. Put on that Systems Engineer hat and understand the ramifications of all of the commits this will create throughout all of the routines that call it. Create your commits in your primary routines and leave them out of your sub-routines. Not only is it a better design in terms of I/O, but by sticking with this principle you'll be able to navigate your application with ease when determining where a commit is occurring. If you see a commit, you know this routine is never called by another routine. And don't forget: As soon as you turn a primary routine into a sub-routine, strip out the commit and create a new routine that calls it and commits to replace where it was being called directly before.
Nothing I've suggested here is a hard and fast rule. There are always exceptions. I wanted to share with you some best practices I've picked up over the years to save you some of the headaches that come from buggy development. Consider these suggestions when designing and developing your own applications and I think you'll find they will result in a less 'buggy' application!