The Ayn Rand book “The Virtue of Selfishness”[1] has a title that intentionally juxtaposes a negative concept with a positive one for the shock value it implies. And it works: right off the bat, the reader is interested to see how the author can pull off this miraculous combination of polar opposites. In the same manner, many programmers feel you can safely replace the word selfishness with laziness, giving us the “Virtue of Laziness”. Should that have been the title of this book? We will see in the end; however, it is safe to assume the title of this chapter is indeed, “The Virtue(s) of Laziness” as we will discuss the positive aspects of a “labor-saving” mindset. Let’s walk through the most common ways that “Lazy programming” can be considered “good”:
1. Lazy can mean “efficient”.
Specifically, efficient in the long run. If a programmer performs a task repeatedly, then that programmer can save time “in the long run” by automating that task. If your version of “laziness” is to exploit every opportunity for automation then that is a good thing. Whether that should be called laziness or innovative is debatable; however, for now we can put that debate aside and focus on how this is accomplished. An obvious example of this is when you find yourself repeating the same terminal commands over and over. Programmer’s save time by not repeating themselves in two ways: one via a simple command line shortcut called “aliasing”. Many shell languages like Bash allow you to create a short alias for longer commands like this:
$ alias rm=’rm -i’
$ alias ll = ‘ls -al --color’
$ alias untar='tar -zxvf '
As you can see, an alias is an abbreviation for a longer command that saves the developer typing. You may be thinking, “Do we really count saving a few characters in a command as ‘labor-saving’?” The answer is twofold: first, common commands will be used thousands and tens of thousands of times so all those saved keystrokes add up. Secondly, some aliases can be for longer commands and even multiple commands where the output of one is piped into the input of another like this:
$ alias psmem10='ps auxf | sort -nr -k 4 | head -10'
The above command would give you a list of the top 10 processes consuming memory on a linux system. The first command is ps which provides information on processes (running programs) and that output is piped into the sort command which is sorted based on the 4th column of output and that output is then piped to the head command which outputs the first n lines of input (in this case 10).
Now, let’s move on to a better form of automation than mere abbreviations – automating repetitive tasks with a shell script.
Like most software developers, most of my day is consumed with our development cycle as depicted in
Figure 1.
Figure 1 A Development Cycle
We do this over and over (sometimes not ‘pushing’ up the results to the feature branch until we have coded, tested and fixed the code multiple times). Regardless, of the exact sequence of steps, we do these things over and over so we again have an opportunity to save some labor by automating repetitive tasks.
I have scripts and aliases that help me through each of these phases. One of the most time consuming phases is patching the test instances to test a new change. Simply stated patching involves packaging up all my java archive files (jars) into a tar file, secure copying it over to a virtual machine based test server and unpacking it there. The pseudocode for the script is as follows:
cd /<parent-dir>
tar -cvzf mytar.tar.gz <src-dir>
scp mytar.tar.gz <remote-machine>:/<path>
cd /<deploy-dir>
tar -xvf mytar.tar.gz
rm mytar.tar.gz
Of course, the above commands are condensed and simplified from the actual script but you get the idea how a script file (a set of commands grouped into a single executable file), can save you a significant amount of time.
So, efficiencies can be achieved in the long-run by a bit of extra effort up front. This sort of “strategic laziness” (lose the battle but win the war) is a good thing. Another variant on this labor-saving theme is to find an easy way to do something hard.
2. Lazy can mean “creative”.
The popular quote, “I choose a lazy person to do a hard job. Because a lazy person will find an easy way to do it”, imbues the lazy person with a level of craftiness and creativity to find a “better way” than those industrious little ants that just “follow orders”. While we could take this to mean the occasional flash of inspiration that saves millions of dollars in wasteful effort on the production line; instead, let’s look at more common ways that programmer’s find easier ways to do hard things. In my daily software development, I routinely use these techniques to make things easier: stack overflow, open source, and devops. Let’s delve into each of these.
The website “stackoverflow”[2] is a Q&A site for programmers. It is often the number one search result returned by search engines for programming queries. At the time of this writing it’s front page claims it has answers to 16.5 million questions. Questions are answered by a community of volunteers (similar to other crowd sourced sites like Wikipedia) that share their knowledge. Of course, a broader technique for this type of search is “googling” or using search engines to find an answer to a question. It is a good first instinct to turn to your favorite search engine if you encounter an unknown problem in your work. In fact, many times if you receive an error message from any part of your system, you can cut and paste the exact error message into your browser and often see numerous responses from others who encountered the exact same error! Beyond using the search engine to answer problems of a small scope, another “lazy programmer” technique is required to tackle big problems like designing a new system, a new architecture, or major portions of a large system. This technique can be euphemistically called “OPC” or “Other People’s Code”[3]. On the big data project that I support, we use hundreds (if not thousands) of open-source libraries and frameworks. Reuse of open-source software is a valuable technique to effectively speed up development of major systems. We use the React framework for our user interfaces, the Hadoop framework for parallel processing and many smaller libraries for custom visualization, caching, plotting, JSON processing, and much, much more.
Finally, on the creative front, modern IDEs and devops techniques (like virtual machines, puppet configuration and docker containers) are yet more evidence of labor-saving tools and innovations that have made the practice of programming more efficient.
3. Lazy coding techniques are part of the programmer’s everyday vernacular.
Several common techniques have the word “lazy” in the expression, to include, “Lazy Instantiation”, “Lazy Initialization” and “Lazy Evaluation”. All of these techniques are related because lazy instantiation is a form of lazy initialization which is a form of lazy evaluation. Lazy instantiation is delaying the creation of an object until it is needed. An example of this is when filling an Array as depicted in Listing 1.
package us.daconta.lazy.programmers.examples;
import java.awt.image.BufferedImage;
public class LazyInstantiation {
private static final int MAX_IMAGES=1000;
private BufferedImage imageArray[];
public BufferedImage getImage(int index) {
if (imageArray == null) {
// lazy instantiation
imageArray = new BufferedImage[MAX_IMAGES];
}
if (index >= 0 && index < MAX_IMAGES) {
if (imageArray[index] == null) {
// lazy initialize the image here...
}
return imageArray[index];
} else {
return null;
}
}
public static void main(String args[]) {
LazyInstantiation lazy = new LazyInstantiation();
BufferedImage myImage = lazy.getImage(100);
}
}
In Listing 1 we see an example, where instead of creating the array of images as soon as the class is created (with the call of the no-arg constructor in the main method), we defer the creation of the imageArray until a user requests an image via the getImage method. Of course, the use of the term “Lazy” for these techniques is debatable. Should these really be called “lazy” when the term “deferred” works just as well? Of course not, however; many programmers delight in being snarky, clever and even counter-culture. It is a worn-out trope of Hollywood’s incarnation of a hacker to be so smart that you don’t need to follow any rules or any social norms for dress or behavior. Like Humpty Dumpty in the book Alice in Wonderland, programmers will sometimes use a word, term or expression sarcastically or obtusely in order to keep management off balance. Implying that “Lazy is Good” is part and parcel of a “you don’t understand us” or “technical priesthood” mindset. As you can surmise, I believe the profession has no need for such childish cruft on the image of our profession.
Similar to these explicitly “lazy” techniques, there are many similar efficiency techniques that are commonly used. Two popular ones are the D.R.Y. principle and caching. The “DRY principle” stands for the labor saving mantra, “Don’t Repeat Yourself” and was coined by Andy Hunt and Dave Thomas in their book, The Pragmatic Programmer. The principle is stated like this: “Every piece of knowledge must have a single, unambiguous, authoritative representation within the system.” A simple way in which I have seen this principle violated over the years (and admittedly, have even violated myself) is to “cut-and-paste” code from one class into another, sometimes in multiple places in the code. This is the worst form of replication because it ignores the danger of pasting something into a new context for the sake of minor expediency. So, here we have an interesting contrast: an example of what you would consider a good lazy habit (DRY) alongside an example of a “bad” lazy habit (cut-and-paste). The funny thing is that both could be and have been labeled as “lazy habits” just that one has the positive connotation of lazy and one has the negative connotation of lazy.
In the same vein as “lazy initialization” is the technique of caching unchanged data. Caching is an efficiency technique of saving a potentially expensive, and more importantly unnecessary, data fetch operation. To do this, after data is fetched the first time it is stored in a memory cache. Then, upon a subsequent request, the data is retrieved from memory and not refreshed across the network (thus saving the expensive network operations of re-fetching the data from an external computer). We see this every day in modern web browsers that cache web pages after retrieving them.
These examples of “good” lazy techniques are the primary cause of this debate. They provide the fuel for the “pro-lazy” crowd to argue that “laziness is good” (which flies in the face of common sense). So, now let’s begin to look at the other side of the equation.