Please Use Loosely Coupled Pure Subroutines : How To Subdivide Programs

Some people get bent out of shape about superficial style choices in code.  Whenever you work with someone new there is always the “Okay, curly braces should be after a newline” conversation.  I have yet to find someone who writes code in such a way that it makes a difference for me, I don’t care where you put your curlies.  The things we should care about are substantial style choices that make code more reliable and easier to maintain.  For example I think it’s hardly controversial to say that we should name variables so that they match their semantics.  “count” is a better variable name than “c”, just as “widgetCount” is better than “count”.

There is another style consideration in the same vein with which I thought we were all on the same page.  Or at least in short time everyone would be, but it’s been 8 years since I’ve made this revelation and I haven’t seen a change in practices.  So I’m going to cry it out to the world in hopes that people adopt it.  If there was one thing I could tell every programmer that I think would help the State of Software the most, it would be this.

Please use loosely coupled pure subroutines.

Every programming language worth talking about has support for subroutines, all the way down to the assembly level (6502 assembly jsr opcode, c functions, java methods).  We all have to make the decision constantly while programming of when and why to break certain logic into subroutines.  How do you do it?  Let us refer to the Linux Kernel Coding Style document, they have alot of important code with many maintainers, they should have a good idea:

This is more or less the rule I’ve adopted, hopefully you have something similar running through your brain.  Maybe this is controversial, maybe I should be happy stopping here so we can all get on the same page, but no, I’m going to push forward and assume we all agree with some version of this rule.

Different languages have different faculties for limiting scope and mutability, C has Blocks for Structured Programming, Java has Objects with private fields, C++ has ‘const’, etc.  Generally, why do languages have these features?  Because managing context and capabilities in code allows us to work with complex code efficiently by dividing our code into small chunks that are easy to reason about.  When we divide our program into subroutines we can do better by limiting their context and capabilities.  We do this by using loosely coupled Pure Functions.

(For those who don’t know what Pure Functions are, they are functions who given the same input arguments always return the same value no matter the calling context and they cause no side-effect.  Loosely coupled in this context means the subroutines should have the minimal set of information needed to make their calculations, also check out Information Hiding.)

For the purposes of our discussion we’ll say that functions that appear pure by contract are as good as pure.  We don’t care if you have side effects on an object that you created and no one else can see, we aren’t Haskell.

Enough abstract talking, lets bring this principle back to a real language, C#.  C# unlike Fortran or Nim doesn’t have explicit support for pure functions and private methods pass around all the state of the object.  How can we use this principle?  By building our methods from loosely coupled pure static functions:

CalcBar and CalcBaz are loosely coupled pure functions.  What have we gained by dividing our code this way?

  • The temporal coupling between CalcBar and CalcBaz is explicit at the call site in Start, this means Joe Programmer coming back into Start will be less likely to futz that up.
  • CalcBar and CalcBaz can be evaluated on their own merit, all the code and possible data required to understand the code is right between the curlies.
  • CalcBar and CalcBaz are easily and directly testable.  Maybe Foo.Start is difficult to test but there is no excuse not to test CalcBar and CalcBaz if you want to.

Right about now OOP heads are starting to get mad at me.  Cool down my babies, let me explain.  Objects as an abstraction are very stateful, they have public methods with void return types.  If you are working with OOP that’s just how things are done.  I’m not suggesting you write all your code functionally and ditch OOP.  Keep building objects’ public interfaces however you were doing it.  I’m merely suggesting you build those public methods on solid functional footing by dividing them into collections of loosely coupled pure functions.

Now I understand that depending on your situation pure functions might be hard to swing, like I said earlier apparent pure functions are just as good, and loosely coupled functions are better than methods.  You just have to choose the most limiting construct for your subroutine.

If you haven’t read Andrew Hunt’s “The Pragmatic Programmer” I highly recommend it.  It boils down to a list of tips for programming that are useful no matter what tools you are programming with (here is the list tips).  I’m now going to list all of his tips that are apropos to programming with loosely coupled pure functions:

  • DRY—Don’t Repeat Yourself – when you divide your code into loosely coupled pure functions you remove required context which makes your code more reusable
  • Eliminate Effects Between Unrelated Things – this one should be pretty apparent, pure functions are orthogonal by definition
  • Always Design for Concurrency – pure functions factor out state change which makes them easier to reason about in threaded code since they can’t have race conditions
  • Design to Test – public pure functions are easier to test, all you have to do is call them with their input and check the output

If you are already using a functional language, chances are you blew off this article paragraphs ago, but according to TIOBE you probably aren’t.  I honestly think this is the one easiest thing people can start doing to make their code easier to maintain, easier to write, and less buggy.

Leave a Reply

Your email address will not be published. Required fields are marked *