The law of small numbers is the tendency to observe patterns in small sets of data that doesn't hold as the set gets larger. This can result in hasty generalizations early on, often stunting the progress of whatever the work is in the long run as one discovers that they made a hasty generalization and has to discard their work.
Most programmers should recognize this effect and draw parallels to their own field, specifically with abstractions. I'll use the word abstractions here to mean structures that are devised in code to make the advent of programming easier by reducing the amount of code it takes to do a set of things. Functions are one such construct.
What I will call the law of small programs is the tendency to devise faulty abstractions in the early onset of a project. This is dangerous to do because code is notoriously difficult to change once it has been placed, and an abstraction can achieve the opposite effect of what it was intended to do if it is faulty in its nature. I find that it is very tempting to make your abstractions in the early stages of the project. I tell myself "If I just set this up right now and make it as generic as possible, I'll get this project done in record time." More often than not, the abstraction fails spectactularly or I didn't make as much use of it as I intended. This also has a demoralizing effect for me which leads me to put another project into the DNF pile.
I think I have learned my lesson now and am refraining from abstractions in small projects. Specifically the grander abstractions that dictate the architecture of what I'm writing. It would be insane to not use functions for instance. But for the larger ones, I will wait until my project is in the order of two or three thousand lines of code before taking the time to inspect it and see which parts can be clumped together.
Secondly, I make use of partial abstractions1. In my recent advents in C, for instance, there were times I was tempted to make the most generic Result
type and a pattern of programming that allows for smooth error propagation upwards the call stack like those found in Rust or Zig or any such programming language that touts its error handling mechanisms, and the use of macros to facilitate this all, but I decided that I will only have one or two concrete result types and to use if statements on the tags of these tagged unions to determine what to do next. I can live with this abstraction because it deduplicates just enough code, but it doesn't enforce anything restrictive on the structure of code itself. If it so happens that I must rewrite a big part of my code and it involves changing how errors are handled, or I have to make the change keeping in mind the constraints of my abstraction, this structure wouldn't inhibit me from doing so nicely.
After applying it, I have seen the benefits of this approach in my own programming in a number of ways. Firstly, I have become most productive. Thinking of the right abstraction is hard, and more often than not, I am wrong with whatever I come up with. Simply programming the problem and waiting for the right abstraction to manifest itself is the right way to do things. Secondly I have stopped caring about the right abstraction. Writing code in this manner has been a sort of exposure therapy to me. I had tendencies to perfect the abstractions before programming the problem that prevented me from getting anything done beforehand. Now I revel in the fact that I'm programming without distracting myself with those endeavors.
I don't mean to villianize abstractions in writing this post. I find them quite interesting actually. There is something abotu getting at the nature of a program and distilling it to its simplest most generic components and working on those components that is beautiful to me. One of the things in my to-do list is to learn Haskell proper and see what all the Applicative Monad yap is all about. The intent of this post was more about knowing to wait until the right abstraction presents itself than to guess it and enforce it when it may not be appropriate.
Footnotes:
I was tempted to call them imperfect abstractions, but decided to call them partial abstractions because they are distinct in that they don't solve problems in the complete domain that they operate in.