Monday, 12 November 2007
What does it mean for software to be elegant? When I write code, elegance is something I aspire to, and in some senses goes hand-in-hand with beautiful code, but that doesn't really make it any clearer. Certainly, I think there is a strong element of "elegance is in the eye of the beholder", but I think there are also some characteristics of the software that are contributory factors — how a particular person may rate the code on any aspect may vary, as may the importance they place on any given aspect, but these aspects will almost certainly impact how elegant the code appears.
Factors affecting the elegance of software
Here's a short list of some of the factors that I think are important. Obviously, this is not an exhaustive list, and all comments are my opinion, and not definitive.
- Does it work?
- I'd be hard-pushed to call software "elegant" if it didn't work
- Is it easy to understand?
- Lots of the following factors can really be summarised by this one: if I can't understand the code, it's not elegant.
- Is it efficient?
- A bubble sort is just not elegant, because there's lots of much more efficient algorithms. If a cunning algorithmic trick can drastically reduce the runtime, using that trick contributes to making the code elegant, especially if it is still easy to understand.
- Short functions
- Long functions make the code hard to follow. If I can't see the whole function on one screen in my editor, it's too long. Ideally, a function should be really short, less than 5 lines.
- Good naming
- Short functions are all very well, but if functions are called
wrt_lng_dt, it can still be hard to understand the code. Of course, this applies to classes just as much as functions.
- Clear division of responsibility
- It is important that it is clear which function or class is responsible for any given aspect of the design. Not only that, but a class or function should not have too many responsibilities — by the Single Responsibility Principle a class or function should have just one responsibility.
- High cohesion
- Cohesion is a measure of how closely related the data items and functions in a class or module are to each other. This is tightly tied in to division of responsibility — if a function is responsible for calculating primes and managing network connections, then it has low cohesion, and a poor division of responsibility.
- Low coupling
- Classes and modules should not have have unnecessary dependencies between them. If a change to the internals of one class or function requires a change to apparently unrelated code elsewhere, there is too much coupling. This is also related to the division of responsibility, and excessive coupling can be a sign that too many classes, modules or functions share a single responsibility.
- Appropriate use of OO and other techniques
- It is not always appropriate to encapsulate something in a class — sometimes a simple function will suffice, and sometimes
other techniques are more appropriate. This is also related to the division of responsibilities, but it goes beyond that — is
this code structure the most appropriate for handling this particular responsibility? Language idioms come into play here: is it
more appropriate to use STL-style
std::sorton an iterator interface, or does it make more sense to provide a
sortmember function? Can the algorithm be expressed in a functional way, or is an imperative style more appropriate?
- Minimal code
- Code should be short and to-the-point. Overly-long code can be the consequence of doing things at too low a level, and doing byte-shuffling rather than using a high-level sort algorithm. It can also be the consequence of too many levels of indirection — if a function does nothing except call one other function, it's getting in the way. Sometimes this can be at odds with good naming — a well-named function with a clear responsibility just happens to be able to delegate to a generic function, for example — but there's obviously a trade-off. Minimal code is also related to duplication — if two blocks of code do the same thing, one of them should be eliminated.
One thing that is not present in the above list is comments in the code. In my view, the presence of comments in the code implies that the code is not sufficiently clear. Yes, well-written comments can make it easier to understand a given block of code, but they should in general be unnecessary: truly elegant code can be understood without comments. Of course, you might need to understand what it is that the code is trying to accomplish before it makes complete sense, particularly if the code is using advanced algorithms, and comments can help with that (e.g. by providing a reference to the algorithm), but my general view is that comments are a sign of less-than-perfect code.
Let me know what you think constitutes elegant code.