loading...
Cover image for Orthogonality

Orthogonality

fluffynuts profile image Davyd McColl ・5 min read

Definition

Orthogonality is a mathematical term referring to lines which are 90° to each other:
Graph with two lines, AB and CD. AB is at 90° to CD. The intersection between the two is labelled and the graph has a label "AB and CD are Orthoganal"

Seems abstract

The concept applied to software engineering may seem initially abstract, but the idea is quite simple: the axes on a graph are orthogonal to each other and provide the example we need: if we consider two axes, x and y:

Basic x/y graph without any data series plotted. Looks a bit like graph paper.

We can see that if we only alter x, moving in any direction, we don't require a change in y. This is because x and y are orthogonal, and this is the point we're getting to in software terms:

Orthogonality in software refers to parts of the system which are not directly related. Often these parts interact with each other, but changes to one do not directly affect the other.

Getting practical

A common example of orthogonality is your business logic versus how your data is stored. For a business which sells items and needs to generate invoices, it doesn't really matter where we store invoices and line items -- flat files (never underestimate the humble flat file!), local database, Amazon s3 buckets, whatever. This is a common place where the principle applies. For example, if you're using Entity Framework (.net) or Knex (Node), you could quite easily swap out the method of storage (MSSQL, MySQL, Postgres) or even radically change the method of storage (Mongo, CouchDb) without having to change other layers in the app -- if you've ensured that there's a layer of abstraction in between those interacting layers.

Consider: if we write traditional ADO.NET code (with newer C# syntax, because it's neater!):

var customers = new List<Customer>();
using var conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Main"].ConnectionString);
using var cmd = conn.CreateCommand();
cmd.CommandText = "select * from customers where is_trial = 1";
using var reader = cmd.ExecuteReader();
while (reader.Next())
{
  customers.Add(MapRowToCustomer(reader));
}
return customers;

Then we're quite tied into using MSSQL and ADO.NET here, especially if we have code like this scattered throughout the codebase. If you were using EF, this would be hidden from you:

  • no need for each part of the code to know where to find a configured connection string
  • no need to know how to open a connection
  • no need to know about the command/reader semantics of ADO.NET
  • no need to Dispose all of those (the clean new syntax hides 3 Dispose calls!)
  • no need to know the correct SQL dialect to perform the query

for example:

using var db = new Context();
return db.Customers
  .Where(c => c.IsTrial)
  .ToList();

As with everything, there are trade-offs -- at Codeo, we take a bit of a middle stance, using a CQRS pattern with commands / queries which have explicit SQL in them, so there are definitely parts of the code which are coupled with the database implementation. This has been done in the name of performance. But the rest of those concerns are tucked away in a common set of code.

Another common example is presentation (eg HTML, CSS, XAML) vs logic (eg JavaScript, C#, SQL). Even in the logic arena, we can have well-defined areas of abstraction which interact but changes in one don't (or shouldn't) cause changes in another.

A counter-example in the real-world is the control system of an helicopter: changes along one axis of movement require compensation along others. Becoming an helicopter pilot is no trivial task!

Orthogonality goes hand-in-hand with keeping things DRY because as common concerns are abstracted from code, we often find that a good abstraction will make the consumer unaware of the nuts-and-bolts of the code at the core of that abstraction.

Benefits of observing orthogonality

  • Isolation of common logic allows for across-the-board improvements or changes without affecting other parts of the system
  • Avoid vendor lock-in: if you've kept orthogonal concerns well-separated with abstract interfaces between them, it will be easier to swap out parts when necessary to address concerns of pricing or performance
  • Easier to test: when you don't have to spin up an entire functional "world" for tests, you can write smaller, faster tests that are:
    • more likely to be run (faster)
    • less likely to break when the system changes (maintainable)
    • will better show where issues are introduced (more focused)

Design

Whilst programming for orthogonality, observe:

  • The "S" in SOLID (single responsibility)
    • if a piece of code is doing too much, chances are good it's crossing orthogonal boundaries
  • Layering of your application
    • Instead of shoving all code into one giant file, consider the many types of layered approaches which exist, some of the simplest and well-known being:
    • Onion architecture
    • MVC / MVVM
    • This layering can be applied to documentation too
    • Consider keeping presentation and content separate: write your documentation in a markup language which can be rendered neatly later (eg Markdown)
  • Keep code de-coupled
    • Reveal the least necessary to let parts of the system interact with each other
    • Consider the law of Demeter
    • Instead of changing state on objects, ask them to do so; in this way, when a behavioral change goes hand-in-hand with that state change, it can be accomplished within the class where the change is required, instead of outside code having to do so
  • Avoid global data
    • Global data becomes a point of coupling between different parts of the system. If you have a global static ApplicationSettings class which everyone is looking at, you can't test parts of the app in parallel with different configurations. If you have an injectable IApplicationSettings object, you can fake out the values for settings without having to actually back them from a database or wherever your settings come from. The settings themselves are orthogonal to the storage!

The takeaway

Whilst you're busy solving all of your requirements, always keep a look-out for orthogonal concerns and see how you can keep them from becoming entangled. This will provide you more freedom to change your system when required to do so.

You will find it easier to keep orthogonal code apart and DRY if you're constantly critical of your own code. Always look for neater ways to get things done.

Discussion

pic
Editor guide