You are currently browsing the Sticky Bits blog archives for December, 2011.

The Five Orders of Ignorance

December 30th, 2011

It’s not often you read a paper that has something unique and fresh to say about a topic, and expresses it in a clear and concise way.

Somehow, Phillip Armour’s The Five Orders of Ignorance had eluded me, until I found it referenced in another paper.

It really is an interesting point of view on software development.  You can read the paper here.

Armour’s central tenet is software is a mechanism for capturing knowledge. That is, (correct) software is the result of having understood, and formalised our knowledge about the problem we are developing.

Clearly, at each stage of the development process we have different levels of knowledge (or ignorance; the conjugate of knowledge) about our problem.  As we move towards delivery our knowledge increases; and our ignorance decreases – hopefully!

A stratified, or layered, model of ignorance gives a good measure of our progress through the development – in some ways a far superior model than the traditional time/artefact/activity–based approach.

Armour’s levels – or orders – of ignorance are as follows:

Zero Order Ignorance is knowledge; something we know and can articulate (in software)

First Order Ignorance is something we don’t know; a question we need an answer to.

Second Order Ignorance are the things we don’t know we don’t know.  That is, we don’t even know what questions to ask.

Third Order Ignorance is lack of methodology – we don’t have techniques, tools or processes that can identify and illuminate our lack of knowledge.

Fourth Order Ignorance means we don’t even know there are orders of ignorance!

(In many ways Armour’s work is a far more cohesive version of Donald Rumsfeld’s infamous “Known Knowns” speech.)

Armour’s paper crystallised a couple of very important points to me:

Why requirements analysis is so vital.

For nearly the last decade I have been promoting the importance of requirements analysis as a key part of development.  If we understand the problem we are meant to solve – completely and with precision – developing a solution in software is relatively straightforward. 

It’s heartening that most engineers are actually pretty good at developing solutions. But they’re not really very good at understanding problems. When people call me in to help with ‘design issues’ it’s most commonly the case they don’t actually understand their problem properly. Usually, I help their ‘design’ skills by doing detailed requirements analysis with them!

I have found the teams that spend most time performing requirements analysis spend the least time designing and debugging and have the most comprehensive and maintainable solutions.  This is because their software captures the system knowledge efficiently and their code isn’t riddled with what Armour calls ‘unknowledge’ – irrelevant, or incorrect knowledge about the system captured as code (you know, the stuff that leads to ‘features’!)

What process is all about.

Processes are a technique to give you questions, not answers. I think this upsets many developers (and their managers).  Many people want handle-turning solutions: Feed in some customer requirements, crank the handle, and out comes lovely, pristine software. 

Unfortunately, but the world doesn’t work like that.  If it did, we’d all be replaced by machines (that’s been threatened since the Sixties and it hasn’t happened yet. I’m not holding my breath, either.) 

Every software problem is unique and full of those delicious little subtleties that make our jobs as embedded developers so interesting (and yes, you can take ‘interesting’ in the sense of the old Chinese curse!) There is simply no way you can mechanise the behaviours needed to elicit, understand and formalise all the knowledge required to develop a typical embedded system.

Most approaches to software process description assumes software development is a (linear) mechanical process; and the (procedural) transformation of input artefact to output artefact will (somehow) produce working software.  Whilst this approach works for other manufacturing processes it cannot deal with the simple fact that software development is about knowledge capture and, well, we often don’t know what we don’t know!

The best processes are those that consist of a set of goals and a corresponding set of methodologies.  The goals effectively give you an appropriate set of questions that must be answered before you can continue; the answers to those questions will yield pertinent information about the system. 

One could argue the artefacts are supposed to embody the appropriate design questions but engineers are notorious for simply filling in the blanks with banal waffle just so they can move on to the interesting stuff – that is, hacking code (and learning about the system!)

Overcoming Name Clashes in Multiple C++ Interfaces

December 23rd, 2011

Interfaces

One of our key design goals is to reduce coupling between objects and classes. By keeping coupling to a minimum a design is more resilient to change imposed by new feature requests or missing requirements[1].

An Interface represents an abstract service. That is, it is the specification of a set of behaviours (operations) that represent a problem that needs to be solved.

An Interface is more than a set of cohesive operations. The Interface can be thought of as a contract between two objects – the client of the interface and the provider of the interface implementation.

The implementer of the Interface guarantees to fulfil the specifications of the Interface. That is, given that operation pre-conditions are met the implementer will fulfil any behavioural requirements, post-conditions, invariants and quality-of-services requirements.

From the client’s perspective it must conform to the operation specifications and fulfil any pre-conditions required by the Interface. Failure to comply on either side may cause a failure of the software.

Read more »

Effective Testing: The “Swiss Cheese” model

December 20th, 2011

Why do we test?

Software development is a collaborative effort. You bring in input from customers, managers, developers, and QA and synthesize a result. You do this because mistakes in requirements or architecture are expensive, possibly leading to lost sales.

