As developers, we're sometimes presented with the potentially unpleasant task of returning to really old code (or worse, as consultants, visiting it for the very first time). In this article, I take a look at a few projects and discuss some of the techniques used to get up and running quickly.
Excavating Old Code
I started writing code as a student/hobbyist in 1980, and professionally in 1990. For a lot of my career, the notion of working on old code was a relative one. If I work on something else for three months and then come back to it, that's old, right? I knew there were mainframe systems out there that were older than I was, but I hadn't seen any first hand.
My professional start centered on Visual Basic 3.0, and eventually HTML, JavaScript, and Classic ASP. Web technology was new, and changing so fast back then, that the concept of old code wasn't even on our radar.
In the early 2000s, Microsoft simultaneously announced the impending demise of “Classic” VB and heralded the arrival of VB.NET and C#. I jumped on that bandwagon, luckily dodging the “old code” bullet, because at that time, there was no legacy .NET code. Now, here we are and .NET is 15 years old. There's old code everywhere you look. In fact, .NET itself is now so bogged down with legacy junk that Microsoft is effectively starting over (again) with .NET Core.
In fact, .NET itself is now so bogged down with legacy junk that Microsoft is effectively starting over (again) with .NET Core.
One Piece at a Time
There's a great Johnny Cash song about a factory worker who builds Cadillac cars. He really wants one of his own, but can't afford it, so he smuggles out one part every week for years. When it finally comes time to assemble the car, unsurprisingly, none of the parts fit together quite right.
That car was this project I'm about to exhume. Sure, it worked - mostly - but maintenance was a nightmare.
This relatively small ASP.NET Web project had been around for a few years and had been touched by A LOT of different people. You could practically see each person's distinct fingerprints, in the form of coding style and architectural choices.
By the time I inherited it, there were at least four different ways of doing CRUD operations in the code. One guy was a big believer in Stored Procedures and another preferred inline SQL. Another person wanted everything in a data layer that he wrote himself, and another preferred using Enterprise Library.
Depending on your task, you may not have time to fix everything else you find that's horribly, horribly wrong, no matter how much you may want to.
'Case In Point
If Not IsNothing(obj) = False Then
...
End If
If you're hunting down an issue, sometimes you have to break stuff (temporarily) in order to fix other stuff. If you aren't sure what something does, turn it off (comment it out) and see what breaks. If you royally screw something up and can't find your way back, there's always source control.
I'm making an assumption here, so if you don't have source control, stop what you're doing and get the project into source control NOW, or make a backup or whatever you have to do. If you're laughing, I'm assuming it's because you're already painfully aware of how often this happens in the real world.
Depending on your task, you may not have time to fix everything else you find that's horribly, horribly wrong, no matter how much you may want to.
If you're not there to fix a bug, it stands to reason that you must be trying to add something new. The existing architecture may make adding new things a painful process.
“Bolt-On Features” are often regarded as a bad, but sometimes they're an unavoidable necessity in this line of work. In a legacy application like this one (containing an architectural mish-mash), there's no single right approach aside from a total rewrite. Your best bet is to go with the style that most closely matches your own, or follow the architectural choices that you're most comfortable with. If you're lucky, this will be the newest tech, but don't count on it.
There are times when it's ok to show off your individuality and brilliance, but this probably isn't one of them. Adding more chaos to the pile could introduce unforeseen issues, make it harder for you to maintain later, and almost certainly make it harder for the next person to come along. As they say, “When in Rome, do as the Romans do.”
Digging Deep
The next project I'm going to discuss is one of my own. I've been working on a small RPG game (called “Heroic Adventure!” or “HA!” for short) since 2003, off and on. I have periodic bursts of productivity, punctuated by years of inactivity.
Every time I revisit it after a long break, I realize just how much I've learned since the last time. Unfortunately, that means I start getting distracted by all the things I want to fix or refactor, instead of focusing on the reason I'm back in the code in the first place. Unless it's your intended goal, you must avoid the temptation to refactor just because you know a better way now.
A few months ago, I came back to the code after about two years away from it. There were some known bugs I wanted to fix, and some new features I wanted to add. Unfortunately, I hadn't written production VB.NET in quite some time, having been working mostly in C#, and the fact that this project was started in VB.NET 1.1, and upgraded to 2.0 (years ago) didn't help.
Unless it's your intended goal, you must avoid the temptation to refactor just because you know a better way now.
Half of the things I tried didn't exist in .NET 2.0, so I had to upgrade the project to 4.x. Fortunately, it didn't break anything (nor should it have), but that can and does happen.
I also spent a lot of time stepping through code in an effort to remember how it worked, or why I wrote it that way. A lot of people don't realize that F5 and Ctrl-F5 (debug and run without debugging, respectively) are not your only options for starting your code. You can also use F10 and F11 (step over and step into, respectively) which are especially handy if you aren't sure where exactly where in the codebase your application begins.
One thing I don't recommend is excessive use of the `<DebuggerStepThrough()>`` attribute. It's OK for relatively short methods that you have to step through often, but there are few things more frustrating than stepping through code you barely remember (or have never seen) and bouncing off of one of these. You'll likely find yourself wanting to rip out every instance you find. If not, anyone who comes after you almost certainly will.
'Case In Point
'This is ok:
<DebuggerStepThrough()>
Function SimpleMethod() As Integer
Return RND.Next(1, cap)
End Function
'This is not ok:
<DebuggerStepThrough()>
Function ComplexMethod() As Integer
' ...
' 380 lines of complex code
' ...
End Function
If you aren't familiar with it, this attribute essentially tells the debugger “everything is fine here, nothing to see, move along” and as a result, even if you're stepping into all of your methods line by line with F11, this behaves as though you hit F10 and simply executes the code without stepping into it.
You Want Me to Do What?
In this last example, I'd acquired responsibility for a collection of Microsoft Access 2010 forms applications. Like many Access apps, in addition to being really old code, they were initially written by a non-developer, passed through a few hands, and then into mine. To make matters worse, parts of the code were written in Albanian, which was definitely not listed on my resume. I also hadn't even installed Access on my developer computer in probably 10 years or more.
When I opened the first one, I had no idea what the app was supposed to do, how it worked, or what any of the numerous buttons did. So my first step was to insert breakpoints on every single method that served as an event handler and start clicking buttons so I could map everything out.
After I had a pretty good idea of what the buttons mapped to, the next step was to really dig into the meat of the code. In this example, I wasn't there to add new features to any of the apps, but simply to get them working in a new environment. It turns out, that while digging through the code, I discovered there was a lot of stuff that never worked in the first place (buttons that led to nowhere, lack of null-checking on various controls, incomplete methods, etc.).
I made a list of everything I found: environmental stuff (like hardcoded user IDs and network paths), broken stuff, unfinished or orphaned stuff, performance issues, etc. Once the list was done, the team prioritized what to address first, what to research further, and what to defer “indefinitely.”
My first step was to insert breakpoints on every single method that served as an event handler and start clicking buttons so I could map everything out.
While exploring, I kept running into features that were restricted by user ID. Each of these features (there were many) had a hard-coded list of IDs in an If statement that wrapped all the code in the function. Instead of adding my ID to each list, which would have been time consuming and temporary, I put a breakpoint on each If statement and then dragged the execution point into the “True” branch, bypassing the security and allowing me to continue on my way.
'Case In Point
Private Sub cmdDoStuff_Click()
If (ID = "uuuuu"
Or ID = "vvvvv"
Or ID = "wwwww"
Or ID = "yyyyy"
Or ID = "zzzzz") Then
DoCmd.OpenForm "frmDoStuff", 0
Else
MsgBox "Access denied."
End If
End Sub
Incidentally, Google Translate did a pretty decent job on the Albanian (except for the ones that were further subjected to Hungarian Notation), but it still took me a bit to wrap my head around some of the variable and function names.
Wrapping Up
Hopefully, you've found some useful tips in this article and you can make use of some or all of them the next time you have to dive into some old or unfamiliar code, or better yet, may you never get stuck working on someone else's old code. Good luck.