The best software programmers are the most paranoid.
This thought struck in my head, as I was recently debugging some legacy .NET code.
My dev manager recently asked me to start investigating why a particular piece of functionality inside of a .NET web application was not working. It was a legacy application that grew slowly over time, by many different programmers over a number of years, and I was the latest developer to have “inherited” the application.
So I wasn’t responsible for its inception, nor was part of the original team of developers who first created it.
This is pretty typical for most software developers, actually. It’s actually kind of rare that a developer can begin fresh with a “greenfield” project … that is, a brand new software application with no legacy codebase to have to worry about.
On a greenfield project, a software developer has the luxury of being involved in all aspects of the application design … the way it’s supposed to look to the end user, all the software components and libraries that go into the application, how the general architecture of the application should be designed, etc.
It’s why greenfield projects are so coveted by developers in general … when given a choice, I’m pretty sure most developers would prefer a fresh greenfield project over a “brownfield” project, where the code is already written and designed by previous developers, and you are responsible for maintaining, enhancing and/or fixing bugs.
But greenfield projects are generally few and far between, and I started to investigate what the problem was.
At a high level, the software component I was responsible for investigating, was supposed to make an AJAX type HTTP request to a remote server and return some data back from the HTTP call.
I used the built in debugger tools inside the Chrome web browser, to examine all the HTTP network traffic going on, as I began troubleshooting the source of the problem.
The network traffic diagnostic tool inside of Chrome confirmed that the AJAX request to the remote server was failing with an HTTP server 500 failure.
Further analysis helped me pinpoint the exact section of the application where it was failing, in a .NET method definition called “SendFranchiseActivation”.
Original code
[HttpPost] [Route(“SendFranchiseActivation”)]public HttpResponseMessage SendFranchiseActivation([FromBody] SendFranchiseActivationParameters parameters)
{
var results = new SaveDealerDetailsXmlResults();
var DmsUrl = ConfigurationManager.AppSettings[“DmsUrl”];
var url = String.Format(DmsUrl, parameters.ExternalIP);
results.GDIActivations = this.SendXmlToDMS(parameters.XML, url);
var json = JsonConvert.SerializeObject(results);
var response = this.Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(json, Encoding.UTF8, “application/json”);
return response;
}
As you can see from the method definition, there wasn’t that much code to deal with.
The variable ‘results’ would hold the raw XML data payload response from the AJAX call to the remote server.
var results = new SaveDealerDetailsXmlResults();
The variable ‘url’, would be the HTTP internet address to the remote server, which would be read from a configuration text file.
The next .NET statement,
results.GDIActivations = this.SendXmlToDMS(parameters.XML, url);
would be the actual AJAX http call to the remote server. The variable ‘parameters.XML’ would contain the actual XML request needed for the AJAX call, and the url would be the http internet address of the remote server.
The next statement,
var json = JsonConvert.SerializeObject(results);
would convert the raw XML data coming from the remote server into JSON, short for “Javascript Object Notation”, a more popular and lightweight data format that is increasingly becoming the new data standard for internet based applications.
Finally, the last statement,
return response;
returns the JSON data response back to the code which called this method.
It’s pretty typical code that needs to make an HTTP request to a remote server.
The crux of the problem was that even though I was able to verify that the “SendFranchiseActivation” method definition was the source of the error, there wasn’t enough error handling within the method definition to tell me exactly which line of code was throwing the HTTP 500 errors I was seeing in my Chrome debugger tool.
Until you know exactly WHERE in a software application an error is happening, there is absolutely no chance you can even begin figuring out HOW to fix the problem.
I’m only making an educated guess here, but I think most programmers, myself included, make the incorrect assumption that software code always runs on a HAPPY PATH.
A happy path is exactly like it sounds … it’s the assumption that things will always run perfectly all the time.
Of course, we all know that isn’t true at all. Murphy’s Law dictates that whatever CAN go wrong, WILL eventually go wrong.
Unfortunately, we programmers often are just too forgiving on our own code. Maybe it’s an ego thing, maybe it’s the uncomfortable fact that we’re all fallible and we make a lot of mistakes, or a combination of all these things.
But it could be the reason why we often neglect to put enough error handling in our code.
When we neglect to make our code resilient to errors, it makes debugging it more difficult than it should be.
That’s what error handling essentially does … it doesn’t ELIMINATE errors in our code. But it helps make your code smarter about how to deal with things that unexpectedly go wrong.
As I went refactoring and changing the SendFranchiseActivation method definition, I identified the major things that could potentially go wrong in the method.
When I mean go wrong, I mean potential failure points.
But isn’t EVERYTHING that you write in source code a potential failure point?
Very true. But we’re trying to identify the MAJOR failure points.
For instance, in this line of code:
DmsUrl = ConfigurationManager.AppSettings[“DmsUrl”];
We are trying to read the http internet address of the remote server we are trying to connect to, from an external text file called web.config.
This is a potential failure point.
For instance, what if the web.config text file was missing? The code would try to read a nonexistent text file and fail miserably.
When I say fail miserably, I mean at BEST, when it fails, would generate a very generic error message.
Which is exactly what my chrome debugger development tool was telling me when I first began investigating the problem.
In layman’s terms, the debugger was displaying a general “an error occurred” type error message. Nowhere did the debugger tell me what the specific error was, or where it happened or even why it happened.
Unless you’re a mind reader, it makes it virtually IMPOSSIBLE to figure out what happened, let alone HOW to fix the error.
In .NET parlance, you can wrap potential code inside a try-catch block, as a way to implement error handling.
Anything within the try { } curly braces is what you are attempting to execute. If anything fails within the try block, code execution immediately falls into the catch { } block of curly braces, indicating something went wrong.
This is where you can deal with the error that was thrown. Usually, you want to do things like log the specific error message, and any other relevant information that might be helpful in debugging.
I then THROW my own special kind of error called an “exception”. It includes all the extra helpful information I want to include and see when this kind of error happens.
After the exception is thrown, no other code continues to execute. Essentially everything stops at that point.
Now when I rerun the application, when it hits my try-catch block of code and encounters a failure, my browser debugger will see a much more useful error message.
It tells me where the error happened and the exact error message that caused the error. I know exactly that an error occurred when the method tries to read the HTTP address of the remote server from the external configuration file.
This is how you make code more robust and resilient to errors.
Getting into the habit of being “paranoid” about your code and thinking about where it could potentially fail is a good habit to get into. It’s often referred to as “defensive programming” much in the same way that a “defensive driving” mentality helps make a driver avoid accidents by constantly thinking about bad things that can happen while driving.
The refactored method basically continues the same try-catch concept around any lines of code where I think something could go wrong.
Attempting to send the XML request to the remote server is another major and obvious failure point.
The remote server might be unavailable. Or the network connection between the remote server and the SendFranchiseActivation endpoint might have problems.
So we wrap a try-catch handler around that as well.
Further down in the code, we wrap another try-catch handler where the code attempts to process the data results from the remote server. Perhaps the raw results came back in an incorrect data format.
Refactored code
[HttpPost] [Route(“SendFranchiseActivation”)]public HttpResponseMessage SendFranchiseActivation([FromBody] SendFranchiseActivationParameters parameters)
{
var results = new SaveDealerDetailsXmlResults();
var DmsUrl = string.Empty;
var url = string.Empty;
var json = string.Empty;
HttpResponseMessage response = null;
try
{
DmsUrl = ConfigurationManager.AppSettings[“DmsUrl”];
url = String.Format(DmsUrl, parameters.ExternalIP);
}
catch (Exception configException)
{
var message = String.Format(“There was an error attempting to build the DmsUrl value: {0}”, configException.Message);
var errorResponse = Request.CreateErrorResponse(HttpStatusCode.BadRequest, message);
throw new HttpResponseException(errorResponse);
}
try
{
results.GDIActivations = this.SendXmlToDMS(parameters.XML, url);
}
catch (Exception dmsException)
{
var message = String.Format(“There was an error attempting to send the dealer activation document to the DMS: {0}”, dmsException.Message);
var errorResponse = Request.CreateErrorResponse(HttpStatusCode.BadRequest, message);
throw new HttpResponseException(errorResponse);
}
try
{
json = JsonConvert.SerializeObject(results);
response = this.Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(json, Encoding.UTF8, “application/json”);
}
catch (Exception responseException)
{
var message = String.Format(“There was an error attempting to serialize the DMS dealer activation response: {0}”, responseException.Message);
var errorResponse = Request.CreateErrorResponse(HttpStatusCode.InternalServerError, message);
throw new HttpResponseException(errorResponse);
}
return response;
}
The final refactored method might be bigger, but the larger size of the code is worth the cost, as it helps make the application much more easier to troubleshoot and debug for the next software developer who needs to take over and maintain the application.
Error handling is extremely important. As a software developer, you need to hone your personality to MISTRUST code. Avoid thinking that everything runs on a happy path.
The happy path programmer may feel confident that nothing
can go wrong ….
go wrong …
go wrong …
go wrong …
**** 500 internal server error — good luck debugging ****