YAGNI-C as a Practical Application of YAGNI

 
Author:  Follow: TwitterFacebook
Job Title:Sarcastic Architect
Hobbies:Thinking Aloud, Arguing with Managers, Annoying HRs,
Calling a Spade a Spade, Keeping Tongue in Cheek
 
 

ALGOL 68 was the first (and possibly one of the last) major language for which a full formal definition was made before it was implemented.

— C.H.A. Koster —

… as a tool for the reliable creation of sophisticated programs, the language [ALGOL 68] was a failure …

— C.A.R. Hoare —

The YAGNI (‘You aren’t gonna need it’) principle is well-known in the agile world, going back to XP (as in ‘eXtreme Programming’, not ‘Windows XP’) in the end of 1990s. Unfortunately, this concept is too open to interpretation, which causes lots of confusion and heated debates both in industry [Fowler04] [Devijver08] [Litzenberger11] and in academia [Boehm02].

You Aren't Gonna Need It!

This article describes a practical approach to YAGNI, which has been tried in practical agile projects (one of which has had releases to millions of customers every 2–4 weeks). For the purposes of this article, we’ll name it YAGNI-C (as ‘YAGNI-Clarified’). While not being universal, we hope that YAGNI-C might be useful in quite a wide range of projects. Oh, and if somebody is about to say “Hey, we’ve been doing exactly the same things for years” – of course, YAGNI-C is not something really new; the problem is that such practices (which we think are best practices) are not often described, and therefore cannot be widely used.

The very beginning

Dreaming hare:The story starts when we’re about to start our new agile project to make our super-duper app.The story starts when we’re about to start our new agile project to make our super-duper app. The first question is – are we going to have some architecture? In general, it depends, but let’s consider projects where architecture is essential, so the answer is ‘yes’. The second question is – within the architecture chosen, are we going to have our own set of libraries (let’s name them ‘infrastructure libraries’) which needs to be common for a significant part of project? Again, in general, it depends, but let’s assume that in our project (for example, due to the project size/complexity) we’ve decided to have such a set of infrastructure libraries (it may be just a glue, or something more substantial – it doesn’t matter too much, the key thing is that these libraries are supporting a big part of the whole project). Now, let’s assume that APIs to these libraries are designed and supported by one or more people, let’s name them ‘library API maintainers’ (with a hope that there is at least one of the architects involved in this group). Now let’s try to define some principles and procedures of how ‘library API maintainers’ should approach YAGNI within our YAGNI-C model.

Thinking, not implementing

The First Principle in YAGNI-C is that

Thinking ahead is good, implementing ahead is bad
YAGNI-C First Principle

While the second part of the First Principle is actually YAGNI in its pure form, the first part of the First Principle may need a bit more of explanation. There are numerous complaints out there (see, for example, [Fowler04]) that YAGNI is (or at least can be) misused to the point where any thinking about architecture is prohibited, and the project becomes a mess of ad hoc tactical decisions. Hare thumb up:As long as you can think about design without starting to implement it – you’re fine, anything beyond that is over-designThe other side of the spectrum (library which does everything in sight) is also well-known to have led to disasters (ALGOL 68, DCE RPC, and especially X.500 are good examples of the over-designed systems which were so complicated that nobody was able to implement them properly). The First Principle above aims to strike the balance between these two undesirable extremes, and in practice it seems to work reasonably well (while in some cases, preliminary proof-of-concept prototypes may be needed before First Principle can be applied, starting from post-prototype development seems to work pretty well).

One other way to look at the First Principle is to rephrase it as ‘as long as you can think about design without starting to implement it – you’re fine, anything beyond that is over-design’. Essentially it restricts the amount of ‘thinking ahead’ to the amount of information which fits into the heads of the ‘library API maintainers’, which is subject to the cognitive limitations of the human brain, so it is fairly limited. In fact, the First Principle is much closer to the ‘very lean’ end of spectrum, while still allowing a certain amount of thinking ahead.

Specific cases

The Second Principle of YAGNI-C is

If nobody in the team can describe a very specific use case for a problem – the problem doesn’t exist
YAGNI-C Second Principle
(and whatever doesn’t exist doesn’t need to be solved). This Second Principle is of extreme importance for the whole process to function. What it allows is the transfer of discussion from the space of “Hey, why are we not using XYZ?” and “Why we don’t support paradigm ABC?” (which are subjects which can easily take months to deliberate on) to the space of very specific use cases, applicable to the current project, and while decisions might be not so obvious, at least it can be reasoned about not from the Swiftian big- and little-endians1 point of view, but from the point of view where at least some logic can be applied.


1 Not to be confused with Intel/DEC little- and big-endian

Prohibit misuses

The Third Principle of YAGNI-C is

If in doubt how it should behave – prohibit it
YAGNI-C Third Principle
If, as a library implementer, you don’t have specification on a certain behaviour (for example, answering “what will happen if x.g() will be called before x.f()” is not specified, and you yourself have doubts about what will happen in this case) – you should prohibit such behaviour (for example, inserting an assertion, but other means are also possible).

