Why are Reusable Components so Difficult? (2016)

· 10 min read
Why are Reusable Components so Difficult? (2016)

A couple of weeks ago, Uwe Friedrichsen asked a pretty good question on his blog:

"...Re-use is the holy grail of IT: whenever a new architectural paradigm comes to IT town, 're-use' is one of the core arguments why to go for that paradigm. The business sales pitch typically goes like this: 'Switching to  will cost some money upfront but due to the re-use it enables it will soon pay back'...In short words: re-use based on any architectural paradigm never worked as promised, the promise was always broken..."

He offers up CORBA, Component-based architectures, EJB, SOA -- then asks if microservices will be any different. Why is the promise of re-use broken? Why can't we create truly reusable code?

These are great examples, and Friedrichsen does a pretty good job of going over his version of why reuse sucks so bad. You should read his essay. I believe, however, he misses the key point: classic OO and pure functional programming lead to vastly-different endgames when it comes to reusability because they're based on different assumptions.

Let's perform an experiment. Holding everything else the same, let's implement the game "FizzBuzz" in idiomatic pure FP and OO using F# and C#. That's about as small a code example as I can come up with. First, the F#:

let (|DivisibleBy|_|) by n = if n%by=0 then Some DivisibleBy else None
let findMatch = function
  | DivisibleBy 3 & DivisibleBy 5 -> "FizzBuzz"
  | DivisibleBy 3 -> "Fizz"
  | DivisibleBy 5 -> "Buzz"
  | _ -> ""
[<EntryPoint>]
let main argv =
    [1..100] |> Seq.map findMatch |> Seq.iteri (printfn "%i %s")
    0 // we're good. Return 0 to indicate success back to OS

Looks like about a dozen lines of code. Notice these three things:

  • It's all in "pieces". Nothing is connected to anything. There's a weird thing called DivisibleBy, then there's a chunk of stuff that looks like the main FizzBuzz program. But it's not called from there. Instead, there's this third chunk -- the only "real" line of code, which only one line. If you didn't know any better, you wouldn't know what chunk went with what.
  • At the end of the FizzBuzz game, we usually ask, "What if there is yet another rule?" In this case, it's obvious from the code that you'd just add something to those DivisibleBy lines in the second chunk. Nothing else would change.
  • Once the pieces are in-place, it looks much more fluid as to how the final line would work. If you're a functional programmer, you realize that the final chunk is really up to the programmer. Here I've done it "point-free"[1], which is just piping things from one place to another. But I could have done it several other ways. It really doesn't "do anything" aside from evaluate the sequence and print it. How I choose to do that is up to me. I make various choices in order to minimize my cognitive load.

As an example, for that final line, I might go:

let fizzBuzz n  = n |> Seq.map findMatch |> Seq.iteri (printfn "%i %s")
    fizzBuzz [1..100]

In that case, I've collapsed everything down to one token, "fizzBuzz" that handles everything but the range of numbers. This allows me to change it up.

fizzBuzz [50..200]

I know this sounds trivial but it's not. I made the decisions around the tokens based on anticipated usage in my project. I have wide freedom to join things up -- or not. There is a skill to make tokens that come together in a "mini-language" to solve the type of problem in front of me. I don't write the solution; I write the pieces I then can assemble together in various ways to make the solution.

Now let's look at the C# code.

// from https://stackoverflow.com/questions/11764539/writing-fizzbuzz
namespace oop
{
    class Program
    {
        static void DoFizzBuzz1()
        {
            for (int i = 1; i <= 100; i++)
            {
                bool fizz = i % 3 == 0;
                bool buzz = i % 5 == 0;
                if (fizz && buzz)
                    Console.WriteLine (i.ToString() + " FizzBuzz");
                else if (fizz)
                    Console.WriteLine (i.ToString() + " Fizz");
                else if (buzz)
                    Console.WriteLine (i.ToString() + " Buzz");
                else
                    Console.WriteLine (i);
            }
        }
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            DoFizzBuzz1();
        }
    }
}

