Lazy Programmers: Chapter 3 (Part 2)

The Bad

 

2.      Sweeping problems under the rug.

Unlike the passive laziness of taking shortcuts, this is a more aggressive form of laziness that willfully turns a blind eye to problems.  Make no mistake here, at this point laziness is costing you big.  Here are three examples of such active laziness:

a.      Ignoring code smells.

Have you ever had to counsel a tween that is ignorant of their own body odor?  Typically, this is more prevalent among boys right after they reach puberty and have not become used to the additional maintenance chores required by their new states.  Well, like teenagers, code can have smells (figuratively) and bad code smells are often ignored by programmers too lazy to clean up the mess!  They figure that since they didn’t make the mess they are not responsible to clean it up so they ignore the smell.

In Chapter 3 of Martin Fowler’s book “Refactoring: Improving the Design of Existing Code”, he introduces the concept of code smells (it is worth noting that Kent Beck co-authored the chapter).  A code smell is an indication that a body of code is in need of refactoring.  Furthermore, refactoring is defined as “a change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior.”[1]  A bad lazy programmer will ignore these code smells by looking the other way and pretending not to see the litter that is stinking up the code base.  Some examples of code smells are duplicated code, long parameter lists, global data, temporary fields and mutable data.  Listing 5 demonstrates multiple smells though often times each smell is buried in a larger body of code and thus obscured. 

Listing 5. SmellyCode

package us.daconta.lazy.programmers.examples;

import java.util.HashMap;

/**

 *

 * @author mdaconta

 */

public class SmellyCode {

    public static int mutableGlobalValue = 120;

    static HashMap<String, Object> appStuff = new HashMap<String, Object>();

    enum ObjectType {typeOne, typeTwo, typeThree};

    ObjectType objectType;

   

    public void excessivelyLongFunctionNameThatIsARedFlag(int paramOne,

int paramTwo, Integer paramThree, Double paramFour,

Object paramFive, int paramSix, long paramSeven, double paramEight,

int paramTooMany) {

            int value1 = (int) paramOne / paramTwo + paramThree

                                            * paramTooMany - paramSix;

           

            // modifying global values

            mutableGlobalValue = 10;

            appStuff.put("One", paramOne);

           

            // duplicated code

            int value2 = (int) paramOne / paramTwo + paramThree

                                            * paramTooMany - paramSix;

           

            // tagging a class instead of using subclasses

            objectType = ObjectType.typeOne;

            

            // too many if then else statements

            if (paramOne !=100) {

                paramOne = 100;

            } else if (paramOne != 200) {

                paramOne = 200;

            } else if (paramOne != 300) {

                paramOne = 300;

            }

    }

}

Each code smell in Listing 5 is annotated in the comment above it.  Some of these are obviously wrong but most just innocently get inserted into a codebase in a casual, almost off-hand manner.   But these small infractions accrue over time and add up to big ramifications.  Let’s use an analogy to demonstrate such impacts.  A code base can be thought of as a virtual city where the “broken window theory” is just as relevant to the upkeep of the city as it is to the upkeep of a code base. The “broken window theory” simply states that tolerating even one broken window in a building leads to that building (and eventually the entire block) becoming run-down and dilapidated.  In turn, that block’s ruin eventually leads to the entire city becoming run-down and dilapidated.  All from the small start of a broken window.  Why?  Because a broken window signifies a lack of concern, a casual attitude that let’s small things slip by as if they don’t matter.  Code smells are the “broken windows” of your code base!  They cannot and should not be ignored by lazy programmers!  Unfortunately, code smells are just the tip of the iceberg of dangers to your code base from laziness.  So, let’s dig deeper…

b.      Ignoring technical debt.

Moving up the scale of impact and thus seriousness, lazy programmers will ignore technical debt.  Really lazy programmers will not only ignore clear technical debt but they will even ignore the reporting of technical debt.  Technical debt is the slow accrual of known design flaws into a large code base that increase in cost the longer they are left unfixed (aka unpaid).  This metaphor mimics the notion of financial debt in order to highlight that delay in repayment of the debt increases the size of the debt via interest accrual.  In essence, not fixing the debt, increases the debt.  Of course, we all intuitively know that ignoring a problem does not make it go away, it usually makes it worse.  Examples of technical debt are lack of design, complexity creep, misuse of a technique, overuse of a technique and many variants on these themes.  Listing 6 demonstrates code with Technical Debt:

Listing 6.  TechDebt

package us.daconta.lazy.programmers.examples;

import java.awt.image.BufferedImage;

import java.sql.Connection;

import java.sql.DriverManager;

import java.util.HashMap;

import java.util.Random;

/**

 * Brief examples of Technical Debt.

 * @author mdaconta

 */

public class TechDebt {

    /* FIXME - we should really replace this in the future...

       this would be better in a database.

    */

    static HashMap<String, Object> sharedEntities =

                                                                new HashMap<String, Object>();

    

    /* BubbleSort - really?  This ARRAY can be HUGE! Junior developer said

       "No time to do this right! So, I copied this from the internet." */

