The Lazy Programmer
"If you have a difficult task, give it to a lazy person - they will find an easier way to do it." -Hlade's Law
Whenever I am asked why something should be done "a certain way," the answer at the front of my mind usually begins, "Laziness. This is the easiest way." Then I spend a minute recomposing the answer in terms of "efficiency" or "best practises." But enough. It is time to stop villifying laziness and start recognising the virtue of the true key to successful programming.
The "lazy" way I describe is not necessarily the easiest path in the short-term. Instead it is the easiest in the long run to understand, reuse, maintain, extend, and often, to explain. Long range laziness. When the history of software is pondered, the lazy, efficient paths will have wasted the least amount of people's time, money and energy.
In the long run, the truly lazy will always seek the most efficient solution. As you begin to see problems the lazy way, it becomes clear that all the great approaches to software development are in fact predicated on laziness. Prime examples are the Gang of Four's Design Patterns, Boyce-Codd Normalization, and Fowler's Refactoring. Reduce, reuse, recyle.
A Case Study
To demonstrate, consider the threat of SQL Injection. This is where a hacker tries to hijack a legitimate SQL query with malicious code. A SQL Injection attack on a login page may be identified by the use of certain characters not found in normal usernames or passwords, like these:
';#&*%"
There are many ways to protect a site against such attacks, and they all reduce to various degrees of laziness.
First Lazy Form: First Order Lazification (1LF)
First Lazy Form is the solution that plugs the hole in the least efficient way. It is the Band-Aid solution that may work today, but probably does not scale, adapt, extend, or read well. In our SQL Injection example, it would be the solution that proposes using the following sort of code in the login processor:
...
' 1LF
' Prevent SQL Injection attacks: Clean input with Replace()
Dim QUOTE as String = chr(34)
Dim strRejectedChars as String = "';#&*%" & QUOTE
Dim strCurrentCharacter as String
Dim intX as Integer
For intX = 1 To 7
strCurrentCharacter = Mid(strRejectedChars, intX, 1)
pUserName = Replace( pUserName, strCurrentCharacter, "" )
pPassword = Replace( pPassword, strCurrentCharacter, "" )
Next
' Query credentials against database
...
end sub
This is unnaturally elegant for First Lazy Form, but it qualifies. The problems?
Inefficient code: All input is run through the same 7 iterations of the loop, for a total of 21 function calls.
Hard-coded Dependency: If someone discovers a new dangerous character (like ":" or "|"), it must be added by hand to strRejectedCharacters, and the loop counter adjusted. Even if the loop were from 1 to strRejectedCharacter.Length
, this approach still needs programmer intervention to adapt to any new characters. Sounds like work to me.
Not Reusable: Reusing this code block involves cutting and pasting. More work.
Second Lazy Form: Second Order Lazification (2LF)
Second Lazy Form takes a little thought and/or planning, but results in cleaner code. It will address 1LF's problems of efficiency and make an attempt at a general solution.
...
' 2LF
' Prevent SQL Injection attacks: Clean input with Regular Expressions and Replace()
Dim options As RegexOptions = RegexOptions.None
' Rule: Only numbers, lowercase, and uppercase characters are allowed
Dim regex As Regex = new Regex("[^0-9a-zA-Z]", options)
' Pre-check the data to see if action needs to be taken
if regex.IsMatch(pUserName) OR regex.IsMatch(pPassword) then
pUserName = regEx.Replace(pUserName, "")
pPassword = regEx.Replace(pPassword, "")
end if
' Query credentials against database
...
This time, a conscious effort is made to only execute code if necessary. The slower Replace operations are only executed if the faster IsMatch operation(s) deems it necessary. And since expressions are evaluated right to left, if the pPassword test returns true for invalid characters, .NET never needs to evaluate pUsername to proceed to the Replace operations.
The approach introduces a rule which eliminates Hard Coded Dependency, thus providing a more General Solution. Usernames and passwords must contain only alphanumeric characters. Spaces and symbols are forbidden. Rather than try to guess all the possible harmful characters and manners of expressing them, this solution defines a finite sandbox which is trivial to enforce.
One problem remains. Our code is:
Not Reusable. Using this block elsewhere still requires cutting and pasting. Maintaining two or more copies of the same code is work, and work is the enemy of the lazy. A comfortable solution lies one level deeper.
Third Lazy Form: Third Order Lazification (3LF)
Third Lazy Form takes code which meets requirements for Second Lazy Form and encapsulates it into a reusable package. There is a lazy debate over whether encapsulation should actually be the rule for 2LF, and clean, general-purpose code the rule for 3LF. An argument can be made that making code reusable provides more leisure time than making code maintenance-free. On the other hand, bad code encapsulated is still bad code. And frankly I'm too lazy to rewrite the section on 2LF so let this be a lesson to you: don't sweat over inconsequential details. And something about planning. Every software mistake teaches something about planning.
' 3LF
Public Class StringCleaner
Public Function StripNonAlphaNumeric( byRef inString as String ) as Boolean
Dim options As RegexOptions = RegexOptions.None
Dim regex As Regex = new Regex("[^0-9a-zA-Z]", options)
if regex.IsMatch(inString) then
inString = regEx.Replace(inString, "")
StripNonAlphaNumeric = true ' Something changed
else
StripNonAlphaNumeric = false ' Nothing changed
end if
End Function
End Class
Note that encapsulation allowed me to get rid of a few comments, a benefit described by Refactoring. Good structure combined with appropriate function and variable names equals less to be explained. In the code that calls this method I might comment that the purpose is to prevent SQL Injection, actually naming the function "StripNonAlphaNumeric" instead of "PreventSQLInjection" is more descriptive of what the method actually does.
Third Lazy Form is good enough for most people. The solution can be understood, reused, maintained, extended, and explained. Why would you ever go further? I mean it's late, people want to go home already. But there is an even lazier solution.
Fourth Lazy Form: Fourth Order Lazification (4LF)
At the beginning it was discussed that the laziest solution takes the longest view, and requires the least work over the life of the app. Fourth Lazy Form meets all the requirements of 3LF, but also requires you to add features to make problems easier to solve in the future. 4LF is benevolent that way. 4LF pays off in the long-run. This may require rethinking the application's purpose, and perhaps entirely restructuring your approach.
Consider our string-cleaning code. It all began as a slap-dash effort to turn dirty input into sparkling, clean input and evolved into a reusable class. But think about it, is the solution to that problem really to clean up a hacker's input and let it run? Not only is the string replacement a waste of time, but allowing any query to run at all is a deeper waste of resources. And what if we later choose to strip the word "UNION" from input, since it too could be part of a SQL Injection attack. How long will it take before you or tech support figures out why user "PaulBunion" can't login, or why Gina's "unionstation" password no longer works?
The issue is that the problem is solved, but the solution is not appropriate and in the worst case introduces new problems. Rather than trying to dress bad input in new clothes, the 4LF solution is to change the approach. The most appropriate and efficient behaviour is to emit an error, stop the query and get on with the next cycle. This calls for a custom error object that we can reuse for any bad input.
Public Class CustomAuthentication
Private Function IsAlphaNumeric( byRef inString as String ) as Boolean
Dim options As RegexOptions = RegexOptions.None
Dim regex As Regex = new Regex("[^0-9a-zA-Z]", options)
if regex.IsMatch(inString) then
IsAlphaNumeric = false
else
IsAlphaNumeric = true
end if
End Function
Public Function Login( strUsername as String, strPassword as string ) as String
Dim errLogin as CustomError = New CustomError()
If IsAlphanumeric(strUsername) AND IsAlphaNumeric(strPassword) then
' Check credentials with a stored procedure on the database server
' Write to log
' Set up the user environment
' ...
Else
errLogin.ErrorCode = "ERR_NONALPHANUMERIC"
' Write to log
' Could emit errLogin.Description here
End If
End Function
Public Function IsAuthenticated( ) as Boolean
...
End Function
...
The code has all the benefits of 3LF, plus in solving the problem a new CustomError object was created which simplifies the current solution, and also generically helps the developer define any other errors produced by the app throughout its development life.
You may ask, "What is this CustomError object?" Well, in theory it's a class which contains an ErrorCode property, and a Description method which might be defined in a Resource File or off in a multilingual database. In practise, today, right now, it remains a figment of my lazy imagination. The point is, if the CustomError object exists then you can use it not only to report "Invalid Input," but also to access appropriate messages to the user for "Account not found," "Bad Password," "Password not provided," and so on. Lazy Paradise.
Conclusion
This pseudo-methodology can be described as "half serious and all true." Seeking the lazy path will make you a better programmer. While the specifics are up for discussion, I believe that long-term efficiency will be the crux of any unified theory of the physical laws, and in people is the only characteristic worth attaining. And not only at an individual level, the principle gains strength in numbers. All this document does is apply the principle to programming. Beautiful, efficient, lazy programming.
So while people better suited to the task work out that whole gravity / magnetism / strong and weak-nuclear thing, why not enjoy the benefits of universal truth and make your coding a little lazier. If you would like to contribute to the Lazy History of Programming, or have a story where laziness saved the day, please write me or submit a Comment below.