When we develop products we want them to be successful. We want our customers to buy, enjoy, and recommend our products or services. In other words, we want to build quality products. In this case, quality means four inter-related things:

  • Compliance quality– Can we demonstrate that the software fulfils its requirements?
  • Intrinsic quality – is the product robust, reliable, maintainable, etc?
  • Customer-perceived quality – How does the customer react to our product on a sensory, emotional level?
  • Fitness for Purpose– does our product fulfil the stakeholder’s needs?

Which of these types of quality is most important to us depends a lot on our problem domain and the goals and driving forces of our business.

We take considerable pains (but not enough in most cases – but that’s a different argument!) to analyse, design and implement our system to meet our quality goals. Unfortunately, engineers are not perfect. They are under increasing pressure to deliver more features, in less time, and as a consequence they do not have the luxury of time to learn the problem domain, or examine their solutions.

As a consequence engineers make mistakes. These mistakes appear as corruptions in the translation from a specification (what to do) to an implementation. An error can occur wherever we translate a specification to an implementation.

clip_image002

Figure 1 – Typical errors in embedded systems

 

This raises an important question – how to you measure confidence in a product? That is, how do we know the quality of our product is good enough that we can release it for production?

The simple answer is: we test.

Or, perhaps more correctly: we perform validation and verification activities.

A note on definitions

for this article I will use the following meanings for validation and verification. I’m being explicit because every development group I’ve spoken to uses a different word (or combination of them!) to describe the same concepts.

Verification

Verification confirms some predicate about the system. Verification confirms the existence (or non-existence) of, state of, value of, some element of the system’s behaviour or qualities. Put simply, verification asks questions that yield a yes/no answer.

Validation

Validation is focused on assessment of system predicates, rather than their confirmation. In other words, validation asks the question: should the system actually perform a particular behaviour or have a particular quality. Validation assesses the system in terms of good/bad.

Just to confuse matters, I will use the terms validation and verification (V&V) and testing interchangeably.

Introducing the “Swiss Cheese” approach to testing

Testing is often the last resort for finding mistakes. A requirements specification is produced, a design created and implemented. Then, testing is used to verify the design against the requirements and to validation both the design and the requirements. Testing cannot be a perfect technique. Done poorly, many faults will still remain in the system.

image

Figure 2 – The cost of finding – and fixing – a bug

 

This is a well-known graph. It shows the (relative) cost of finding – and fixing – a fault in a system throughout the lifecycle of the project. In simple terms it shows that it can cost orders of magnitude more to find and fix a problem at the end of the development lifecycle than during the early stages; and even more once the project is in service.

A key point to note is: Testing does not improve your software. Testing is not about proving the software has no faults. (This is impossible!) Testing is a quality improvement technique. It provides evidence – in the form of testing metrics – to support the engineers’ claims (that their design works and is valid).

Just testing the software doesn’t make it better:

• Testing just identifies the faults

Fixing the faults makes the software better (although, not always!)

And simply writing more tests won’t make your software better – You must improve your development practices to get better software. The more testing you do the better your product confidence should be – provided you perform good tests!

Closing the holes (improving) testing requires effort and costs money. Moreover, the more sophisticated, rigorous and detailed you make any particular testing technique the more it will cost. In fact, there is a non-linear increase in the costs of applying a technique:

image

Figure 3 – The cost of improving any particular testing technique

 

Trying to ‘perfect’ one technique – for example, black-box unit testing – is not a cost-effective way to gain product confidence. Using multiple, independent techniques (each playing to their own strengths) is far more effective.

The “Swiss Cheese” approach to testing uses multiple techniques, each with a different focus. The techniques are applied with the clear knowledge that no technique is perfect (nor should it be) but the flaws in any one technique do not overlap (much!) with the flaws of another layer.

image

Figure 4 – the Swiss Cheese model

 

The Error-Fault-Failure chain

clip_image010

Figure 5 – The Error-Fault-Failure chain

 

The Error-Fault-Failure chain shows the propagation of mistakes through the development lifecycle. Mistakes made by engineers lead to errors; errors are manifested as (latent) faults – code just waiting to go wrong!; in some cases a fault may lead to the system deviating from its desired behaviour – a failure.

Clearly finding latent faults before they become failures is more effective than just trying to detect failures; and finding errors that lead to latent faults is more effective still.

Dynamic Testing

Dynamic Testing focuses on identifying failures.

Black box testing is concerned with measuring the correctness of the system’s behaviour, performance or other quality. Black box testing is therefore primarily a verification technique.

Black box testing tests specification without knowledge of implementation. The unit under test is stimulated via its interface(s). Black box testing requires a complete, consistent unambiguous specification to test against.

Black box testing typically involves isolating a subset of the system and executing it in isolation in a simulated environment.

White box testing, or Coverage testing is about establishing confidence in the structure of the code. Coverage testing focuses on the rigour of the dynamic testing process.

