TPP Topic 10: Orthogonality

steadbytes profile image Ben Steadman Updated on ・4 min read

This post originally appeared on steadbytes.com

See the first post in The Pragmatic Programmer 20th Anniversary Edition series for an introduction.

Challenge 1

Consider the difference between tools which have a graphical user interface and small but combinable command-line utilities used at shell prompts. Which set is more orthogonal, and why? Which is easier to use for exactly the purpose for which it was intended? Which set is easier to combine with other tools to meet new challenges? Which set is easier to learn?

Command-line utilities are more orthogonal than GUIs. The command-line utilities are independent of each other - separate programs, with separate codebases and developers. They can be independently updated or replaced entirely without affecting the others.

Generallly, command-line utilities are easier to user for exactly the purpose for which they were intended. This is because (often) that is all the program does and nothing more - as such it can be difficult to use for anything but the intended purpose.

Command-line utilities are far easier to combine with other tools to meet new challenges. These programs typically use standard streams for I/O, allowing them to be composed through redirection to accomplish a larger task. GUIs, on the other hand, are almost impossible to combine as they do not provide standard methods for I/O - one cannot easily pipe the output of a textbox in one GUI to a textbox in another for example.

Challenge 2

C++ supports multiple inheritance, and Java allows a class to implement multiple interfaces. Ruby has mixins. What impact does using these facilities have on orthogonality? Is there a difference in impact between using multiple inheritance and multiple interfaces? Is there a difference between using delegation and using inheritance?

These facilities improve orthogonality. Multiple inheritance for example allows common functionality to be implemented in within multiple smaller classes and added into other classes without re-implementation within that class. Changes made in the inherited classes will be present in any classes inheriting from it - potentially removing the need to change multiple classes.

Multiple inheritance allows 'sharing' of implementation - the actual code to perform some action (which can of course be overridden). Multiple interfaces allow 'sharing' of specification - the intended API for which a class must adhere to without the actual implementation. There's a lot more detail and nuance to this of course, but I think these are the main points.

Delegation allows the implementation of certain functionality to be kept within a class specifically for that functionality, yet enabling another class to use or expose that functionality. This maintains orthogonality by avoiding adding extra behaviour to a single class.

Exercise 1

You’re asked to read a file a line at a time. For each line, you have to split it into fields. Which of the following sets of pseudo class definitions is likely to be more orthogonal?

class Split1 {
    constructor(fileName) # opens the file for reading
    def readNextLine() # moves to the next line
    def getField(n) # returns the nth field in the current line


class Split2 {
    constructor(line) # splits a line
    def getField(n) # returns nth field in current line

Split2 is more orthogonal - it only implements the behaviour for splitting a line into fields and is not concerned about fetching the line from some source (i.e. a file). This allows the source of the lines to be changed without needing to alter the field splitting class and allows it to be used in other contexts. For example, one could re-use the class for splitting lines of logs streamed over a network from a log aggregation API (i.e. AWS CloudWatch GetLogEvents) or for splitting lines of a web page fetched via an HTTP request.

Exercise 2

What are the differences in orthogonality between object-oriented and functional languages? Are these differences inherent in the languages themselves, or just in the way people use them?

In general, the enemy of orthogonality is high coupling between supposedly independent modules/sections of a software system. Both object-oriented and functional-languages provide ways to increase and decrease this, depending on how they are used.

Functional programming tends to use a large number of small, usually pure, independent functions and compose them to build up larger modules of functionality. This decreases coupling and improves orthogonality as each function is independent and can in theory be re-used and changed without affecting the larger module. However, these functions operate on data structures, transforming them and producing results which are fed to other functions. This can produce hard to spot coupling between the functions - changing the data can lead to change across many functions.

Object-oriented programming provide classes which encapsulate modules of functionality, in theory making them independent of one another. However (as previously discussed) multiple inheritance, interfaces, subclassing/overriding and a host of other language features can lead to increased coupling and decreased orthogonality - classes inheriting unneeded methods from a parent, changing a parent class method implementation and breaking all the subclasses etc.

In summary, I think the level of orthogonality between the two paradigms primarily comes down to the way people use them. However, in my experience the way in which functional languages are used tends to be more orthogonal than object-oriented.


markdown guide