For many coding best practices you read about you will see exceptions like “for small programs it’s ok to not follow this”. Using global variables or untyped/dynamically typed languages are two of the most commonly mentioned exceptions. But allowing exceptions for small programs opens the question when is a program “small” and what happens when it grows? Even more, I think that saying “it’s ok for small programs” distracts from the real question:
“Why would you want an exception?”
An exception makes sense if having it means less work. Let’s apply this test to the two examples above.
Writing code without use of direct access to global variables is the same amount of work as writing complete functions. So there really is no justification for an exception here.
package di
import (
"flag"
"fmt"
)
var (
// In Golang, this is the closest we come to a global variable.
SomeFlag = flag.String("string_flag", "", "A flag")
)
func notLikeThis() {
fmt.Printf("I'm using the global flag directly. Here it is: %s", *SomeFlag)
}
func main() {
notLikeThis()
}
The code above is no different from the one below in terms of work to write it, but the second version is much more flexible.
package di
import (
"flag"
"fmt"
)
var (
// In Golang, this is the closest we come to a global variable.
SomeFlag = flag.String("string_flag", "", "A flag")
)
func butLikeThis(msg string) {
fmt.Printf("I'm data properly. Here it is: %s", msg)
}
func controller() {
butLikeThis(*SomeFlag)
}
The second example, using untyped languages, which usually means scripting languages that you can run without out compile and build steps, is less clear cut. The problem is that programs grow. Eventually any small program will become a medium or large program. So what to do when that happens? Will you rewrite your entire program in a different language? Will you rewrite parts? Will you keep it all?
In practice, rewriting never happens. Especially in environments where it’s not you, the developer, but some manager who makes that decision. And even if you are rewriting, you wasted all the previous work, because you are completely replacing it. And because it still is a simple program, the learning effect from implementing it twice is very small.
There is an important trade-off you have to make at the start of development. You have to balance the time you save by using an untyped language with the risk posed by the potential growth of the program. That risk comes in two flavors. First, there is a very small chance that your program will stay small forever. In that case you win. There is a big chance that your program will keep growing. At that point you are faced with the choice of rewriting what you already have, or keeping the untyped language. If you make the decision to rewrite early enough, you lose only a little. If you decide to stick with the untyped language, you enter purgatory. The best way to avoid this is to start with a typed, compiled language. Especially when you will not be in control of the decision whether to rewrite, but can influence the initial choice of language. If you check the required reading below, you will find that this opinion is strongly at odds with some of the literature. But I stand by my experience.
These two examples illustrate the questions you should ask yourself before employing any exception to best practices:
- What will it save me now?
- What will it cost me later, and how likely is that cost to happen?
The answer to both of these questions will change with experience and practice. Some of the coding practices I discuss in this blog take time to learn and turn into a habit. During that time, they have a non-zero cost to you. So you might feel you save something by not using them. The same goes for coding practices that have a real up-front cost.
Long term costs require even more, and longer term experience to assess somewhat correctly. You have to experience the exponential difficulty of keeping an untyped program stable and reliable. It is hard to reason about logically, but if you experienced the agonizing death of a project because of it, you will understand much better why this is a real risk. The flip side of the coin is that you also have to experience project success where everything seems to be going smoothly, and then take a retrospective look at the techniques you employed in that project compared to one that didn’t go as well. When doing this, don’t forget that it’s not all about programming techniques, the people doing the programming are probably even more important.
If you are unsure which way to lean, remember this:
Best practices are techniques that strike the right balance for the long run.
Required Reading
- Martin, Robert C. 2008. Clean Code: A Handbook of Agile Software Craftsmanship. 1st edition. Upper Saddle River, NJ: Pearson.
- Thomas, David, and Andrew Hunt. 2019. The Pragmatic Programmer: Your Journey To Mastery, 20th Anniversary Edition. 2nd edition. Boston: Addison-Wesley Professional.