There is a decades-old programmer’s joke that goes:
There are two hard things in computer programming: cache invalidation, naming things, and off-by-one errors.
This week the database code for our new accounting software finally started to work properly for very large files. It took almost a month, or twice as long as expected. Along the way we faced both of those three types of problems.
A cache is data in temporary storage, so it’s quicker to access. Invalidation is just programmer talk for keeping the cache synchronized with the original data. Basically, the cache determines how long we keep accounting records stored in RAM, and when we save back to disk.
Our staff wrote the caching system for Goldenseal Pro almost 2 years ago. It worked great with small files. Then we started testing with our own company file, which is big enough to require six ‘sector managers’ to manage record locations. Anything put into the extras would eventually give errors. After dozens of debugger runs, it narrowed down to the cache system. Changes weren’t being saved.
Meanwhile, naming things is a chronic issue for all computer programmers. Poor naming is the main reason why code becomes impossible-to-understand ‘black boxes’. Good code tells a story, and explains exactly what it’s doing at every step. Getting it that way takes time and effort.
It wasn’t obvious why the cache system wasn’t saving sectors. Stepping through it, everything seemed OK. After a couple frustrating days of debugging, our staff rewrote the whole thing. Same basic approach and almost identical code, but with names that made more sense. Renaming everything was a good way to see how the cache interacted with other parts of the software. Finally, it became clear that sectors were indexed differently from everything else. After changing one line of code, it worked fine again. One minute to fix it, and three days to find it.
Then there are the off by one errors, sometimes called ‘off by on’. For anything involving numbers, it’s easy to get close but no cigar. It doesn’t help that there are math quirks. For example, the end of a record is at start+length-1, not start+length. It’s easy to forget that. Even worse, libraries written by true programmer nerds start counting at zero, while those written by mortals start at one. NeoAccess considered the file end to be the last byte of data, while C++ libraries put ‘end of file’ at one after the last byte. Etcetera.
Earlier this week, our TurtleSoft file was converting OK, but the file size was over 43 gigabytes. Obviously, a gap-finding problem. It took most of a day to track it down to code that called ResetToEnd on a list, thinking that it selected the last item. Nope, it was one past the last item. That name makes sense if you want to add something at the end, but not if you want to access the item at the end. Off by one.
Adding a ResetToLastItem function solved the problem, and file size dropped instantly by 800x.
Anyhow, we’re back to interface programming.