    public static void sortArray(int [] array) {

        for (int i=0; i < array.length; i++) {

            for (int j=1; j < array.length - i; j++) {

                if (array[j-1] > array[j]) {

                    // swap them!

                    int temp = array[j-1];

                    array[j-1] = array[j];

                    array[j] = temp;

                }

            }

        }

    }

   

    public static void main(String args[]) throws Exception {

        // Initial Load - do these ever get garbage collected?

        for (int i=0; i < Integer.parseInt(args[0]); i++) {

            sharedEntities.put("entity" + i, new BufferedImage(10000, 10000,

                                BufferedImage.TYPE_4BYTE_ABGR));

        }

        

        // TODO - put this in an encrypted file

        Class.forName("org.postgresql.Driver");

        String dbPassword = "password123";

        String sysUser = "System";

        String url = "jdbc:postgresql://localhost/test";

        Connection conn = DriverManager.getConnection(url, sysUser, dbPassword);

        

        // create quantities

        Random random = new Random();

        int size = Integer.parseInt(args[1]);

        int [] counts = new int[size];

        for (int i=0; i < size; i++) {

            counts[i] = random.nextInt();

        }

        sortArray(counts);

        System.out.println("Sorted " + counts.length + " items");

    }

}

A run of Listing 6 produces:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

                at java.desktop/java.awt.image.DataBufferByte.<init>(DataBufferByte.java:76)

                at java.desktop/java.awt.image.Raster.createInterleavedRaster(Raster.java:266)

                at java.desktop/java.awt.image.BufferedImage.<init>(BufferedImage.java:391)

                at us.daconta.lazy.programmers.examples.TechDebt.main(TechDebt.java:37)

Listing 6 demonstrates several small examples of technical debt that you will see in larger code bases.  Typically, the larger the code base the more technical debt you will encounter.  In listing 6, we see three examples of technical debt, the use of a large global data structure, an inefficient algorithm choice and an obvious security violation.  Of the three, the first one is the most subtle and dangerous because sometimes there are good reasons for a global data structure but in general this is a bad approach.  All “global” data goes against the best practice of data encapsulation to protect one classes’ data from all other classes.  By making data globally accessible you increase the chance of data corruption, memory leaks, and race conditions.  This is why in safe languages, like Rust, all data is immutable (unchangeable) by default. 

The second example of technical debt in Listing 6 is something we covered before in choosing a poor algorithm out of expediency.  The developer’s excuse of “no time to do it right” is something every technical lead has heard and of which, I have been guilty of in the past.  The problem lies not in the initial act which usually has good intentions but of the failure to actually go back and correct it.  Does your program have a dedicated “technical debt” team whose full-time job it is to go back and fix technical debt in your system?  If not, your code base will degenerate over time.  The third example is a serious security violation that happens far more often than anyone would like to admit; however, is so serious that this example of laziness should be grounds for immediate firing.   Simply stated, you can never hard-code passwords in your code for convenience.  They must be in a secure external file that at a minimum uses the operating system’s file protections to protect the file from unauthorized users.     


            While these are small examples of technical debt, usually a large code base has larger examples of serious technical debt.  Some examples of larger technical debt are using a polling approach to implement asynchronous web services (yes, this is an oxymoron but a real example); or having a user interface with four distinct event mechanisms with duplicative functionality; or updating database entities without appropriate locking and thus suffering from the lost update problem.  Typically a developer stumbles upon technical debt when something doesn’t smell right in the code they are working on.  Instead of ignoring their unease, they should go and discuss it with the technical lead.  If the technical lead confirms that they have found technical debt they should either see if they can fix it within their current sprint[2] budget or, at a minimum, document it by putting it on the backlog of future work.

c.       Failing to create and enforce standards.

As generally intelligent people, most programmers are fiercely independent and like to “go their own way.”  This programmer characteristic makes creating and enforcing standards difficult at best.  I have run multiple standards organizations in my career, both commercially and for the Government, so I understand the difficulties in gaining consensus among smart people.  Every programmer in your team or in your project will run across the need for standardization in your coding practices.  For example, code formatting, third party libraries, units of measure, data models, git procedures and testing gates.  Like code smells, lazy programmers have a tendency to “look the other way” when noticing non-standard behavior in the code.  It is very easy to shrug and say, “I didn’t create that” or “someone (else) should really fix that”.  An area of organizational failure is the common failure of enforcing standards across teams when they are being violated.  A favorite to ignore is not requiring best practices out of fear of thwarting developer creativity.  For example, let’s say it is agreed upon that all Javascript development should leverage Typescript to achieve strong typing; however, this is not enforced.  Thus, time and again, untyped Javascript code enters the baseline with its ensuant risk of dynamic typing.  The sad thing here is that a failure to enforce standards is doubly lazy – lazy program management coddling lazy programmers that can’t be bothered with a modicum of discipline.



[1]Fowler, Martin; Refactoring, 2nd Edition; Pg 45.

[2]In Scrum, a sprint is a two-week development cycle. See: https://en.wikipedia.org/wiki/Scrum_(software_development).