This Third Principle is essentially a manifestation of agile principle known as ‘deferring commitment’. In practice, whenever a library is released, people start using it in all kinds of ways, including those ways which were never intended. Prohibiting unintended uses (effectively deferring commitment of library writers regarding these uses) is good for at least two reasons: first, it makes code more reliable (making sure that caller really understands what is going on), and second, it allows implementation details to be hidden as deep as possible, reducing chances that library modifications which don’t change the specification can break the client code.

How it works

Within YAGNI-C, the process of infrastructure API design is as follows.

  1. First, library API maintainers design a very minimalistic API.
  2. Then, people from the rest of the project start to come and say, “Hey, your library doesn’t support this call, please add it.” According to the Second Principle, each such request MUST be accompanied with a specific use case – “WHY this call is necessary?”
  3. This is a point where library API maintainers perform analysis, deciding if a new call (class/…) should be added to the library API. As practice shows for good library design, about 30% of requests from step 2 above are turned down as “You’re solving the wrong problem, what you really need for your use case is…”, another 50% are turned down as “This can be done using existing API as follows:…”, and remaining 20% lead to extending the APIs. And as ‘library API maintainers’ did think about potential requests (see the First Principle), implementing additional calls is normally not that a big deal.
  4. Rinse and repeat from step 2.

It should be noted that YAGNI-C is substantially iterative, and therefore can’t possibly work in strictly-waterfall development environments.

Practical Example

Let’s take a look and see on a specific example, how YAGNI-C might work in practice. Of course, this example is inherently extremely limited and oversimplified, but it still provides a good illustration of the concepts involved.
Let’s assume that Alice is a library API maintainer, and one of the classes she develops, is a class File:

class File {
    FILE* f;
    public:
    File( const char* filename ) {
        f = fopen( filename, ... );
    }
    size_t read( char* buf, size_t bufsize ) {
            /* ... */
    }
    void write( const char* buf, size_t bufsize ) {
         /* ... */
    }
    ~File() {
        fclose( f );
    }
};

Listing 1

This class, as written, has a problem: it has an implicit copy constructor, which, combined with the nature of ~File(), will cause all kinds of problems. Now, Alice faces a question: what to do about it? One thought quickly crosses her mind: to add something like Listing 2:

class File {
    FILE* f;
    //...

    //violates YAGNI-C First Principle:
    File( const File& ff ) {
        f = fdopen( dup( fileno( ff.f ) ) );
    }
    File& operator =( const File& ff ) {
        fclose(f); f = fdopen( dup( fileno( ff.f ) ) );
    }
};

Listing 2

However, she quickly realizes that as there is no requirement to copy class File, this is a feature which would violate our First Principle. Now, to comply with both the First Principle and the Third Principle, she writes Listing 3:

class File {
    FILE* f;
    //...
    private: //copying/assigning of File
             //objects is prohibited
    File( const File& );
    File& operator=( const File& );
    //in C++11, File( const File& ) = delete;
    // and File& operator =( const File& ) = delete;
    // can be used instead
};

Listing 3

This construct prevents other classes from calling the File copy constructor/assignment operator. This implementation is consistent with all the principles stated above.

Some time later, Bob comes to Alice and complains, “Hey, why don’t you support a copy constructor for File?”. Given such a request, it is impossible to judge if it has merits or not, as it is not clear exactly what problem Bob faces; formally, this request violates the Second Principle and is therefore denied. As a next step, Bob elaborates:

The following piece of code doesn’t compile: void f( File ff ) { /* … */ }

Now request is specific enough, but it immediately becomes obvious (at least to Alice) that Bob has just forgot to put & in the function declaration, so his problem can be solved without introducing a copy constructor for the class File.

At some point, Charlie comes to Alice and complains:

Why is the assignment operator prohibited for class File? I want to copy a file and am trying to write:

File f1( filename1 );
File f2( filename2 );
f1 = f2;//compiler error”

Once again, when a specific use case is provided, it is obvious that adding an assignment operator to File would be quite the wrong thing to do to solve Charlie’s problem, so Alice explains to Charlie how he can implement file copying for File (or she may decide to add static File::copy() for this purpose).

Summary

Arguing hare:YAGNI-C is an attempt to clarify YAGNI so it becomes less vague and easier to follow. While YAGNI-C (though not under this name) has been successfully used for agile projects, it is certainly not a silver bullet, so you’ll still think to think if it is beneficial for your project. It should not be considered as a new approach to development (we know many teams which follow these or very similar approaches), but as a new way to describe existing best practices.

Don't like this post? Comment↯ below. You do?! Please share: ...on LinkedIn...on Reddit...on Twitter...on Facebook

[+]References

[+]Disclaimer

Acknowledgements

This article has been originally published in Overload Journal #117 in October 2013 and is also available separately on ACCU web site. Re-posted here with a kind permission of Overload. The article has been re-formatted to fit your screen.

Cartoons by Sergey GordeevIRL from Gordeev Animation Graphics, Prague.

Join our mailing list:

Leave a Reply

Your email address will not be published. Required fields are marked *