I was recently challenged about one of our corporate programming guidelines - "Separate the logic from the interface where possible." On the face of it, it seems such a common-sense rule to me, that I actually struggled to explain why it is a rule.
Separation of Interface from Implementation is something drilled into programmers as a Good Software Engineering Principle(tm). It is one of the bricks that make up the wall of Software Engineering, the wall being the dividing line between Programming as a Discipline and Programming as a Hobby (read "hacker" here). Along with "Never Use Goto" and "Use Meaningful Variable Names" it is a Pillar of our Community.
What a collection of Capitals! The indication is that by following these "guidelines", our programs will be readable, maintainable, robust, delivered on-time and within-budget (Capitalise at will ;->). But it can't be as simple as that, can it?
No, it can't. Unless we question the motivation behind such principles (and this applies to the broader perspective outside Software Engineering, too), we will not have understood the spirit of the rule, only its letter. If we don't understand the context within which the rule was born, and its differences from, and similarities to the context in which we are working, then the rule has no meaning.
The use of meaningful variable names is a real case in point. First off, what is meaningful? Apart from differences of spoken language and culture (I'm English, therefore round_robin is a meaningful name. It probably loses something in translation to, say, Danish), there are issues of context, scope and maintainability to consider. Most people I'd guess consider the following acceptable in C++:
for (int i = 0; i < 10; ++i) //...
What is i? On its own, it's meaningless. Given context and scope, it becomes obvious that i is a numeric value used as a counter. Does this mean that i is a good example of a meaningful variable identifier? Conversely, is counter_of_contained_dibblys any better? Would the judgement change if the variable were at function scope? Would the size of the function matter? Etc.
Some old compilers and languages suffered from similar problems to Old DOS. Creating meaningful file names in MS-DOS 6 and before could be painful; the limitation of 8 characters (even the 3 letter extension was OS or Application property) resulted in a whole culture of file-name dreaming. A directory with lots of files with similar naming conventions produced, by necessity, names like:
and the naming convention would be near incomprehensible to anyone but the author. Even the author might struggle after a few weeks! Variable names have one very similar property to files; no two may have the same name within the same scope. If the size of the scope has inverse proportion to the restriction on identifier length, you'll get incomprehensible names. QED. Modern environments (well, ones I have used in the last 5 years or so) have fairly liberal rules on name length. I think the most restrictive was 31 characters. But then, that's no excuse for counter_of_contained_dibblys.
When I first learned Windows Programming, Charles Petzold's book of that title was the recommended (very nearly the only) text for beginners in the field. His take on variable naming conventions seems roundly denounced (outside of Microsoft Press publications) now, but to me, as a novice, and to the University Lecturers under whom I studied, his arguments were convincing, and Windows Programs used the Hungarian Notational style of variable naming.
Given that Windows API Programming is based upon a single monolithic switch statement, it seemed sensible to explicitly identify the type of the variable at every mention; however, my own notions of good style have changed dramatically since those days, and it appears to me now to be a band-aid trying to cover the limitations of the wider method in use (as endorsed by Petzold himself).
And yet. Petzold's "Windows Programming" is a seminal text. Being able to compose a Windows program from pre-built components still doesn't remove the occasional necessity of understanding Windows at a lower level. The point is, as with the Software Engineering Principles, taking anything at face value is dangerous, but not as dangerous as not knowing about it in the first place.
The use of goto in almost any language is, to put it mildly, the subject of some debate. I remember distinctly one of my first lessons in using C as a language. I was told that C contained a keyword "goto" which behaved much like the keyword of the same name in BASIC. I was then told to forget it exists. It's fair to say I've never used the goto keyword in a C or C++ program. But that's not to say I won't out of some congenital fear of doing so.
The use of goto is frowned upon because it can be misused. But then, it's not the only keyword that can be misused, not even in C++. Used with care, and under the right circumstances, using goto can improve the clarity of your code. For the best example of this I've seen, look up the Programmer's Stone (http://www.reciprocality.org/Reciprocality/r0/index.html), Chapter 4, under the section called Coding Standards and Style Guides.
Which brings us to the purpose of all this - separating interface from implementation.
I first encountered this principle when learning to write Abstract Data Types in Pascal. As in C++, the idea is to have a published section of your code (the header) and a private section containing the actual logic of the code. It's familiar enough to C++ programmers (and C programmers) as the .h/.c pair. The C++ class is much closer to the Pascal ADT than a C struct or function library, but the principles remain the same.
Our rule about keeping interfaces separate from implementations isn't really focussed on the concept of ADTs, or classes, however. It is focussed on the interface between GUI and Application layers in windows program. Specifically, programs written for MS Windows are based around events; button clicks, menu selections, timers, selections, key presses, and a whole host of others. Using C and the Windows API, these events are handled in the monolithic switch statement previously mentioned. Using MFC, event handlers are member functions of the class used to represent the window. C++ Builder and Delphi handle events similarly. We use C++ Builder here, and skeletons of these event handler members are generated automatically.
In these conditions, our rule is extended to say "don't put logic code in event handlers". It was this point upon which my questioner pounced; given that an event handler is just a function, why shouldn't it contain logic? It's certainly much easier to do that, than create a new "Unit" (a .h/.cpp pair), write another function and have the event handler call that.
I couldn't disagree. It's far simpler to do the former. Even in the face of the classic maintenance argument. Consider a window with a button on it. Clicking the button is intended to perform some task. It has an event handler, which is easy enough to find, and contains the code to implement this task. Thus, maintaining the code is easy, because all the necessary code is available in the event handler. If the handler called another function, in a different module, finding the necessary code involves at least one more step, and it's easy to lose your thread if the function calls nest too deep...
So, it's time to step back, and consider the why of the principle. There are many reasons for keeping logic separate from interface, including maintainability, or wanting to keep proprietary code private. These are side issues, however. The underlying point is that code which depends on implementation details is liable to break if the implementation changes. The solution to this is Abstraction. Abstraction is achieved by what? An interface. Code which uses only an interface will not break if the implementation changes.
Library writers (well, most of them) understand this, but application developers too often do not. Sure, they use interfaces all the time; it's a good Software Engineering Principle - don't rely on the implementation of the library, use its interface. But when it comes to writing application code, it doesn't matter; the only client code is mine, and I know when things change. It's easy to put the logic in the event handlers; even easier when you don't have to type out the declarations for that function. You then end up with an Implerface.
The main issue with this is duplication. Your window with a button also has two text fields, which are intended to contain dates. Clicking the button validates the entries in these text fields before continuing with its task. Later in the project, another button is added, which does a different job, but has to validate the dates in the text fields. If the validation code was in the event handler for the first button, you have two choices: duplicate it or factor it out into a function.
Duplication of the code is just plain stupid. Factoring it out means not only testing the functionality of the new button, but re-testing the functionality of the old one, to ensure your surgery hasn't introduced errors. If the validation code had been made a separate function, accessed via a controlled interface, in the first place, re-testing the old button would be unnecessary. Similarly, if changes are required to the date validation function, it can be tested in isolation. The success of these tests can be confidently passed on to the functionality of the buttons.
A trite example, for sure, but a real-world one nonetheless.
There are, as with "Never Use Goto" and "Use Meaningful Variable Names", economies of scale. There are few benefits in factoring out a single line of code to a function, unless it is a long line, is used in many places, and the function name is short ;)).
The point is that we should think about what we are doing, rather than blindly following the rules. Having explained why we made a rule out separating interface from implementation, I then had to explain the valid exceptions to the rule! But then, that very fact brought a thought. I was being asked the reasons behind a rule about code style, and I struggled to explain. My interrogator was doing everything right - questioning the rule. I on the other hand, had clearly not really thought about it. Think about that.