Coverage testing is not concerned with verification or validation but with ensuring all code has been adequately exercised. The motivation is that code that has not been executed during testing may have faults in it; and it we don’t check that code the faults may manifest themselves when the system is in service.

Dynamic testing involves execution of the software in a (simulated and, later in the develop cycle, live) environment.

clip_image012

Figure 6 – Environments for executing dynamic tests

 

Dynamic testing does not find latent faults effectively. The amount of effort required to find potential failures in your code is extremely high.

Static Testing

Static testing, using Static Analysis tools, look for latent faults in code.

Static Analysis tools is a generic description for tools that aid verification without having to execute software.

There are (currently) more than three dozen tools on the market. Most are commercial tools, but there are many academic-based tools. There are a small number of free- or share-ware tools available, also.

clip_image014

Figure 7 – Categorisation of static analysis tools

 

These tools are nearly always language specific – that is, there are tools for C, C++, Java, etc.

They are commonly based on compiler technology, since the initial stages of static analysis are the same as those required for compiling code.

Pattern Matching tools

Pattern matching tools look for dangerous expressions. .Grep-like tools search for known incorrect patterns. They can be useful for simple latent faults and enforcing style standards in code.

Semantic Analysis tools

Semantic analysis tools are based on compiler (parser) technologies. They use the first stages of the compilation process to build an Abstract Syntax tree. The parse tree in enhanced with additional semantic information. The enhanced Abstract Syntax tree is then evaluated against a rulebase looking for violations.

Symbolic Execution tools

Symbolic execution tools look at data-flow analysis. Symbolic execution tools are less concerned with language level constructs and instead focus on how data is created used and destroyed.

Abstract Interpretation tools

Abstract Interpretation involves treating the code as an abstract mathematical characterisation of its possible behaviour. The model is then executed to examine its behaviour (for faults).

There is a considerable overlap between Abstract Interpretation, Symbolic Execution and Semantic Analysis in most commercial tools.

In practice most commercial tools use a number of techniques, both to provide better analysis and also to minimise the shortcomings of each of the techniques.

 

clip_image016

Figure 8 – There is considerable overlap between commercial SA tools

 

Most tools will have a bias towards one particular technique. When choosing a tool it is prudent to consider where the tool’s bias lies and whether that suits your usage of static analysis techniques. In some cases you may consider buying more than one static analysis tool.

Static Analysis tools focus on the use (or misuse) of programming language. They cannot make any meaningful judgements on design issues – for example cohesion, coupling, encapsulation of abstraction flaws in the system’s design.

Review

Judgements on design, system evaluation, measures of good/bad, and other aspects of validation cannot be automated easily. The most effective technique to find mistakes in these aspects is human review.

In the past reviews have focused on (attempting to) find latent faults in code by detailed inspection. Unfortunately, this is something humans are particularly bad at so it becomes an activity that either 1) takes a long time or 2) is very poorly executed (that is, merely given lip-service).

The strength of human review is the ability of humans to build abstract representations and evaluate them. Thus reviews should focus on the things tools can’t automate – design, modularisation, evaluation, intrinsic quality, etc.

Applying the Swiss Cheese model

In the Swiss Cheese model no technique has to be perfect. Each technique is used for its strengths with the knowledge faults not found by one technique may be caught by another.

Some faults will still get through, though!

clip_image018

Figure 9 – In this Swiss Cheese approach no technique needs to be perfect

 

The aim is to spread our effort across multiple techniques to get the most effective result for the minimum effort.

Consider: If we are not particularly diligent with any particular technique, such that each technique only finds 50% of the mistakes at each level, with three different levels we have the potential to find 87.5% of the mistakes in the system. And this without disproportionate effort in any area. (and yes, I know this is a somewhat optimistic and simplistic scenario)

More appalling user interface design

December 16th, 2011

I came across a wonderfully counter-intuitive piece of user interface design this week.

The room I was in had a sliding shutter (that, for reasons best known to the architects, opened into the main building and not outside).  The two halves of the shutter are controlled independently – that is, you can close one side or the other, or both.  Each shutter is controlled with independent switch panels.

Common sense would suggest a single rocker switch: pushing one side would close the shutter; pushing the other would open it.  The designers, however, had other ideas and selected the implementation below:

Annotated interface

 

Each shutter has a pair of single-action switches – one to close the shutter (the one at the top) and one to open the shutter.

Pressing the top switch (on its right hand side) closes the shutter – as expected.

Pressing the bottom switch on its left hand side (the intuitive action) does nothing.  In fact, you have to press the bottom switch on its right hand side to get it to do anything.

Even better, the switch panel for the right shutter is an exact copy of the the left; so the controls are completely opposite  – the top switch opens the shutter, the bottom switch closes it!

As they say:  good design is like oxygen – you don’t notice it until it’s not there.

%d bloggers like this: