Showing posts with label software engineering. Show all posts
Showing posts with label software engineering. Show all posts

Thursday, 12 July 2012

Test Driven Development solutions to same problem

!WARNING - THIS POST IS WRITTEN FOR SOFTWARE FOLKS!

I discovered Test Driven Development / TDD in about 2005/6 thanks to Gunjan Doshi's TDD Rhythm and 1-2 old articles on JavaWorld.

Years later I read Kent Beck's book Test Driven Development (The Addison-Wesley Signature Series) (or US) and was glad I had not missed anything fundamental from my non-standard "foundations" on the subject. These days there is a fairly good intro on Test Driven Development wikipedia entry. This post is aimed at software craftsmen and journeymen and perhaps even masters - to inspire a bit of thought along the following dimensions that impact the solutions that evolve:
- the understanding of the functional requirement
- the understanding of the non-functional requirement
- approaching the solution in primarily an iterative approach (managing complexity, perhaps)
- approaching the solution in primarily an incremental approach (managing complication, perhaps)
- the understanding of the required test strategy to ensure required quality level is achieved

Dimensions that, in TDD, result in the tests selected AND the order in which to approach the tests, which result in the emerging of different solutions.

I've spent a great deal of time leading/mentoring/coaching Test Driven Development (TDD) sessions, working with teams and individuals on a particular problem which they had no experience in beforehand. These sessions were the correct environment for us both to learn TDD, or for them to learn TDD, or even as part of a recruitment selection process where the team was practising TDD and we had to ensure candidates could practice TDD as we did.

I selected a problem that was documented on wikipedia on the page describing Numerology. I like discovering interesting coding problems on wikipedia as the descriptions there are [theoretically] internationally acceptable and understandable - which helps enormously to "level the playing field" for English first language speakers as well as non-English first language speakers.

The specific problem I wanted to pair up on is described very well under "Methods" in terse language (another "pro" as time is always pressured, and I wanted a problem understood, solved and discussed in 30-45 minutes).
In essence for this challenge, I wanted solutions to be developed that would convert a word, a phrase, and ultimately a person's full name, into a number, using simple rules that had to be analysed on the wikipedia page and extracted, without my help, to establish the even playing-field going into the solution development phase and discussion phase thereafer.

The first part of the rules were simply how to encode letters to numbers according to Numerology:

1 = a, j, s;
2 = b, k, t;
3 = c, l, u;
4 = d, m, v;
5 = e, n, w;
6 = f, o, x;
7 = g, p, y;
8 = h, q, z;
9 = i, r

The second part of the rules would usually give the candidate 2 strategies depending on how detail-oriented the person was.

For those who rushed through understanding the requirements, the rules were simply summing individual numbers repeatedly until the total < 10, eg using the wikipedia examples:

3489 ? 3 + 4 + 8 + 9 = 24        ? 2 + 4 = 6
Hello ? 8 + 5 + 3 + 3 + 6 = 25  ? 2 + 5 = 7

For those who paid a bit more attention, the rules were simply:

  value modulo 9, if 0, then 9

It is *always* interesting to see who picks up the shorter modulo 9 strategy, and who does not. (feeding into a lightweight Belbin assessment - see my Coaching with Belbin)

The various solutions below highlight [clearly] how the right problem tackled in the right way creates a much more readable, maintainable, extensible and flexible solution using TDD. Although even the less TDD-correct approach still produces an acceptable result. For me though, the best solution was actually produced largely without using TDD. Can you spot it?

/***************************************/
// TESTS
/***************************************/

I have removed name identifications and some "interesting" tests from the typical TestClasses that were all similar to either:

// Simple
import Calculator;
import org.junit.Test;
import static org.junit.Assert.*;
public class CalculatorTest {
    Calculator calculator = new Calculator();
    /*
     * Test Cases:     *    
     * 3,489 => 3 + 4 + 8 + 9 = 24 => 2 + 4 = 6     *          
     * Hello => 8 + 5 + 3 + 3 + 6 = 25 => 2 + 5 = 7  *
     */
    @Test public void testCalculateImportantChars() {        assertEquals(2, calculator.convertCharToNumeric('k'));        assertEquals(1, calculator.convertCharToNumeric('a'));        assertEquals(9, calculator.convertCharToNumeric('r'));        assertEquals(5, calculator.convertCharToNumeric('n'));        assertEquals(8, calculator.convertCharToNumeric('h'));        assertEquals(5, calculator.convertCharToNumeric('e'));        assertEquals(3, calculator.convertCharToNumeric('l'));        assertEquals(6, calculator.convertCharToNumeric('o'));        assertEquals(9, calculator.convertCharToNumeric('i'));        assertEquals(8, calculator.convertCharToNumeric('z'));    }

