Programmers tend to loathe writing bookkeeping software. Just thinking about doing something so mundane and un-sexy as writing a double-entry bookkeeping system is probably enough to make a whole raft of programmers contemplate switching careers in earnest.
This is interesting because at heart all programming is bookkeeping. If a program works well that means that all the results come out exactly as expected, there is no room at all for even a little bit of uncertainty in the result. So if you’re going to write correct software the internals of that software will look very much like a bookkeeping program!
Let me give you a concrete example of what I mean to clarify this:
Imagine you’re building a computer system to administer a telephone switch. The switch has a whole bunch of lines attached to it, some lines are ‘trunks’, lines with which you can forward messages to other telephone switches, and some lines that connect to subscribers. Each subscriber is represented by a a wire pair and by manipulating the signals on the wire and by listening to the wires you can communicate with the phone on the other side.
Your options for the wires to the subscribers are: detect the phone going ‘off hook’, detect the phone going ‘on hook’, detecting a short across the wires that lasts < 50 ms and so on. The trunk lines are much the same as the subscriber lines, only difference is that you have a limited number of them to route calls ‘out’, and instead of being tagged with subscriber ids they are tagged with their long-distance prefixes.
A bunch of hardware will take care of abstracting all this and presents a nice and clean port-mapped interface for your phone switchboard, or maybe it appears as a chunk of memory. If you’re doing all this in the present time then you’re going to be talking some digital protocol to a primary rate interface or some chunk of hardware further upstream using TCP/IP.
No matter how it is implemented though, you’re going to be looking after incoming calls, outgoing calls, subscribers that are available to be called, subscribers that are setting up calls, subscribers with calls in progress and trunks that are engaged, free or used for dialing or in-system messaging.
Whenever a call completes starts or ends you’ll have to log that call for billing purposes and so on.
If you don’t approach this like you would approach a bookkeeping problem with checks and balances you’re going to be tearing out your hair (or it’ll be prematurely grey) about questions such as “Why do we have so many dropped calls?” and “How come the billing doesn’t match the subscribers recollection?”.
To fix this here is what you can do:
For every interaction in the system you assign a simple counter. Phone goes off-hook: counter. Phone goes on-hook: counter. Call-start: counter. Call-end: counter. Calls-in-progress: number (that’s a harder one, but it should be incremented whenever a call is set-up and decremented when a call ends), etc, etc. And then you go and define all the ways in which these numbers relate to each other. At any given point in time the number of calls set up - the number of calls disconnected should be equal to the number of calls in progress. The number of calls in progress should always be >= 0. The number of ticks billed to the user should equal the number of ticks the subscriber lines were ‘in call’ on non-local numbers and that number in turn should be equal to the number of ticks on the trunks.
You can use this information in several ways:
when you’re debugging: When you are searching for a bug the information in the counters can guide you to the location of a potential problem. The counters and variables have all these nicely defined relations and if one of the relations does not work out then it will stand out like a big red flashing light. This indicates either that your understanding of how the system works is not accurate, that the system does not work in the way it should or that there is an alternative path through the code that wasn’t properly instrumented. Either way, something needs to be repaired to get the bookkeeping to match again.
when you’re testing: you define a number of scenarios and how you think they should affect the various counters and variables. Then you set up your tests, run them and check the state of the counters and variables after every test has run to make sure you get the desired result. If you’re doing longer test runs you might want to have thousands of concurrent sessions to put the system through its paces and then you check afterwards if the aggregate of the various counters and variables accurately reflects what you did during the test run.
to give you confidence that you have indeed fully mastered the system and that there are no undefined states or chunks of un-instrumented code that have effects on your billing or internal bookkeeping. This will help with you getting enough sleep at night.
that bookeeping is so useful that I usually keep it around in persistent storage (typically, a database table) on a per-minute or per-hour basis. That way I can reach back in time if there is anything amiss and I can get within an hour of the introduction of a problem. That’s usually close enough to be able to narrow down where in the system the problem is located.
Similar techniques apply to things like memory allocation, process initiation, opening files and so on.
In most languages you can then assert that these conditions are true at any time during execution of your tests (usually with something called ‘assert’). These are called ‘invariants’ (things that should always hold true while your program runs).
In the end all programming is bookkeeping, even when at first glance it has nothing to do with bookkeeping.