Railway Oriented Programming

Refactoring procedural code to ROP in Kotlin

Hari Krishnan
polarizertech
4 min readFeb 26, 2020

--

Attribution: I, Daniel Schwen / CC BY-SA (http://creativecommons.org/licenses/by-sa/3.0/)

Procedural code can become hard to read, hard to test and more importantly hard to maintain. Sometimes error conditions and “not so happy path”s tend to govern the codebase to a large extent. We are left searching for the happy path in such applications. The usual approach I may have taken to refactoring long and highly conditional code would be to go with OOP to decompose it into classes and leverage polymorphism or patterns to remove if conditions. However, are there other approaches?

Railway Oriented Programming (highly recommended article) is an approach that applies functional programming techniques to keep code focused on the happy path without sacrificing error handling. This post is about the steps I took to refactor procedural code to functional paradigm.

It may not help to show procedural code and directly jump to the functional equivalent. What will be interesting is to trace the steps we need to take to refactor our code. So we will be going over each incremental step to refactor a method written in procedural style to ROP.

This post assumes little to no Functional Programming knowledge. We will only briefly touch function composition.

Sample Problem

Requirement: Write a “matches” function in HttpRequest which allows us to verify if it matches another instance.

  1. Input: HttpRequest A and Http Request B
  2. Output: Whether A and B match and if they do not match, return error message that mentions which part did not match.

The request parts should be matched in the order given below

matchUrl -> matchMethod -> matchBody

Example: If the method did not match, it should return below error immediately. The body should not be matched.

Method did not match. GET not equal to POST

If everything matches we should get a success result.

Procedural

Good old procedural code with a generous serving of “if” conditions.

Observations

  1. Even with the “guard clause” style programming the error handling code is becoming quite a distraction.
  2. At this point it takes some time and effort to glean out the happy path of this code which we mentioned earlier.
  3. Such code easily decays into chaos if we need to add exception handling and more if conditions.

Refactoring to ROP

Step 1: Method Extraction

The code is currently just a long list of “if”conditions. Let us start by extracting methods that embody major parts of this code, matching url, matching method and matching body.

Observations

While this code looks a little nicer, it is hardly a picture of the happy path.

  1. The when blocks are more or less just if conditions. We are also having to check the result of each step.
  2. Repetition of returning on Failure.

Step 2: Setting up the pipeline

If you stare at the code long enough, you would have noticed that all these functions (matchUrl, matchMethod, matchBody) are very similar in what they accept as parameters and what they return as result.

  1. Argument: HttpRequest
  2. Return: Result.Success or Failure

We will be able to build a pipeline of methods if we return the HttpRequest on Success so that it can be passed as argument to the next method.

So going by this requirement (highlighted in bold) we need to make matchUrl, matchMethod etc. return the HttpRequest when the matching is successful, so that we can pass it to the next function.

Observations

The code is hard to read because we have to deal with “return on failure”. The HttpRequest inside Success has to be extracted and passed to next method.

Step 3: Extracting the Error Handling

We need a way to extract “return on failure” and on success, the code that extracts HttpRequest from Result.Success to a separate function to avoid repeating ourselves.

Explanation

  1. “then” is handling “return on Failure” — In line number 9 when the result is Success, we are calling “f” with this.message (HttpRequest). Here “f” is function that needs to be called next. Example: matchMethod is the “f” to the result of matchUrl. On error we just wrap the errorMessage in a Failure and return.
  2. “pipeTo” is starting the series of functions by passing the Http request to matchUrl. To begin the pipe we call pipeTo which wraps the “anotherHttpRequest” as a Success and sends it to “then”. “then” realises that this is a Success, extracts the httpRequest inside the Success and calls matchUrl with it.
  3. “error” is called when any of the functions matchUrl, matchMethod or matchBody (these functions can be thought of as junctions) return Failure. This behaves like the switch in the railway track and immediately moves execution to error when any of the junctions return Failure.

Kotlin infix helps us call the functions without “.” or parentheses.

Recap

  1. Method Extraction — extract major code sections as functions
  2. Setting up the pipeline — refactor these functions to return the parameter that they take in as input
  3. Extracting the Error Handling — compose these functions such that error handling is extracted to the ROP code

There are more ROP constructs that you can experiment with once you have got the basics.

The code is available on GitHub.

--

--

Hari Krishnan
polarizertech

Full Stack Developer, Architecture Consultant, Lean/XP Coach and Trainer. https://about.me/harikrishnan83