For the past year or so, I have been working on a new technique for creating software. I was frustrated with the disconnect between the efforts of development teams (both those I have been on and those I have helped manage) and the outcomes the software was providing when it was actually used.

TDD and other technical practices (exemplified by the Software Craftsmanship movement) are great for validating the correctness and quality of the code, but how do we establish the validity and quality of running software for its intended use? (No, I don’t believe ATDD solves that problem. I’ll dive into the differences in a bit).

Outcome Driven Software (ODS) represents a new way to create software. In short, it is a way to make software by first creating the demonstrative outcomes that will prove validity, then using those outcomes to drive the creation of the functional systems to support them.

Part process, part technique – ODS is still a journey, not a destination. My hope is that others can contribute to its refinement and evolution, as well as the production of tooling (similar to xUnit frameworks) that aid in the process.

Change Your Perspective: Software Is Massless

Before diving into the practical application, there are a few key concepts and insights I want to run by you. Much of this I have not seen mentioned elsewhere, if you know of other sources or pioneers of the past who have been here before, please let me know.

The first thing may seems obvious, but is not at all reflected in the habits of modern software development: software is what runs on hardware when it is used by its users. Please read that one more time.

It’s not about your code, your development process, your tests, your continuous integration or deployment, your build process, your stories, your fixtures, your methodology, your tools, your IDE or any of that stuff far removed from what it actually is and what’s really important and that’s those human beings sitting at the other end who use and live through what is actually your software.

I use the mantra “Software Is Massless” as a reminder that it’s electrons running through the CPU and I/O activity and impulses from input devices and to display devices – all being exercised by, or at the bequest of, users aka human beings. And what those humans want more than anything else from your software is positive emotional outcomes. They want to feel good about your software and they want to feel good knowing they got what they wanted out of your software.

Beyond validity, ODS is also designed to improve the quality of outcomes – moving beyond acceptable or satisfactory and instead delighting and exceeding expectations. Ultimately, the quality of software depends on the creativity and empathy of those involved in creating it. However, ODS structures the process to allow this to happen more freely and as an upfront requisite before system design.

ODS is complimentary to core XP technical practices, though it fundamentally changes the act of coding and largely reduces the amount of TDD required.

ODS is a replacement for Acceptance Test Driven Development (and other derivatives such as BDD and Specification by Example). The intense focus on stakeholder conversations and collaboration from these techniques is directly applicable. However, the practices are different in two regards:

  1. ODS creates running software instead of separate fixtures or artifacts that only run in the development shop and are not part of the software as it is used.
  2. At the start, ODS explicitly avoids system design and focuses only on outcomes. In the “Given, When, Then” parlance of BDD, ODS starts by creating “Then” to the exclusion of “Given” and “When” which are created from “Then”.

Understanding the Difference between Functional and Demonstrative Parts of Technology

One way to think about any technology is as a progression from inputs to outputs and then from outputs to outcomes.

Realize though that the boxes and arrows are somewhat misleading because what we actually create are the arrows not the boxes:

The progression from input to output occurs through a functional mechanism (or functional system if you like). This translates quite easily into code, for example consider the following:

function add(a, b) { return a + b }

Given an input of 1 and 2, the output will be 3. The “rightness” of input/output relationships is usually expressed via “test”:

assert.equal(3, add(1,2))

Understanding the progression from output to outcome can be more difficult. This is because the outcome exists at the intersection between the running software and its user(s). Meaning outcome can only be understood as running software, not as code. Expanding the add example:

function add(a, b) { return a+b }
var prompt = require('./console-prompt')
prompt(['First number:', 'Second number:'], function(addends){
   var a = addends[0], b = addends[1]
   console.log(a + ' + ' + b + ' = ' + add(a,b))
})

it will yield the following when run:

C:\node\ods>node add.js
First number: 1
Second number: 2
1 + 2 = 3

Given the simplicity of this example, we can consider the entirity of the three lines:

First number: 1
Second number: 2
1 + 2 = 3

to be the demonstrated outcome. But to simplify for a momment, let’s just look at the last line presented to the user:

1 + 2 = 3

as the outcome. The demonstrative system (meaning the part of the software that produces this output to the console)  produces this ‘artifact’ to provide validation of the outcome. This artifact of outcome can be judged qualitatively. It could have been:

Your answer is 3

Or as is too often the case today, the output could also have been provided directly (technically the REPL facilities are being leveraged to deliver the outcome via an on-screen presentation of the number) with no attempt to consider outcome:

3

In this very simple example as presented, this may be fine. But imagine the potential frustration if the originally provided addends were not visible and the user had to re-run the software. The important thing is the conversation and collaboration to discover the best demonstration the outcome. The ‘right’ thing to do is bound to context and focused conversation with users and stakeholders on the outcome reveals all the subtle nuances and details.

It is also very critical to realize that the discussion about outcome can occur without the functional system. This fact is exploited by ODS: outcomes are discussed and determined early on and the functional system is only built after the purpose of the software is understood.

[If you’re wondering about more complex software, ODS can be applied at multiple levels. Software systems, and system here includes input all the way through outcome, can have nested sub-systems.  For example, the prompt for addend can be thought of as a sub-system. This example is so simple there is hardly any difference between the output and outcome other than display of the number as it is produced.]

The Outcome-First Process

In the add example, as I mentioned, because it so simple we can consider the outcome as the total record of supplying and adding the numbers. So let’s assume that for the process of adding the number 1 and 2, we work with the user to come up with the following:

