Decoupled Design

Recently, I’ve had a large number of discussions around the use of mocks for testing, probably because I keep saying that Mocks are Evil.

As a result, I’ve had to articulate how I design systems. This has forced me to wordify my design instincts. Now they are are concepts that I can manipulate consciously, not just execute subconsciously. So, thank you to those who have argued in favor of occasionally using mocks. You’re still all wrong, of course.

This, then, is how I design systems.

A system has its static structure and its run-time structure. An application also has two relevant time frames: initialization and running (with appropriate patterns, shutdown is not very interesting). Finally, my design criteria are testability, namability, and lack of duplication.

My perfect system is a set of highly namable (aka cohesive) and independent units (often classes or lone functions). Each of them is independently testable, which tends to mean that they have few (but still some) dependencies and side effects. This is the entire static structure of the program, as regards normal execution. These units are not statically attached to each other at all. They expose a bunch of events, take functions as args, avoid interfaces, and have almost no inheritance relationships.

The only other static components are initialization / binding functions. These functions create the run-time structure. They create instances of the independent units and bind them together. The binding functions execute during program initialization, and sometimes during program transformation (in response to an event, sometimes the program alters the way it will respond to future events).

One of the key elements in such a design is that, at runtime, elements are bound directly together. There is no conditional logic, or any other logic, that happens between elements. For example, assume that I’m creating a dialog box which will invoke some action. There’s a “go” button, and a bunch of controls that provide inputs, as well as one where some results go. Here’s one way I might write that:

