Main image of article 5 Ways to Catch and Handle Errors in Desktop and Web Apps
It’s a sad fact of life that, no matter how skilled the programmers writing it, all code has errors (at least initially). There are lots of reasons for this: misunderstanding the project specs, for example, or an inelegant attempt to patch a feature. It’s up to tech pros to handle these errors as best they can; here are five suggestions for efficient troubleshooting:

Logging

This is probably the most important method of the five. With logging you can:
  • Validate every run for errors/correct running.
  • Build up a history of runs.
  • Log special issues (i.e., unusual occurrences).
For headless software (i.e., software that runs without a user interface) such as servers or batch jobs, logs provide proof that either everything went okay or something failed. There's really no other efficient way to catch bugs; when a headless program crashes without logs, it’s a hard fix. At its simplest, a logging system can just output a text file. At its most complex, a platform along the lines of Apache Log4J 2.0 (for Java applications) or the .NET equivalent (Apache Log4net) can provide almost every type of log possible. For example, do you want logs sent to email, written to disk, or both? I wrote a very short class in C# to log to a text file. It uses the .NET StreamWriter class with the append parameter set ‘true’ to append the file, not overwrite it. Note how this code below includes Trace (for more information about Trace, see the OS Debug Routines in the next section):
using System;
using System.IO;
using System.Diagnostics;  // for Trace

namespace logging
{
    public class logging
    {
        string filename;

        private string InQuotes(string text)
        {
            return '"' + text + '"';
        }

        public logging()
        {
            filename = DateTime.Now.ToString("dd-MM-yyyy") + ".txt";
            Trace.AutoFlush = true;
            log("Program started");
        }

        public void log(string msg)
        {
            using (var sw = new StreamWriter(filename, true))
            {
                sw.Write(InQuotes(DateTime.Now.ToString("dd-MM-yyyy hh:mm:ss")));
                sw.Write(",  ");
                sw.WriteLine(InQuotes(msg));
                Trace.WriteLine($"Message= {msg}");
            }
        }
    }
}
This was called from a small console application:
        static void Main(string[] args)
        {
            var l = new logging();
            l.log("Application Ended");
        }
Run logs show that the application ran successfully, or reveal the errors that killed the run. You can also use debugging to “breadcrumb” the path right up to the application’s point of failure; that means adding a log call after each statement (Log(‘1’), code, Log(2), etc.); the last value in the log shows where it died.

OS Debug Routines

For many years, Windows has included a built-in low-level function, OutputDebugString(), that outputs text messages from your application so you can monitor output locally or from a networked PC. Applications in C/C++ are most likely to call this directly. With UDP (the internet protocol used for this communication), you can build lots of calls into your release code, with little (or even no) impact on performance. Microsoft's Visual Studio handles this in its output windows, but you can also download the free Systernals DebugView, a standalone application that captures UDP output data. If you use Trace, you can also use DebugView or anything that captures output in order to capture the output of C# applications. This is enabled in code by adding the using System.Diagnostics to the logging class and then calling Trace.WriteLine() to output to it. Run the earlier sample inside Visual Studio and you'll see the two highlighted messages in the output window. But run the application (Release or Debug) outside Visual Studio, and DebugView will also capture the output.

Print Values

This is the crudest and simplest method of debugging, especially if you don't have a debugger. (Fun side note: I used this for many games developed during the 8-bit era 30 years ago.) This entails printing variables to the screen or setting screen areas to a particular color. You can still use this technique nowadays, especially with games when you want to see certain values while the display is rendering at 60 frames per second (fps). If you don't have your own text output routines defined, changing colors might be easier; I wouldn't expect to call TrueType print routines and still get 60 fps.

Breakpoints

The ability to run code until it hits a break point and then stops in the debugger cannot be overstated. It's probably the most useful way to check that code is performant. Once a breakpoint is hit, control returns to you, and the debugger lets you inspect memory, variables, as well as view XML, HTML or JSON documents. You can watch variables change as you step through your code line-by-line. Microsoft's Visual Studio Enterprise debugger even lets you step backwards so you can see what happened before the breakpoint was hit. When I write new code, I like to step through it at least once to confirm its correctness. I'll also put breakpoints on exception handlers, so in the unlikely event they trigger, I can try and figure out how the program took a wrong turn. For bonus points, try using conditional breakpoints, where there’s only a break when a condition is true (such as a flag changing state or a variable equalling a particular value). Sometimes it’s just as easy to put the condition test directly into the code instead!

Exceptions

For much of your code (i.e., C#/.NET), you don't need an exception handler. But calls to external code, such as the SQL Server Management Objects (SMO) to restore a database, can fail for a variety of reasons. You have to handle the exceptions in those cases. At one point, while developing an application that restored databases, I found that a database could become unusable on subsequent runs after the application crashed; the database was left in a state where the program just couldn't access it, and the exception offered no explanation for why. Eventually I figured out that the crash left an open connection that was blocking access, and the only way to sort that out was to detach and then reattach the database in code. I built that into the program to prevent lockouts during restore. In other words, all unhandled exceptions bubble up to the operating system level and will crash your application. You need to deal with them. With exception handlers, you can trap the exception and report it via logging.

An Errors-Free World?

No one ever develops bug-free software on the first attempt. You need to test and fix any bugs and errors. The ability to debug is an essential part of being a software developer. If you get good at it, you can even go on to create your own debugger in Visual Studio code (VSC), Microsoft’s free cross-platform IDE. I prefer Visual Studio, even the free community version, but VSC comes a very close second.