For the F# code, I mashed up some code I found on the net. For the C# code, I went to SO and found the top answer to the "How to do FizzBuzz" question. That's the fairest way I know how to handle this.

The C# code is about three times longer. Here's what stands out:

  • It's all in a fixed structure. There's a namespace. There's a class. There's a method. Everything has a place and is part of everything else. Things are where they are for a reason.
  • Looking at the structure, it feels like adding new rules makes things more complex. I'm pretty sure a new rule would involve a new line after those two "bool" lines with fizz and buzz in them, but then we get into that nested if/else-if/else-if/else structure and something's going to have to change there. Very easy to do, of course My feeling is that adding new rules makes things more complex, where with FP we reached a complexity peak and then it was easy. You might phrase that as "with FP we started off generic" On the SO page there was another C# example that offered generic rules, but the other commenters said -- and I agree -- that it looked overly complex. Frankly it looked like a lot of FP crammed into an OOP app. But it was more generic. It's just not the code you'd most likely end up with as a C# programmer. (Remember that code-is-code, and you can just about do anything with anything. The issue today is why coding on average in one paradigm turns out differently than coding on average in another one, not what's possible or what you can or can't do.)
  • It looks much more capable in regards to components and reusability. This is where things get odd. Technically, the C# code should be better at components. We have a namespace. That prevents components from getting mixed up. We have a class, "Program", that allows us encapsulation and data hiding for our work, preventing consumers from having to worry about how we do things. I've made the method static, but even if we leave it static, an object wrapper clarifies that "DoFizzBuzz1" is this particular version, perhaps not the same one as one offered by "Program2" -- or Program with perhaps a different constructor. If nothing else, in OO things have the appearance of being especially constructed for reuse.

In the C# code, I'm not managing tokens to create a mini-language, I'm managing structure to keep my code organized. Things go into certain places in OOP, and we have rules to explain when and where they're supposed to go. It's an organization system.

So on the surface, the C# code should be much better set up to be a reusable component. It's better-organized, after all.

Only one way to check that. Let's build a component.

Can I drop the C# code into another container, say something to render HTML from a server to a client?

Well, not so much. Everything is stuck in the Main method, and the Main method is coupled to the DoFizzBuzz1 method. In addition, the range, 1 to 100, is coupled to the implementation. The class is the way it is because it's coupled to being a C# console app. Most of the difference you see in line count between the two is because the C# app is a template. Everything is stuck together in a tightly-coupled, rigid, yet oddly mutable structure.

Code walkthrough. At the end I get a bit mixed up about components and reuse. The topic here is general reuse. Building components to fit into sockets is another situation with its own problems.

None of that is right or wrong, but we run into three or four problems trying to reuse 30 lines of C# that just gets worse the more code we have: everything is coupled to everything else and mutability makes the linkages impossible to separate. In fact, because objects are both data and code, that's by design. It's supposed to work that way!

[Ed]: Another way of phrasing the thesis in this essay is that in FP, you should naturally be scoped by what the code does. That scoping is airtight. In OO, you should naturally be scoped by a bunch of terms you use to group your code in. There are thousands of different ways to think of those terms and what might belong inside or outside of code grouped using them.

We'd probably want an "HtmlProgram" class instead of a Program class. Maybe we'd want an HtmlRenderer class because, dang it, that Html stuff has to go somewhere. FizzBuzz factory? Enterprise FizzBuzz, anyone?

How about the F# code? Sure. I've got one line of code in main that I'd need to put somewhere else. Everything else is in the global namespace. Using my second example, if I need to change the range, it's easy -- not coupled to anything. In pure FP everything is loosely-coupled until the last responsible moment. Remember, I could juggle those tokens around any way I want right up until that last line. I have wide latitude. OO, on the other hand, requires us to commit to a design early on, as soon as we have enough code and data that indicate it. Otherwise, there's no point in using OO.