   @Test public void testCalculateHello() {        assertEquals(7, calculator.calculate("Hello"));    }
}

OR:

// More along Kent Beck's final example approach

import Calculator;
import org.junit.Test;
import static org.junit.Assert.*;

public class CalculatorTest {

   Calculator calculator = new Calculator();

   /*
    * A = 1
    * B = 2
    * C = 3
    * a = 1
    * b = 2
    * j = 1;
    * k = 2
    * i = 9;
    * 0 = 0
    * 1 = 1
    * 11 =  2
    *
    * AA = 2
    * AB = 3
    *
    * 1B2a =6
    */

   @Test
   public void testCalculate1() {

       String[] inputs = {"A", "a", "B", "b", "C", "k", "j", "i", "AA", "AB", "ABc", "0", "1", "11", ",", " ", "1B2a", "-1", "hello", "3,489"};
       int[] outputs = {1,1,2,2,3,2,1,9, 2, 3, 6, 0, 1, 2, 0, 0, 6, 1, 7, 6};

       for(int testcase=0; testcase < inputs.length; testcase++) {
           assertEquals(outputs[testcase], calculator.iWantToBelieve(inputs[testcase]));
       }

       assertEquals(-1, calculator.iWantToBelieve(null));
   }

}

/***************************************/
// SOLUTIONS, all checks and Exceptions removed for brevity
/***************************************/



/* Common Solution 1 */


public class Calculator {

   public int iWantToBelieve(String string) {
       if(null==string) {
           return(-1);
       }
       int result = 0;
       for(int pos = 0; pos < string.length(); pos++) {
           int valueToAdd;
           char currentChar = string.charAt(pos);
           if(Character.isDigit(currentChar)) {
               valueToAdd = Integer.valueOf(String.valueOf(currentChar));
           }
           else if(Character.isLetter(currentChar)){
                int tmpValue = Character.toUpperCase(currentChar) - 64;
               int valueOfCharacter = tmpValue % 9;
               valueToAdd = valueOfCharacter == 0 ? 9 : valueOfCharacter;
           }
           else {
               valueToAdd = 0;
           }
           result+=valueToAdd;
       }

       if(result>9) {
           result = iWantToBelieve(String.valueOf(result));
       }
       return(result);

   }

}


/* Common Solution 2 */


import java.util.HashMap;
import java.util.Map;

public class Calculator
{
 private static Map numericCodes = new HashMap();
  
  static 
  {
   numericCodes = initialiseNumerologyMap();
 }

 public Calculator(){
 }
  
 public int calculate(String word) {
   char[] chars = word.toLowerCase().toCharArray();
   int[] digits = new int[chars.length];
    
   for (int i = 0; i < chars.length; i++)
   {
       char characterValue = chars[i];
       Integer numeric = numericCodes.get(characterValue);
       if (numeric == null)
       {
         digits[i] = 0;
       } else {
         digits[i] = numeric;
       }
   }
   int sum = 0;
   for (int i = 0; i < digits.length; i++)
   {
     sum += digits[i];
   }
   if (sum > 9)
     return calculate(""+sum);
    
   return sum;
 }

 /* 
  * 1= a, j, s; 2= b, k, t; 3= c, l, u; 4= d, m, v; 5= e, n, w;
  * 6= f, o, x; 7= g, p, y; 8= h, q, z; 9= i, r  
  */
 private static Map initialiseNumerologyMap()
 {
   // a to z mappings
   for (int i=97; i<123; i++) {
     int alphabetValue = i%96;
     int modulatedValue = alphabetValue%9;
     if (modulatedValue == 0) {
       modulatedValue = 9;
     }
     numericCodes.put((char)i, modulatedValue);
   }
   // 0 to 9 mappings
   int j=0;
   for (int i=48; i<57; i++) {
     numericCodes.put((char)i, j);
     j++;
   }
   return numericCodes;
 }

}

/* Common Solution 3 */

public class Calculator {

   public int calculate(int n) {
       if (n == 0) {
           return 0;
       }

       int result = n % 9;
       if (result == 0){
           return 9;
       }
       return result;
   }

   private String a2n = "abcdefghijklmnopqrstuvwxyz";

   public int convertCharacter(char ch) {
       return calculate(a2n.indexOf(Character.toLowerCase(ch))+1);
   }

   public int convertWord(String word) {
       int sum = 0;
       for(char c : word.toCharArray()) {
           sum += convertCharacter(c);
       }

       return calculate(sum);
   }

}

/* Common Solution 4 */

public class Calculator {

   public int convertCharToNumeric(char c) {
       int intValue = (int)c;
       if ((intValue >95) && (intValue < 129)) {
           int numericFate = (intValue - 96) % 9;
           return convertForNumerology(numericFate);
       } else {
           return 0;
       }
   }

