Naïve assumptions sound like a bad idea. Sergey Ignatchenko discusses how to spot good assumptions in the face of changing requirements.
Disclaimer: as usual, the opinions within this article are those of ‘No Bugs’ Bunny, and do not necessarily coincide with the opinions of the translator or the Overload editor. Please also keep in mind that translation difficulties from Lapine (like those described in [Loganberry2004] ) might have prevented providing an exact translation. In addition, both the translator and Overload expressly disclaim all responsibility from any action or inaction resulting from reading this article.
There is a common wisdom in the programming world which says that the word ‘naïve’, when applied to anything programming-related, is essentially an insult (often aimed at the code, but usually also towards the author who has written such naïve code); as one of the authors has put it: ‘ I cannot remember a time when a working insightful function has been worse then the naïve equivalent ’ [ Meyer2012 ]. However, as is usual with common wisdom, it is not as universal as it seems to be. There is at least one ‘naïve’ thing in the programming world which not only shouldn’t be avoided, but which should be adhered to, especially in the agile programming world.
Naïve vs NotSoNaïve
Where there’s a will, there’s a way
~ proverb
Let us consider a simple example. Let us consider a hypothetical programming team which needs to write a plane simulator; it is stated in the specification that the plane to be simulated is a Cessna 162. So our team has started the design process, and the original version of the design (let’s name it Naïve Design) has placed the
Engine
as a member of the class
Plane
:
// naive.h class Plane { Engine engine; };
But then one of the younger team members Y (having noticed that a Cessna 162 is a single-engine aircraft, and being in the middle of reading the book
Design Patterns
[
GoF1994
]), said: ‘Hey, why are we not using the singleton pattern here?’ As he referred to the GoF book, which is considered a very reputable authority, the team has agreed to make both
Engine
and
Plane
singletons (see Listing 1).
// notsonaive.h class Plane { public: static Plane* Instance(); private: Plane(); static Plane* m_pInstance; //... }; class Engine { public: static Engine* Instance(); private: Engine(); static Engine* m_pInstance; //... }; |
Listing 1 |
The team (obviously, starting with team member Y) has been very excited about this change, saying that it not only looks much more professional and uses ‘cool’ newer technologies (as opposed to the naive.h approach, which was described as ‘so eighties’), but also arguing that this approach reduces coupling. Although this is a good thing per se (while we won’t comment here whether it really reduces coupling or not, this argument can easily be made and accepted in such context; if there is a will to use a certain pattern – there is a way).
Incoming!
Welcome changing requirements, even late in development.
~ Agile Manifesto
Working hard, our hypothetical team has managed to write the Cessna 162 simulation software, and the simulation project turned out to be commercially successful. One momentous day, the manager addressed the team in very excited tone, saying, ‘We have just secured a contract from the Big Company to extend our simulator to cover the whole line of Cessna aircraft’. It was certainly a good thing from a business perspective, with only one caveat: the whole line of Cessna aircraft includes the Reims-Cessna F406 Caravan II, which is a twin-engine aircraft, and support for twin-engine planes was a strict requirement by Big Company.
Obviously, the singleton
Engine
didn’t allow for twin-engine planes, and unfortunately, dependencies on it being singleton were extensive and spread all over the code. It was at this point that the flight simulator program had to be redesigned almost from scratch, which delayed the software release by more than half a year, which in turn caused this second product to be late to market, and the project was closed pretty soon after the launch.
Moral of the story
The perfecting of one’s self is the fundamental base of all progress and all moral development.
~ Confucius
The moral of this story was never intended as ‘never use singletons’ (though in our experience, singletons tend to be overused more than any other pattern), and certainly was not to imply that ‘design patterns in general are bad’. This story was intended to illustrate two main points:
- that to survive in the agile world, where changes of business task are frequent by definition, changes need to be anticipated (this has been explicitly said in the Agile Manifesto [ AgileManifesto ], so there is nothing new about it)
and
- that ‘naïve’ mapping of the business task into software (such as classes and functions) tends to survive changes to business tasks significantly better than any artificial mappings.
Assumption is the mother of...
As soon as you have made a thought, laugh at it.
~ Lao Tzu
Let us analyze the situation in a bit more detail. What we’re discussing here is essentially a mapping between two domains: the first one is a domain of business tasks, and the second one is a domain of programming solutions. If we take a look at NotSoNaïve mapping compared to Naïve mapping, it is easy to notice that a NotSoNaïve design essentially introduces two major assumptions into the picture: the first assumption is that there can be only one plane within the simulator, and the second one is that there can be only one engine within the simulator. It is the second assumption which was proven to be fatal in the case described above, however, even the first assumption could become bad enough (for example, if Big Company wants a simulator with more than one aircraft).
As usual, it was an invalid assumption which brought the design down. However, designing without assumptions is not usually feasible, so there is a question – how do we distinguish good assumptions from bad ones? Our hypothetical case study has suggested the answer to this question is as follows: only assumptions which are ‘natural’ from the point of view of the business task in hand, are ‘good’ ones. Obviously, this is not a strict definition, and answers may be different depending on who you ask, but still it is better than nothing.
One example of a ‘reasonably good’ assumption for our example of a flight simulator: while assuming that there is only one engine, one plane, and even one airport might be too restrictive, assuming that there is only one object Earth, is probably a reasonably good one (and therefore singleton object Earth is probably not a bad idea). It should be re-iterated, however, that even this ‘reasonably good’ assumption can fail, if, for example, there is a company which may want to use such a simulator in a sci-fi game covering multiple planets. Where exactly to draw the line (assuming that there is only one engine, or only one plane, or only one airport, or only one planet – or there are no ‘only one’ objects at all) – is a judgment call of the design team. What is important though, is that parameters to consider for this judgment call SHOULD include things like convenience and speed of development, and SHOULD NOT include things like ‘using a cool technology’ or (arguably even worse) ‘where could we use this pattern?’ logic.
Naïve mappings and SQL
The key, the whole key, and nothing but the key, so help me Codd
~ unknown
In the relational database world, data design usually starts with direct one-to-one mapping between business domain and programming domain, and ‘non-naïve mappings’ tend to be less of a problem. Still, even in the SQL world, it is possible to make some crazy decisions based on ‘cool’ technologies. One such scenario might happen if at the point of system design our team member Y is in the middle of reading a book on data warehousing; if he is persistent enough and there is nobody in the team who is bold enough to point out how inapplicable this model is to data processing, it may easily result in using ‘cool’ star architecture with fact tables and dimension tables for an operational database. Analysis of the consequences of such a brilliant decision is left as an exercise for the reader.
Summary
To summarize the above in the more formal way:
If we define ‘naïve’ mapping of business tasks into programs, as ‘mapping without unnatural assumptions’, and we define all other mappings as ‘artificial mappings’, and we define ‘unnatural assumption’ as ‘assumption without sufficient justification from the nature of business task itself’, we’re claiming that:
Naïve mappings of business tasks into software design tend to accommodate likely business task changes significantly better than any artificial mappings.
References
[AgileManifesto] http://agilemanifesto.org/
[GoF1994] Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software , 1994
[Loganberry2004] David ‘Loganberry’, Frithaes! – an Introduction to Colloquial Lapine!, http://bitsnbobstones.watershipdown.org/lapine/overview.html
[Meyer2012] Shaun Meyer, Naive Programming, http://wordpress.morningside.edu/meyersh/2012/03/23/naive-programming/
Acknowledgement
Sergey Gordeev from Gordeev Animation Graphics, Prague, has provided the wonderful pictures that have illustrated this series of articles.