How Missing Abstractions Cause Defects

It’s pretty obvious how WET code (We Enjoy Typing or Write Everything Twice) breeds defects: developers modify one instance of the duplicated code block and don’t realize there’s another one that needs the same treatment.

Missing abstractions are a special case of WET code. They occur when a developer fails to encapsulate something in a class. Instead, he exposes the logic in-line, so to speak.

A simple example occurred at my company just last week. We are enhancing our software to support multi-tenancy: the ability for more than one organization to run from one installed instance, as in the cloud. Part of this project will be to make all usernames qualified by an organization code.

Because of the way parts of our infrastructure work, it will sometimes be convenient to package organization code and username together. We will probably use a colon as a delimiter, as in orgCode:username.

It’s the easiest thing in the world to come up with a qualified username in every place you need one by writing something like orgCode + ':' + username and to parse a qualified username at each occasion with qualifiedUsername.split(':').

It would be easy, but it would be a mistake. What’s going to happen when we want to support Active Directory integration, so a username might look like domain\username? Every instance of the parsing logic and every instance of the building logic will have to change. Worse, the poor developer who is saddled with this task is likely to find 98% of the cases, but miss 2%. If QA doesn’t find the 2% needle-in-a-haystack, a bug will find its way into the field.

Much better is a little class like this one that encapsulates today’s trivial concerns but serves as the DRY place where new requirements can be accommodated.

// Encapsulates a username that is qualified by an organization code.
// username - Either the unqualified username or a qualified one as
//            returned by the qualifiedName property.
// orgCode  - An organization code, or undefined if and only if the
//            username is already qualified.
// To keep this example simple, it is assumed that upstream classes
// have guaranteed that username and orgCode consist only of alphanumerics.

function QualifiedUsername(username, orgCode) {
'use strict';
  var self = this,
      orgCodeSupplied = orgCode && orgCode.length > 0,
      delimiter = ':';
  
  if (username.indexOf(delimiter) >= 0) {
    if (orgCodeSupplied) 
      throw new Error('If the username parameter is already qualified, the orgCode must not be supplied.');
  } else if (!orgCodeSupplied) {
      throw new Error('If the username parameter is unqualified, the orgCode must be supplied.');
  }
  if (orgCodeSupplied) {
    self.username = username;
    self.organizationCode = orgCode;
  } else {
    var parts = username.split(delimiter);
    self.organizationCode = parts[0];
    self.username = parts[1];
  } 
  self.qualifiedName = self.organizationCode + delimiter + self.username;
}

As I shared two posts ago, WET code and missing abstractions together caused 12% of all defects in a recent release of our software. This has prompted us to renew our commitment at both development time and code-review time to avoid and spot these errors!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.