   private int convertForNumerology(int yourFate) {
       if (yourFate == 0) {
           return 9;
       }
       return yourFate;
   }

   public int calculate(String word) {
       int total = 0;
       for (char c : word.toLowerCase().toCharArray()) {
           total += convertCharToNumeric(c);
       }
       return convertForNumerology(total%9);
   }

}

/* Common Solution 5 */

public class Calculator {

   public int calculate(int n) {
       int result = n % 9;
       if (result == 0) {
           result = 9;
       }
       return result;
   }

   public int convert(String word) {
       String workingWord = word.trim().toLowerCase();

       int total = 0;
       for (char ch : workingWord.toCharArray()) {
           int intValue = (int)ch;
           if ((intValue > 96) && (intValue < 123)) {
               total += (intValue % 96);
           }
       }

       if (total > 0) {
           return calculate(total);
       }

       return 0;
   }

}


/* UnCommon Solution 6 */

public class Calculator {

   public int calculate(long n) {
       if(n%9==0)
           return 9;
       return (int)(n % 9);
   }

   public long convertStringToNumber(String string) {
       String tmpString = string;
       tmpString = tmpString.toLowerCase().replaceAll("[^abcdefghijklmnopqrstuvwxyz]", "");
       tmpString = tmpString.replaceAll("[a,j,s]", "1");
       tmpString = tmpString.replaceAll("[b,k,t]", "2");
       tmpString = tmpString.replaceAll("[c,l,u]", "3");
       tmpString = tmpString.replaceAll("[d,m,v]", "4");
       tmpString = tmpString.replaceAll("[e,n,w]", "5");
       tmpString = tmpString.replaceAll("[f,o,x]", "6");
       tmpString = tmpString.replaceAll("[g,p,y]", "7");
       tmpString = tmpString.replaceAll("[h,q,z]", "8");
       tmpString = tmpString.replaceAll("[i,r]", "9");
        return Long.parseLong(tmpString);
   }
  
}

/**************************/

It seems to me that TDD represents some interesting mental/psychological challenges for people who try:

  • For software newbies, it is almost effortless, but they struggle to see the value in what they're creating as their solution emerges - I think due to a lack of experience looking at mountains of badly written and poorly designed legacy systems
  • For software oldies, it is darn painful, and they struggle to see the value in the extra steps they're taking, constantly battling the urge to do more error checking and handling, to make the solution more complicated, and generally reach a point of suspended disbelief needing to see more examples and try it again in their work environments
  • For some, and it is so far impossible for me to recognise them, they reach almost immediate "ah ha!" realisations of the power of TDD. They're flexible and open enough to suspend their not-as-entrenched over-thinking thought processes that the "oldies" display yet they have experience of maintaining some old production systems and dealing with some of their more novice mistakes that have come back to haunt them again, and sometimes again and again.
  • Keith Braithwaite   and I once discussed that people applying TDD properly ("coincidentally" those who love doing so) experience  Csikszentmihalyi's "Flow"  - and hence even more benefits for those who successfully embrace, as well as the organisations that setup them up to succeed/embrace.


I look forward to hearing other points of view on the above 6 solutions for the "same problem" over the next few years.

Thursday, 4 November 2010

Strange ATM / Cash Card bug?

I recently had a cash card from a major UK bank.

I used to put it in any ATM / Cash Point, punch in my pin, and get access to the services. I used to be able to draw statements and check my balance. But as soon as I tried to draw money, immediately the machine would eject my card and give me the error message "Temporary error processing your request. Please try again later". No matter which machine or bank I tried, always the same result.

A while ago, friends of mine in that space told me about the ATM / Cash Point algorithm. And the only really significant thing that I remember, is that the machine only actually authenticates your pin against your bank once you select the service that requires authentication... eg balance request, mini statement, draw money.

So...what was going on? I have thought about it for a while now, and still no idea. When I eventually contacted my bank, they told me not to worry as clearly the faults I was experiencing were temporary faults that existed only the Cash Point machines. Yet all my data points told me something else was happening. Eventually the "helpful" call centre operator suggested I just get a new card. Which I did, and it did solve my problem.

I can only think that there was a "magic sequence" of numbers encoded somewhere in the chip / magnetic strip of that particular card which was causing a very nasty bug on the various Cash Point hardwares/softwares to show itself. Lucky me.

Or unlucky them...could such a magic number really exist somewhere and be used somehow to cause a buffer overflow attack on what I've pretty much taken for granted is a well and truly stable set of components and systems after all these years?

These kinds of puzzles is why software, software engineering and software quality will always keep me interested.

Thursday, 9 April 2009

What's Up With All The Cynicism in IT?

