1 Apr, 2010
As many of you know, I run a company called 3aims Ltd and one of those aims is simplicty. For a long time I've thought it was fairly obvious what simplicity is. After all it is easy to spot something that is simple so surely it should be easy to work out the factors that make that thing simple and those that don't?
Of course, I'm particularly interested in simplicity in code design in Python and I've had a few thoughts about it. The most important of which is that:
simplicity is in the mind of the beholder
That is to say, in order for something to be simple, it has to be simple to the way of thinking of the person that has to understand it. Because of this point I believe the following statements are true:
Code is simpler when:
It is written in a language where there aren't a myriad of different ways of achieving the same thing
It uses well-known libraries where possible
It uses standard design patterns in an orthodox way
It meets code standards for that language
These points all ensure that that an arbitrary member of the community reading the code is as likely as possible to be able to follow the code and find it simple.
Writing simple software is therefore a careful balance between intrinsic simplicity and re-using existing knowledge. Sometimes it is better to write software that is intrinsicly slightly more complex if your users will find it simpler because its approaches are more familiar. (On the other hand the reverse is sometimes too, the benefits of simplicity justify making your community learn new patterns).
Beyond the point that simplicity is in the mind of the beholder I do think there are some intrinsic things that most beholders would agree makes something simple but before I go on to explain them I want to explain a thought I've had about complexity and this is that:
complexity can never be eliminated, only refactored
Time and time again when I've tried to make something simpler without limiting the scope of the problem I've found out hours or days later that I simply shifted the complexity of the problem elsewhere. Project Managers often don't realise the above rule so in trying to get developers to meet the next deadline by "making something simpler" they are invariably just shifting the complexity to another place where it will impact later and probably won't have been estimated for.
There is hope though, because just as when working with algebra, if you can refactor a difficult set of problems you can often make life easier for yourself. For example, if you can make your problem look like a set of documents, you can offload the complexity of managing those documents to a database like CouchDB or MongoDB. If you can make your problem look like neat tables joined by foreign keys you can offload a lot of the complexity to a RDBMS like PostgreSQL.
by refactoring you may be able to offload some complexity onto an existing component, making your life easier
I guess I like to think that one of the things that I like to think makes me slightly different from some other developers I meet is that I've written and re-written so many libraries over the years that I have a very good idea of where complexity lurks in web applications and how it can be refactored to be offloaded to simplify the remainder of the problem.
One of the central tenets of simple software design is to keep things in small pieces with specific purposes. This is applies to functions, modules and entire applications. This is a brilliant approach because it means one part can be easily replaced and improved upon without having lots of unforseen consequences elsewhere. It also makes things easier to document, test etc etc.
It is very easy to make something that is easy to use without it actually being simple. Software can have this property just because all its pieces have been glued together in a way that happens to suit your current purpose. This isn't really simplicity to my mind, rather it is just a headstart with the gluing.
don't confuse a simple design (which usually comes as lots of small pieces) with one that happens to do eveything you want; the latter will help you win the battle, the former will help you win the war
This is one reason I wasn't a fan of early versions of Django. Everything was very closely tied together despite the "loose coupling" advertised on the project's website (facts/actions speak louder than words). To my mind Django wasn't simple per-se, rather it was pre-glued. This isn't a critisim, just an observation. Pre-glued things make it easy to build other things that rely on the pre-glued structure but much harder to build something different using the same pieces (which isn't something you are supposed to do with them anyway!)
[Just to really keep digging I don't consider Pylons either simple or pre-glued but the way I intend to fix that will come in another post]
Whilst offloading complexity is one way to make the rest of a problem appear simpler, and using a pre-glued component is one way to trade complexity in the future for quick success now, if you are writing a libary, another approach is simply to bury the complexity under a layer of abstractions, exposing only a subset of the complexity to the user of that library. This is an approach I took in my early days of library writing. Unfortunately this only works if you can guarantee that the user of the library will never need any of the more advanced features you've buried. There are two reasons why this isn't likely to be true:
The features are unlikely to have been developed if they are absolutely never needed
Change is such an important aspect of most development projects that even if the abstractions work now, you can't guarantee they will in the future
There's another problem though, abstractions prevent you having full control over what you are doing. They are like oven gloves in that as long as they are used for short periods in the correct way they usually prevent you from getting burned but also in that they don't guarantee you won't get burned and in that they make it harder to actually handle the things in the oven.
In short:
abstractions cannot keep you safe from the detail forever so are best avoided if possible
If it isn't possible to avoid an abstraction I think it is very important that an API be designed in layers so that once you reach the limitations of one layer you can always drop to a compatible, lower-level API.
I'm quite aware that I haven't really given any specific rules for determining simplicty, and indeed I don't think there are any since it is in the mind of the beholder anyway. But there are a few ways you can get a feel for whether something is simpler or not.
The first and best way to test whether something is actually simpler is to ask yourself:
does this feel like the most correct solution to this problem possible?
If the answer is no then you don't have a simple solution. There's a few reasons why you might answer no:
You might feel there are some short-cuts in the implementation, or some edge cases which aren't catered for in which case you've just ignored the complexity and hoped it will go away. It won't
You might feel that although it is a complete solution it could be done better if it were achieved in a different way, or depended on different tools. This is perfectly valid, it just means that there is a better way to refactor the complexity.
In short:
if the code doesn't feel the most correct it can be, it is unlikely to be the simplest solution
Another test, recommended to me by Ben Bangert, is the shortnest test:
if you re-implement something in a simpler fashion it is unlikely that the simpler code will be longer
Of course there are a number of reasons this test might be slightly misleading but it is a good rule of thumb. Potential problems are:
the different versions might have a different code, documentation or naming convention that naturally leads to more or less lines of code. Try to take this into account before making your comparison.
the original version might not have dealt with the full complexity of the problem. In that case your new version might well be simpler and longer but you'd need to check that the extra features are actually reauired, after all, why were they not in the original if they were needed
If you pass both the correctness test and the shortness test that is a good first step, but because simplicity is in the mind of the beholder you should show your test results to a representative number of people who might use your software and check that they agree that your solution is both more correct and shorter. If it is, it is likely to be simpler too.
I hope I've given you some more ways to look at simplicity and some tools to analyse whether something is really fundamentally simpler or whether the intrinsic compleaxity has just been moved somewhere where you don't notice it. In my next blog post I want to talk about another idea I've had where you can factor out complexity by choosing a subset of tools. This can force complex problems to all look the same, a technique I've been finding very valuable in my own code.
Copyright James Gardner 1996-2020 All Rights Reserved. Admin.