Chapter 3 The Bad
I have personally managed a proud “lazy programmer” for seven years. During that time, I have seen the gamut of lazy behavior – yes, the good, the bad and the ugly. Let us now turn our attention to the dark side of laziness. That which actually conforms to its formal definition. There are three categories of “bad laziness” to watch out for: Shortcuts and Band-Aids; Sweeping dirt under the rug; and Avoiding architecture. Let’s examine each of those categories in detail:
1. Shortcuts and Band-Aids.
A shortcut is a faster way to achieve a result that most often favors expediency over robustness. Sometimes, a shortcut may even involve skipping steps to finish sooner. Shortcuts are, by far, the most common characteristic of avowed lazy programmers. They exalt in the time-saved by their purported cleverness. Here are several specific examples of shortcuts in programming:
a. Brute Force Programming.
This is a general technique of using the computer’s brute speed to cover up a poor algorithm choice or poor implementation choice. In the system I am working on, I see this often in the area of custom user interface visualization updates where there is an attitude of “if in doubt, refresh the user interface.”
This “refresh always” mentality causes numerous performance drags due to the programmer not willing to take the time to determine whether the interface actually changed but instead just “playing it safe” and refreshing it whether it is needed on not. In our system’s case a “refresh” is not free and at times can retrigger a data fetch (very expensive for big data). This is the polar opposite of the “lazy instantiation” or “lazy initialization” technique. The most common examples of brute force programming is in sorting and search. Given that it is an interesting contrast to know that Donald Knuth’s Volume 3 of The Art of Computer Programming is “Sorting and Searching”.
Figure 2 Knuth Vol 3
Listing 2 shows the notoriously inefficient “bubble sort” as the canonical example of a brute force algorithm. The hallmark of a brute force technique is that it is the most “straight forward” way of solving the problem and that is where laziness enters the picture. The programmer could not be bothered to consider whether a better approach (in terms of efficiency and effectiveness) was feasible. Now, if this was driven by management schedule pressure, the blame falls with management but many times that is not the case with a “Lazy Programmer” as their laziness overrides the need for a good solution. The attitude that “any solution will do” is often the wrong attitude! On our “big data” system we don’t have the luxury of ever assuming a bounded N samples of anything, thus the obvious solution is almost always the wrong one!
package us.daconta.lazy.programmers.examples;
/**
* A Lazy Sort
* @author mdaconta
*/
public class BubbleSorter
{
static void swap(int [] numArray, int a, int b)
{
int tmp = numArray[a];
numArray[a] = numArray[b];
numArray[b] = tmp;
}
static void bubbleSort(int[] iArray)
{
int n = iArray.length;
int tmp = 0;
for (int i=0; i < n; i++)
{
for (int j=1; j < (n-i); j++)
{
if (iArray[j-1] > iArray[j])
{
swap(iArray, j-1, j);
}
}
}
}
public static void main(String [] args)
{
try
{
int [] testArray = { 20, 2, 88, 44, 33, 51, 99, 4 };
bubbleSort(testArray);
for (int i=0; i < testArray.length; i++)
{
System.out.println("testArray[" + i + "] is: " + testArray[i]);
}
} catch (Throwable t)
{
t.printStackTrace();
}
}
}
Output of the Program is:
testArray[0] is: 2
testArray[1] is: 4
testArray[2] is: 20
testArray[3] is: 33
testArray[4] is: 44
testArray[5] is: 51
testArray[6] is: 88
testArray[7] is: 99
It is important to understand three things here in this criticism of the bubble sort: first, I do not include junior programmers in this criticism as it is expected they would choose the most obvious path (that responsibility thus falls on their team lead to instruct them properly); secondly, it is easy to understand the tantalizing allure of the bubble sort as it is very intuitive. At each step of the loop, you examine the two numbers in front of you and swap the bigger of the two. For small values of N, this is perfectly fine; however, in the age of big data you should never assume small values of N. Finally, using the built-in Arrays.sort() method provides a faster implementation (via the Quicksort algorithm).
b. Lazy Resource Utilization .
Similar to “Brute-Force” laziness is a “Resources are free” mindset. By resources, I mean the key computer resources that your program can consume like CPU, memory, threads, network bandwidth and disk space. Computing resources are not infinite and diligent programmers should be good stewards of those resources. Unfortunately, lazy (or naive) programmers are not good stewards of computing resources. Listing 3 is a wasteful implementation of a JDBC Connection. We have seen a similar pattern with network connections, threads, and memory allocations where there is zero consideration of the finite nature of these resources.
package us.daconta.lazy.programmers.examples;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
*
* @author mdaconta
*/
public class LazyConnection {
private static String url =
"jdbc:postgresql://localhost:5442/mydb?user=postgres&password=<pwd>!";
private static boolean initialized=true;
static {
try {
Class.forName("org.postgresql.Driver");
} catch (ClassNotFoundException ex) {
System.out.println("Unable to initialive the driver!");
initialized = false;
}
}
static class Worker extends Thread {
public void run() {
try {
Connection con = DriverManager.getConnection(url);
Statement stmt = con.createStatement();
ResultSet results = stmt.executeQuery("select * from students");
while (results.next()) {
System.out.println("From Thread " + this.hashCode()
+ ", Name: " + results.getString(2));
}
} catch (SQLException ex) {
System.out.println("Error: " + ex.getMessage() + " ... Abort!");
}
}
}
public static void main(String [] args) {
try {
if (args.length < 1) {
System.out.println("USAGE: java LazyConnection <numWorkers>");
System.exit(1);
}
if (!initialized) {
System.out.println("Unable to find the database driver");
}
int numWorkers = Integer.parseInt(args[0]);
for (int i=0; i < numWorkers; i++) {
Worker newWorker = new Worker();
newWorker.start();
}
} catch (Throwable t) {
t.printStackTrace();
}
}
}
A run of Listing 3 produces the following output (abridged):
From Thread 1784893060, Name: Harry Potter
From Thread 1784893060, Name: Hemione Granger
From Thread 1784893060, Name: Ron Weasley
From Thread 1784893060, Name: Prof. Dumbledore
From Thread 1784893060, Name: Prof. Snapes
From Thread 1784893060, Name: Hagrid
Error: FATAL: sorry, too many clients already ... Abort!
From Thread 247440311, Name: Harry Potter
From Thread 247440311, Name: Hemione Granger
From Thread 247440311, Name: Ron Weasley
From Thread 247440311, Name: Prof. Dumbledore
From Thread 247440311, Name: Prof. Snapes
From Thread 247440311, Name: Hagrid
Error: FATAL: sorry, too many clients already ... Abort!
Error: FATAL: sorry, too many clients already ... Abort!
The bottom line here is that connections need pools, memory needs caches and threads need executors. So, in listing 3 we actually have an example of two types of laziness: Connection laziness and Thread laziness. Connection laziness is the poor assumption that when you need a connection you just get another one from the database and don’t worry about managing them in your application. As demonstrated here (because we are explicitly not closing them), you can and will run out of database connections. Fortunately, there are many excellent open source Connection pools available to solve this problem so you can manage your application’s connections locally and reuse them instead of paying the performance penalty of creating another one.
Listing 3 also has an example of what I call a “thread bomb” brought on by the attitude that “threads are free” (which is a corollary to its evil siblings “memory is free” and “bandwidth is free”). While the program did not run out of threads, they are operating system resources, and you can run out of them. Especially when on a large system with many programs running and thus your program is not the only one creating threads. (Note: the new JVM feature called Fibers[1] will solve this problem by not relying on OS level threads). There are many variants of this type of laziness like “if in doubt, call Refresh()” and “if in doubt, create yet another cache and cache it without any regard to overall memory consumption” and “invent yet another way to do something instead of learning the way others have already done it”.
c. Lazy logging.
This same theme of expediency trumping diligence, can invade the practice of creating debug and error log messages in your code to assist in analyzing a problem. This shortcut involves doing the bare minimum to get by. Such a strategy conforms to the traditional concept of laziness: trying to do a minimal amount of work to just get by. In the area of logging and documentation, such selfishness can hurt a program for years. In fact, lazy logging and poor documentation can deliver “death by a thousand cuts” to a system. Lazy logging means that your logging messages, especially for errors, lack enough information for other developers to understand the problem. In essence, you are doing a disservice to your fellow programmers. Listing 4 demonstrates lazy logging.
package us.daconta.lazy_programmers;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Lazy Logger
*
*/
public class LazyLogger
{
HashMap<String, List<Integer>> classroom =
new HashMap<String, List<Integer>>();
Logger logger =
Logger.getLogger(LazyLogger.class.getCanonicalName());
/**
* Add a grade to the students list of grades.
* @param grade
* A student's grade between 0 and 100.
* @param student
* A student's full name.
*/
public void addGrade(int grade, String student)
{
if (null != student && !student.isEmpty())
{
List<Integer> grades = classroom.get(student);
if (null == grades)
{
// lazy instantiation (good)
grades = new ArrayList<Integer>();
}
if (grade >= 0 && grade <= 100)
{
grades.add(grade);
classroom.put(student, grades);
}
else
{
// lazy logging (poor level choice and no context)
logger.log(Level.ALL, "grade is bad");
// Better logging
logger.logp(Level.SEVERE, LazyLogger.class.getName(),
"addGrade",
"The Grade [" + grade + "] for Student [" + student +
"] is not between 0 and 100.");
}
}
else
{
// Lazy logging (no context)
logger.log(Level.SEVERE, "No student.");
if (null == student)
{
// Better logging
logger.log(Level.SEVERE, "Student input parameter is null.");
}
else if (student.isEmpty())
{
// Better logging
logger.log(Level.SEVERE, "Student input parameter is empty.");
}
}
}
public static void main( String[] args )
{
try
{
LazyLogger ll = new LazyLogger();
ll.addGrade(100,null);
ll.addGrade(100, "");
ll.addGrade(90, "Mary Smith");
ll.addGrade(110, "Mary Smith");
}
catch (Throwable t)
{
t.printStackTrace();
}
}
}
A run of Listing 4 produces the following results:
Mar 01, 2021 11:10:46 AM us.daconta.lazy_programmers.LazyLogger addGrade
SEVERE: No student.
Mar 01, 2021 11:10:46 AM us.daconta.lazy_programmers.LazyLogger addGrade
SEVERE: Student input parameter is null.
Mar 01, 2021 11:10:46 AM us.daconta.lazy_programmers.LazyLogger addGrade
SEVERE: No student.
Mar 01, 2021 11:10:46 AM us.daconta.lazy_programmers.LazyLogger addGrade
SEVERE: Student input parameter is empty.
Mar 01, 2021 11:10:46 AM us.daconta.lazy_programmers.LazyLogger addGrade
SEVERE: The Grade [110] for Student [Mary Smith] is not between 0 and 100.
Though a trivial example, what Listing 4 shows is that good logging requires specificity, context and detail.