At SPA2009, I had a little session titled "What's Up With All The Cynicism in IT?". It was unfortunately quite time reduced from my original plan, but I did manage to impart a little knowledge of Edward De Bono's Six Thinking Hats method to a group of 8 varied participants, who did supply quite a bit of "food for thought".

I've published my presentation, and the outputs of the group work sessions on the SPA2009 wiki: What's Up With All The Cynicism In IT? Outputs

Wednesday, 8 April 2009

SPA2009

Also known as the British Computing Society Software Practice Advancement Conference, 2009. I attended SPA2009 from Sunday-Wednesday (5-8 April 2009) and was really pleased that I did! There were a lot of new faces, a lot of old faces, great new topics and content on programming, process and people stuff. There were some strong opinions that people were passionate about and argued the merits of - a great learning and networking event!

Thursday, 15 January 2009

SANS Institute Publishes Experts-Agreed List of Top 25 Coding Errors

Some top people in the software industry, as well as critical organisations, have come together and agreed on a list of top 25 programming errors, and provided discussions and ratings on each. The information is well presented and easy to read: Top 25 Programming Errors.

Wednesday, 23 July 2008

Scaling Software Agility by Dean Leffingwell

Dean is the former founder and CEO of Requisite Inc, responsible for the Requirements Gathering and Analysis Tool: Requisite Pro. It seems like his vast experience from startup to merging with IBM has touched on a number of key software development issues and he is now consulting very successfully and writing good books!

I picked up Scaling Software Agility at a book store/stall at SPA 2008 as it seemed to have a couple of new things to say, or at least say them in new ways - and I was very pleased with my choice!

I believe there is something for everyone in this book - wether you are new to agile or an experienced practitioner. The book touches on a number of topics and leads you from brief "beginner" chapters through to more interesting ones that are very relevant in today's software development arena - the scaling of agility.

Things that stand out in my memory of this book are the application of valuable software quality and management metrics, and the many strategies that Dean suggests can be used to counter the arguments typical organisational "police" will use to counter the attempt to "go [more] agile" and potentially inadvertently lead to "acceptable failure" or worse, "death march".

Usually corporations do not react to the infiltration of agile practices as they are kept within [small] team perimeters, thereby "flying under the radar". If you have a requirement to scale agile, then by definition you clearly have more people and teams that you are concerned about. There is more visibility and attention from the people who might strategically oppose the changes they do not understand, and/or department(s) or programme(s) it is being attempted in - key strategic people that you never previously even knew existed, nor what their concerns were, are now watching your every move.

http://www.amazon.co.uk: http://www.amazon.com:

Why I recommend Scaling Software Agility:

Reason 1: Part 1 covers the essentials of Agile, Waterfall, XP, RUP, Scrum, Lean Software, DSDM, FDD in 85 pages!

Reason 2: Part 2 follows with more depth about the 7 Agile Practices that work: Agile Component Team, Agile Planning and Tracking, Iterations, Small Frequent Releases, Agile Testing, Continuous Integration, Retrospectives.

Reason 3: The 7 practices Leffingwell recommends for Scaling Agile:
- "Intentional Architecture": Approaches on how to tackle large software systems with Agile Architecture
- "Scalable Lean Requirements": Three simple topics that avoid analysis-paralysis failure mode: vision, roadmap and just-in-time (JIT) elaboration
- "Systems of Systems and the Agile Release Train": how to plan, and deliver, complex software components with interdependencies
- "Managing Highly Distributed Development": It is very difficult, and is a problem all successful software programmes face. Sooner or later the team is too big to fit in 1 room, on 1 floor, of 1 building, of 1 city, of 1 country. Inevitably practices have to be developed that can assist software that is developed by many different people, in different locations
- "Impact on Customers and Operations": How marketing, or product owners, or programme owners, will be convinced that Agile is a good thing for them
- "Changing the Organisation": How to address the arguments and fallacies that the corporate immune system is going to throw around as things become more agile
- "Measuring Business Performance": Real, usable, useful management metrics that can be used to control and manage large scale [agile] software development efforts

Thankyou for reading my recommendations!

Building Scalability and Achieving Performance

This is a short and very useful read on scaling architectures - InfoQ got 3 key architects with backgrounds in Twitter, eBay and Betfair to share their experiences, some approaches they take, and some tool recommendations. Practical advice! Building Scalability and Achieving Performance.

Tuesday, 1 July 2008

Cool way of visualising Eclipse's history

Here is a little video visualising the various Eclipse players over the years checking source files, documents and images into the repository. It makes for a few minutes interesting viewing! Eclipse Code Swarm (short version)

A smarter SMART for even better collaborative Objectives (including OKRs)

My favourite coaching tools: SMART Acronym Another Update