Overloading or obfuscation?
What is the key objective when you write some code? The obvious answer is that you want to impart some specific functionality to the device that you are programming. That would be fine, if just designing and writing code was all that a software engineer was required to do. There are several programming languages that would enable code to be written very rapidly. An example, that I have an affection for, is Forth. With this language you can crank code very quickly indeed and it can be quite efficient, particularly on memory footprint. The problem comes when you try to read the code later. I have heard people describe Forth as a “write only programming language” and I can see their point.
This is the crux of the issue. Developers only spend a small proportion of their time writing new code. They expend a good deal more effort maintaining and enhancing existing software. So, the real answer to my initial question should be that you are communicating the design of your algorithms and procedures to the poor guy who will maintain the code at some future point [which may, of course, be you!].
So, writing clear, well commented code is vital. This rather puts the nail in the coffin for Forth, but opens up some possibilities in languages like C and, more so, C++ …
The key to writing clear, easy to understand code is to aim for its functionality to be obvious by making everything as intuitive as possible. Identifiers should have meaningful names – that is a good start. C++ classes should behave in obvious ways. Things like enums should be exploited widely. Also, operators should be overloaded to make the code more readable.
Operator overloading is very neat. When you define a class, you specify some data [member variables] and operations that might be performed on/using that data [member functions]. You can also include some special member functions that implement the standard C++ language operators [like +, –, *, >>, etc.] for your class. For example, imagine you have two objects, instantiated from the same class, called alpha and beta and you wish to add beta to alpha [we do not need to know exactly what “add” means in this context]. This might be done using a member function like this:
alpha.add(beta);
But this is not very intuitive. An alternative is to overload the += operator so that you can write:
alpha += beta;
This is much clearer. You could also overload + so that the operation could be coded this way:
alpha = alpha + beta;
Ideal for all frustrated Basic, Pascal and Fortran programmers.
Although operator overloading can be used to clarify code, it can also be used to make it harder to understand. For example, when I was first learning C++, I read in a book that overloading + could be used to write code like this:
alpha + beta;
I was very confused as this should just be an expression, the result of which is discarded. Then I realized that it was really the += operator that should have been overloaded, as this was the functionality imparted to the + operator.
Generally, overloading operators should be performed so that the resulting use is consistent [or identical] to the original meaning of the operator for standard data types. This is the case in the examples above. However, there are exceptions. I am not sure how I feel about the standard overloading of << and >> for stream I/O objects, like this:
cin >> i; cout << i * 2;
This accepts an integer and outputs the result of multiplying it by 2. Normally << and >> are used to denote bit shifting.
What do you think? Do you have any good examples of clear or obfuscated code to share? Please post a comment or drop me an email.