class TheView(Dialog) {
  Button go;
  Spinner some_input;
  ListBox more_input;
  TextBox results_display;

string the_model_function(int a, List b) {
  return "did something";

TheView prepare_view() {
  result = new TheView(); += () => result.results_display.value =
  return result;

// wherever I show the dialog...

Of course, I can do better than that in GUI frameworks with command objects (or, better yet, the Reactive Extensions), but this version can be written in most modern languages (including C++).

Each of these elements is independently testable. There are no dependencies. The only bit that would actually be at all difficult to test is the line that is labeled “wherever I show the dialog.” Typically, I have a very small number of such lines, turn them into one-liner functions that I supply to components when needed, and don’t bother to test. A test of that one line isn’t going to buy me anything.

Even prepare_view is fairly easy to test. It is a pure function. The test could look like this:

void verify_dialog_initialization() {
  view = prepare_the_view();
  Assert.That(, => evt.handlers.count).equal_to(1));

Where Is.delegation() is a general-purpose condition generator that I write to extend my assertion capabilities in whatever test framework I’m using:

TestCondition delegation(
    LambdaExpression result, Func function, LambdaExpression *args)
  return (Object actual) => {
    Assert.That(actual, Is.of_type(delegate));
    body = actual.function_body;
    Assert.That(body.statements.count, Is.equal_to(1));
    dispatch = body.statements[0];
    Assert.That(dispatch, Is.assignment
      .from(rhs =>
        Assert.That(rhs, Is.function_call(function, *args))
      .to(result.body as VariableReference));

All of assertions are simple static analysis. I don’t need to run the code that results from calling prepare_view. I just need to ensure that it is bound to the right values. After all, that’s all the init functions do: set up bindings between already-tested elements.

Creating this library of helper assertions can take more or less effort depending on my programming language’s support for reflection and code analysis. However, it is doable in most modern languages, and I actually don’t need that many assertions. I’m really only dealing with assignments, functions, dispatchers, and events. I don’t do other logic in my bindings, which means I don’t need to verify it.

8 thoughts on “Decoupled Design”

  1. Is it possible that you've moved away from object-oriented programming entirely? Your code looks very functional to me, with the added fillip that you're so relentlessly dynamic that you want even the call tree of a function to be configurable.

    A common distinction between functional and OO is that OO is about lots of custom objects with a few methods per object, whereas functional is about applying lots and lots of functions to a few basic datatypes. It seems to me that your TheView class is essentially just a hash/dictionary/map.

    For what it's worth, has my translation of your code into Clojure. I've used (imaginary) macros heavily to emphasize that (I think) there are three parts to your code: a mapping from names to mutable state-holders; a collection of pure functions that you can use to calculate values; and one or more wiring functions that mention input state-holders, output-stateholders, and the pure functions that connect them.

    1. I was never tied that closely to OO in the first place. I write a lot of Pure code.

      That said, I do still have lots and lots of specific data types. Each represents a critical concept in the domain. Any domain has a lot of important concepts. And each of these does have a smallish number of smallish methods.

      It's just that there isn't much coupling between them. Most objects don't actually need to know about each other. They can be self-centered little twits. They do occasionally send messages out "to whom it may concern," and they may also be given data to use in computing some value, but that is about it.

      Of course, my design thinking has changed yet again since I first wrote this post. Now I'm pulling most of the execution / runtime call sequence out even more. Everything is async and the calls are reified in various ways.

  2. Hello,

    excuse me for commenting on a post that is so old. I really dislike the verify_dialog_initialization test. It looks to me a "bureaucratic" test that you write to give yourself permission to write the production code that you have in mind. It tests the implementation, not the results.

    If I were writing this test, I would like to have a pluggable model_function that returns something easy to test, such as return a.ToString() + b.ToString(). I would write something like

    view = prepare_view(simple_model_function)
    view.some_input = 33
    view.more_input = [1,2,3]
    assertEquals "33[1,2,3]", view.results_display.Text

    BTW, writing the test this way pushes me to make the model function pluggable, which I think is a better design.

    Do you agree that my test is better? If not, why not?

    1. Nope! Because we're testing different things.

      My view and model are completely pluggable by virtue of double-blind dispatch. My test isn't testing the model. Nor is it testing the view. Both of those would be already tested with unit tests. The model just exposes some function. To test it, I call it and examine its results (or its value updates & messages, depending on whether I'm using functional programming or tell-don't-ask for this particular model).

      However, I want to test that the View will actually call the model when a user clicks go. I know button works (tested elsewhere). And I know the model function works. I just need to make sure that I remembered to wire them together. So that's what I was testing.

      BTW, that test is uglier than any I actually write for this purpose because I was making sure to use only constructs available in even downlevel languages (like C++). In C# with FluentAssertions, MVVM + data-binding, and a couple of my extension methods, the test would have had the assertion:

      .WithArgs(result.some_input.value, result.more_input.selected_items);

      Or, more likely, the model & view would each simply depend on the view model, so I'd have data-binding tests for the view (go.Should().BeBoundTo(vm.go_command)), the model would use the VM as a DTO & I'd test it accordingly (taking it via a single param or at init time), and I'd verify that vm.go_command was a Command instance bound to the right model function in some test of init (vm.go_command.Should().Call(model.some_function)).

      However, the same design technique works without MVVM, which I was trying to show here.

      1. You know that you call the right function when the button is clicked. So what's the point of writing this test? It clearly is not a test that helps you design your system, as it's quite clear that you've decided your design in advance. And, this test remains ugly even if you implement it with fluent assertions 🙂

        1. How do I know the right function is called on the model? How do I know that it gets the data from the right controls (no order swap, etc)?

          This test verifies that the view model is bound to the model correctly for all inbound data and signals. Without it, I'd need some test that actually executes sending a signal and either executes the model or places a mock. Or I'd just leave that code untested. Sometimes I do leave the code untested, but in a case like this (where we are gathering data from parts of the VM), I like having the test. Especially because it doesn't require anything to write, given that I have my assertion library for verifying bindings.

          This trivial example was created for the blog, and therefore created code first. However, in normal use I often create this test first. Or even more commonly, I cerate the UI and bind it all to no-op things on the view model. Then I TDD connecting those to the model. Then I TDD having the model do something.

          Of course, the design is simple and uniform enough that I've effectively created the design before the test–several products ago.

Leave a Reply

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