It's important to note that this isn't a C#-bashing essay. One of these isn't better or worse than the other one. They simply solve problems in vastly different ways. The OO code scales up to large, monolithic apps where everything has a place. The FP code scales out to tokens that create a DSL that callers can use to do whatever they want using a new language. In OO, I end up with a big honking chunk of data and code that's guaranteed to do exactly what I need (because of testing). In FP, I end up with a new language that I am responsible for to use to create whatever I like (and test as I go.)

But when the endgame is reuse, say in microservices, the two paradigms lead to drastically different answers. The pure FP paradigm will create reusable code, with added complexity being the responsibility of the consumer in large applications. The OO code will create non-reusable code, with the simplicity of knowing that when you call object-dot-method? The thing that you need will happen. For many, many cases, OO is the better paradigm. It's just never going to make reusable components. Not in a generic sense. You can't get there from here.

On the other hand, in pure FP you create nothing but reusable components. It's just not clear how they fit together.

Moving to theory, it's even more clear what's happening. All code, no matter the language, is a form of structure that you create in response to a problem. The structure is based on two and only two things: desired future behavior and supplementals (or non-functionals, if you like) Even if you never write anything down, as you type code in you're thinking about whether or not it creates a system that behaves the way you want given all the rules you want to abide by. (Rules being the supplementals)

In pure FP, I have no supplementals. That is, there's no SOLID or other things that make me code one way or the other. Everything I write is with the goal of minimizing cognitive complexity while exhibiting the behavior I want. That's it. End of story.

In OO, the supplementals are more important than the behavior. Don't believe me? Ever start in with a new framework and have to implement a bunch of interfaces for your objects, even though none of them are called? Why is that? It's because the rules of using the framework are more important than what you're using the framework to do. It has to be that way. That's the core assumption of OO. Things have a place they go in. Frameworks that do a lot of stuff by definition have to have a lot of places for that stuff to go.

In OO, I am looking outward, building a set of constructs that represent the problem such that I can easily understand and change them. In FP, I am looking inward, trying to use primitives in the simplest transform possible that doesn't involve mutation.

When I tried to reuse the C# code, there was a lot of shuffling around for the code to match the new container I was proposing.

In many ways, good OO is the same as writing down requirements ahead of writing code to fix the problem. It creates a natural, immediate impedance mismatch between what you want -- which you may not know until very late in the game or even after you are done -- and what you can deliver because of all of the other choices you've made.

Good FP projects create reusable components, starting with just 2 lines of code. Good OO projects create understandable code structures, no matter how big the codebase gets. If you want true components and reusability, do straight, pure FP without any supplementals at all, then add whatever you need in at the last responsible moment you can do so. (Insert long discussion here about how the worst thing you can do is mash together paradigms. FP trying to pretend that it is OO can be some of the ugliest code you'll ever see. Pick one and go with it.)

Now you know why.

  1. The examples given are not point-free. (They're close, but no banana). Function piping is when data from one function naturally flows into other functions, like water through a pipe. Piping uses the "|>" operator. Folks familiar with linux should know piping. Point-free code is when the data goes completely away and functions flow where the data used to. (You might consider it piping for functions where the data may or may not catch up later; the goal is to collapse the chain of reasoning into as simple of a function as possible) Point-free is just really-cool-looking basic function composition, i.e. currying, just done at the language level instead of the compiler level. If you're chaining and it involves any kind of data (variables), you're piping. If not, you're composing. My personal style is point-free inside methods and piping outside. I find it easier to reason about code this way while getting the same benefits of point-free. Apologies for the confusion. Further information on point-free coding available on Wikipedia.

Related Articles

Code Budgets
· 7 min read
Good Enough Programming (2018)
· 8 min read
The Platform Is The Enemy
· 8 min read
Technology Is Heroin (2008)
· 11 min read