Programming By Intention

Posted on Posted in Java, Stories

Programming By Intention

Cohesive, readable and expressive, easy to debug, refactor, enhance and easy to unit test. These are the qualities of good software code. However, there are factors which hinders most of us from achieving these. When a software is handed over to a number of developers over the years, code degradation happens. And as bugs starts to show up, in pressure of deadline, most of us settle down to quick fixes which adds up to this degradation and code starts to loose its beauty and becomes spaghetti of weak patches.

Good coding practices and regular refactoring may solve this code degradation, but refactoring is sometimes hard to do for some reasons. But if we have coded with testability, cohesiveness and readability in the first place, we would find it easier to refactor later as the need arises.

This post aims to tackle the practice of “Prefactoring” to achieve the good qualities of software code we mentioned above through a practice known as “Programming By Intention“.

Programming By Intention, is not a new concept, most people know this as top-down programming. This technique, in spite of being old turns out to be valuable in an Agile environment.

Let’s say you have a requirement for a software feature wherein you need to create a search engine. The search engine accepts a String as the search criteria. Then you need to extract the tokens from this String (e.g Split the String using space as delimiter) as array then normalize these tokens (convert all characters to lowercase and non-alphanumeric characters must be removed). Then do the search using these tokens. You decided (rightly or wrongly) to come up with one class with a single public method that performs all of these.

Let’s once again review the requirements for the search engine class:

  • Accept search criteria in the form of String
  • Extract tokens from search criteria
  • Normalize tokens
  • Search using the normalized tokens

We can call these requirements as intentsabstract description of an operation to be performed.

public class SearchEngine {
     public List search(String criteria) {
        String[] tokens = tokenize(criteria);
        String[] normalizedTokens = normalizeTokens(tokens);
        return findMatches(normalizedTokens);
     }
}

The methods tokenize(), normalizeTokens(), and findMatches() do not exist yet, but we are already coding it as if they are already in place and since the methods do not exists yet, you are not constrained to anything except your “intentions” (hence, you are “programming by” them). In writing this way, we are allowing ourselves to focus in breaking down the problem and other issues of the overall context.

Programming by Intention says, rather than actually writing the code in each case, instead pretend that you already have an ideal method, local in scope to your current object, that does precisely what you want. And following this advise, you should ask yourself:

  • What parameters would this ideal method accept? and,
  • What would it return?
  • What name would make sense to me right now as I imagine this method already exists?

So what benefits do we gain by coding this way?

  • Cohesiveness
  • Readability
  • Easier to debug, refactor and unit test
  • Certain patterns would be easier to see
  • Methods are easier to move from one class to another as the need arises

Method Cohesion – cohesiveness is all about singleness. A method is considered cohesive if it accomplishes one single functional aspect of the overall functionality. Looking back at the codes, we basically have a search method which accepts a string as search criteria, and since we did not wrote all logic inside that search method, instead we created methods to do the smaller details of the whole search method, our methods are considered cohesive in the sense that we have a code dedicated for tokenizing, for normalization, and a search worker code. The search() method basically just delegates the actions to our three “helper” methods (tokenize(), normalize(), performSearch()).

Readability and Expressiveness – let’s see again the code and look carefully how easy it is to read and understand what it does.

public class SearchEngine {
     public List search(String criteria) {
        String[] tokens = tokenize(criteria);
        String[] normalizedTokens = normalizeTokens(tokens);
        return findMatches(normalizedTokens);
     }
}

Resolving Code Smells (We don’t want excessive comments!) – we can see that by coding by intention, we reduce the need for comments because we start by writing methods with sensible names. Misleading and un-updated comments are signs of code smells.

Refactoring and Enhancing – Look carefully at the methods we defined above. All these methods accepts parameters. They do not work on any instance variables containing class may have. Writing methods this way, we reduce the coupling between the containing class and the methods we are defining. If there is change and we now want to create different search engine classes that normalizes tokens the same way, we can easily extract the normalize() method into its own class.

Seeing Patterns – Another change require us to create different kinds of normalizer (e.g LowerCaseNormalizer, WhiteSpaceNormalizer), we can easily change the TokenNormalizer class into an interface and since normalization is considered an algorithm that varies across implementations, we can for let’s say follow the Strategy Pattern allowing concrete classes to implement the normalize() method.

Prefactored codes through Programming by Intention proved to be valuable. It’s an old practice yet effective and you may want to give it a try. As with all practices, getting used to it takes time, but I’m sure most of you can get the hang of it quickly.

Reference: Essential Skills for the Agile Developer, (Shalloway, Bain, Pugh, Kolsky)

Leave a Reply

Your email address will not be published.

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