C:\node\ods-add>node addition.js
First number:  1
Second number: 2
1 + 2 = 3

This is implemented very simply as:

var colors = require('colors')
console.log('First number:  1')
console.log('Second number: 2')
console.log('1 + 2 = 3'.green)

Notice that this is a fully runnable, complete software. The user is not being asked in the abstract or conceptual about what they want. No tables or fixtures are being created. They are evaluating real software in its real target environment. The delivery and deployment can also be exercised.

It just only covers the specified example (adding the numbers 1 and 2). Only the outcome needs to be produced, allowing rapid iteration and interaction with the user to find the best possible outcome.

Once there is agreement on the outcome, the next step is to pin this outcome and set up a test that will check that the same outcome is being produced. This will allow making changes that allow us to safely move backwards in creating the rest of the software.

Just like with xUnit for TDD, some basic tooling can help with this process. ODS utilizes tooling that can do photocopy testing. Photocopy testing is the process of capturing the representation of the outcome as an agreed upon ‘standard’ and then being able to compare subsequent outcome production against the captured ‘standard’.

For this CLI example, I’ve created a simple utility that can both pin the outcome and test subsequent executions against that tooling. My main focus is on web development and I have blueprint, a web-based ODS tool for web application development. The process works fine for command line apps as well. It just goes to show how much detail is exposed by focusing on running software versus developing code.

C:\node\ods-add>node test.js addition.js -p
pinned addition.js outcome to addition.outcome.txt
saved outcome:
First number:  1
Second number: 2
1 + 2 = 3

With the –p pin option, it saves the stdout output to a text file. Without the pin option, the program tests the outcome against the expected result:

C:\node\ods-add>node test.js addition.js
First number:  1
Second number: 2
1 + 2 = 3
----------
passed
----------

Combining this with watch enables a continuous real-time feedback mechanism. When the program is modified (and the file saved) we will know immediately if we are no longer producing the agreed upon outcome, for example if we change the last line to:

console.log('1 + 2 = 4'.green)

we see:

C:\node\ods-add>..\watch\watch node test.js addition.js
First number:  1
Second number: 2
1 + 2 = 3
----------
passed
----------

First number:  1
Second number: 2
1 + 2 = 3
----------
passed
----------

failed!

Expected>>First number:  1
Second number: 2
1 + 2 = 3
<<
Actual  >>First number:  1
Second number: 2
1 + 2 = 4
<<
----------

Working backwards is essentially a refactoring effort from static to dynamic input. In ODS, the first step is to extract the output from the outcome. In practical terms, that means creating a view and a model (not showing the first three lines):

console.log('%s = %s'.green, '1 + 2', '3')

One difference for me in working backwards from a fully functional outcome, versus building up an output, is that I see the totality of what is being described. So I’ve decided to isolate the addends as a unit, rather than discrete numbers.

var addends = [1, 2]
,  sum = 3
console.log('%s = %s'.green, addends.join(' + '), sum)

With the simplicity of this example, we’ve not only isolated the output (sum), but the input as well (addends).  Now the functional mechanism can be added:

var addends = [1, 2]
,  sum = addends[0] + addends[1]
console.log('%s = %s'.green, addends.join(' + '), sum)

You can take this in many directions. You could create an add method:

function add(a,b) { return a+b }
var addends = [1, 2]
,  sum = add(addends[0], addends[1])
console.log('%s = %s'.green, addends.join(' + '), sum)

Or maybe a class:

var addition = {
 ,  addends: [1,2]
 ,  sum: 3
 ,  toString: function() {
       return util.format('%s = %s'.green,
       this.addends.join(' + '), this.sum)}
}

console.log('%s'.green, addition)

Or an event emitter/service approach:

calculator.on('sum', function(addition) {
  console.log(formatter.render(addition))
})

ODS provides focus on and awareness of code design. Various designs can be tried and discarded at low cost.

Keeping this example simple, the last step is to implement the input mechanism:

var colors = require('colors')

var prompt = function(prompts, cb) {
  console.log(prompts[0] + '1')
  console.log(prompts[1] + '2')
  cb([1, 2])
}

prompt(['First number:  ', 'Second number: '], function(addends) {
  var sum = addends[0] + addends[1]
  console.log('%s = %s'.green, addends.join(' + '), sum)
})

Now the ‘real’ library can be swapped in (but…):

var colors = require('colors')
,  prompt = require('./console-prompt')

prompt(['First number:  ', 'Second number: '], function(addends) {
  var sum = addends[0] + addends[1]
  console.log('%s = %s'.green, addends.join(' + '), sum)
})

But, the test now needs to be modify to provide the inputs (full code on gitub):

var answers = ['1','2']
child.stdout.on('data', function(data){
  buf.push(data)
  process.stdout.write(data)
  var answer = answers.shift()
  if(answer) { writeLine(answer) }
})

And the test passes:

C:\node\ods-add>node test.js addition.js
First number:  1
Second number: 2
1 + 2 = 3
----------
passed
----------

Of course, there is the working software:

C:\node\ods-add>node addition.js
First number:  34
Second number: 15
34 + 15 = 49

Reviewing the Steps of the ODS Outcome-First Process

Reviewing, here are the foundational steps:

  1. Establish an agreed upon outcome (go big and create delight!)
  2. Pin the outcome via test
  3. Extract the inputs into the outcome
  4. Outcome inputs are also the (functional) system outputs
  5. Work backwards through the functional system to the inputs

Project is available on github here.

You can also check out an Event Registration example I did about a year ago here.

Advertisements