Blog

On Software Quality and the Next Big Language, Part I

In this post, I’ll attempt to explain one of the most important principles in software engineering and show how it affects the design and use of programming languages.

Nature of Software Quality

Software quality is still one of the great challenges in the industry. Due to the inherent and unique nature of computer programs, there are no universally accepted methods for ensuring apparent program quality and even fewer practical methods for program verification. However, both of these are not only very important, but also are related. Furthermore, final product quality takes much more than ensuring good source code quality: there is the user interface and overall user experience, documentation, etc. Quality has to permeate the whole project, not just the software and implicit byproducts. There is requirement analysis, customer communication, project management, support. Customers are not seeking quality software tools for their problems—what they really want is quality solutions to their problems. That aside, my scope here is limited to the source code quality.

Curiously enough, many professional developers seem to be able to distinguish intuitively between a poorly written program and a quality one. Yet most tools capable of producing comparable, numeric values somehow denoting a specific, qualitative aspect of source code are frowned upon. This is not because such tools are useless; the problem is that it takes more than a glance at the final aggregated figure(s) to really benefit from them. In particular, static source code analysis takes you only so far. Many important aspects in the source code do not deal with syntax but with semantics, something of which our computers are blissfully ignorant.

The Principle

I have often pondered a question that an apprentice programmer might pose: What is the single most important thing I should learn to become a Good Programmer? Recently, I think I have found the answer. It is the principle known as Separation of Concerns (SoC). It seems that I am not alone. To put it loosely, SoC means that a module, class, function, or method in a computer program should deal with only one thing, with no functional overlap with others. It is closely tied to the concept of loose coupling, and is considered so important that most programming paradigms aim to help developers achieve SoC.

All design patterns separate the specific composition of objects and functionality as a whole (the pattern) from the objects’ individual, domain-specific functionality. Procedural programming helps achieve SoC by separating concerns into procedures, whereas object-oriented programming uses objects for the very same purpose. The famous MVC pattern is based on the very core idea of SoC by separating the actual logic (model) from the user interface (view and controller). Functional programming allows complex composition of functions irrespective of function arguments, thus separating actual data structures from program flow. Splitting methods to smaller pieces by level of abstraction is applying SoC.

Many other ideas derive from or result in SoC as a direct consequence, such as “A class should have one and only one reason to change.”

Impact on software

The benefits of SoC are numerous. First of all, it helps us understand any given computation unit (module, class, method or function) because it deals with only one “thing.” Second, it makes encapsulation and hiding easier, as a change in one unit usually does not require changes in other units. Third, it makes automated testing much easier, as both the state set-up for the test and testing the outcome are simple: given this input, expect this output (or state change, but not both). As such, SoC is one of the core tools in managing software complexity. [1]

What about performance and reusability? Because SoC leads to indirection and due to extra instantiation and chained method calls, performance does suffer a bit, at least in principle. But the benefits far outweigh the incurred costs, as it allows for very aggressive local optimization without fear of breaking other parts of the system. Increased orthogonality and tightly restricted functionality lead to more reusable components as well.

However, sometimes applying SoC is very difficult. There might be pervasive features such as logging and security that are used throughout the project. Maintaining loose coupling and avoiding repetition could amount to anything between trivial design issues to very difficult ones, depending on the language and/or supporting libraries. For example, the Ruby language makes it relatively simple to, say, decorate certain types of calls with custom callbacks without modifying the source code of affected computation units. This is because Ruby has very powerful metaprogramming facilities such as dynamic method definition, re-openable as well as executable class definitions and method introspection. On the other hand, programs written in Java usually require AOP-style extensions such as AspectJ to achieve similar results, adding extraneous dependencies and new syntax to learn.

The point is that if SoC is so crucial, then surely the programming language should help the developer in achieving it, even coerce the developer to that direction. On the next part, we’ll see what kind of programming language would be ideal from this point of view.

References

[1] http://www.cs.au.dk/~eernst/papers/splat03.pdf

Leave a Reply