Several principles have held true across all that time and all those languages. I have spent most of my testing and debugging time dealing with these same principles over and over (and over and over and…). If you find yourself stuck on a stubborn bug, going back to these basics may help un-stick you. Good luck!
You have to REALLY know your data.
Since my first class on binary data storage, I’ve been asking “How do you know for sure what is in that field?” In the reel-to-reel 9-track tape days (stop laughing!), I would threaten to unreel the tape and hold it up to the light to see the data. That didn’t work too well. Instead, there were DISPLAY statements in COBOL, and a neat little utility called “Hexprint” that would print a variable in hexadecimal notation.
These days, I use the browser’s Inspect feature and console logs, or alert boxes, or the step debugger of an IDE. But, no matter what, I’m still trying to know exactly what the computer is seeing as it executes each piece of code.
You also have to know your data Type.
I once spent a couple of days writing a “de-edit” routine that would strip the $-sign, commas, decimal point, and minus-sign from a text field. It went character by character, from right to left, moving only the numeric characters to another field. Then it divided the resulting number to put the decimal into the right position, and multiplied by minus-one if the number needed to be negative. All of that was done just to be able to use the original number in an arithmetic calculation.
Thankfully, languages usually deal with numeric-masking characters automatically these days. But it still does not get satisfactory results to try to divide “hello” by “world”, or move an array to a text field. If a function is expecting a specific type of data but receives another, it’s time for more debugging.
C = A + B” to “
newRecordID = results._doc._id“, I haven’t met a language where you didn’t have to be consistent in how the variable names are referenced. Some are case-sensitive and others are not. But they are all spelling-sensitive.
In COBOL, after struggling with a bug for hours and finally asking a co-worker for help, the answer was likely to be “The period is missing”. At the time I started, COBOL had no
End-If statement, so the period was the only thing telling the compiler when the condition stopped.
Now, after struggling and finally asking for help, the answer will be “You used single-quotes, this requires double quotes” or “You’re missing a closing bracket” or “You have square brackets instead of curly braces”. If I had a dollar for each hour I’ve lost over a quote or a semi-colon, I would be too wealthy to be writing blog posts!
Date math is nasty.
In the old days, dates were most often stored
MMDDYY in a numeric field. The smarter ones were
YYMMDD so a numeric comparison of “which is higher” would come out correctly. Almost none were smart enough to be
YYYYMMDD, hence the “Y2K” problem (look it up) that made me miss the New Year’s Eve party on December 31st, 1999. Calculating “30 days ago” required coding around the number of days per month and watching whether you crossed between years.
Dates are much smarter now. But still, to get the exact syntax for “30 days ago” requires nesting the right formatting and date-math functions for the language. It usually takes me several minutes of fidgeting before I get it right.
Everybody makes choices.
There’s always some way to do a conditional statement, but it is rarely identical from language to language. Almost every language at least has
if. There’s probably a matching
endIf, with a variety of options for case and hyphenation. Speaking of case, how does the language of the day handle
switch statements? I’ll bet it has some version of them.
In COBOL, it was “
PERFORM PARAGRAPH-ONE THRU PARAGRAPH-ONE-EXIT VARYING INDEX FROM 1 BY 1 UNTIL INDEX > 10“. Now it’s more likely “
for (i = 0; i < 10; i++)“. There may be a
do while or
do until or
map. In any case, there’s something to repeat execution of a set of code until a condition is reached, or every record in the set is processed. (Remember to watch out for whether the condition is tested at the beginning vs. the end of the loop.)
One of the supposed “evils” of COBOL was its
GOTO statement, that could be misused to cause branching that was very difficult to follow. But my main use of
GOTO was to leave a loop or a paragraph by doing a
GOTO PARAGRAPH-EXIT. Other languages don’t often use
GOTO. But they use
return to accomplish the same purpose. There is always a way to leave a section of code without just falling through to the end.
Finally, logic always matters.
My favorite description of programming to a non-programmer is that it’s like writing a recipe for a really dumb cook: If you say “Add two eggs to the cake batter” but forget to mention “Break the shell and add only the inside contents to the batter”, you end up with a crunchy cake. No matter how sophisticated the language, it is still very literally following instructions. If I am not getting the results I want, 99.99% of the time it is because the computer is doing exactly what I said regardless of what I meant!