Blog | Tristan Kernan

“That some of us should venture to embark on a synthesis of facts and theories, albeit with second-hand and incomplete knowledge of some of them – and at the risk of making fools of ourselves” (Erwin Schrödinger)

Parameterization

Recently I was able to leverage parameterization to great effect, and I'd like to share the experience. I maintain an internal application that is run daily and produces some necessary logistics data for our operations. In extending the application, I was asked add support for running the tool for future dates.

Easy configuration

With several years of experience under my belt, I know that time is tricky to get right with software. At work, using Django, it's typical to rely on utilities like timezone.now() and timezone.localdate() to access the current time or date. As there's no restriction on calling these utilities, they may be used in deeply nested call trees. As the concept of a local date relies on global or implicit state (i.e. the active timezone), there's necessary implicit configuration required for the code to work correctly - and when the active timezone can be manipulated in the same fashion, down the call tree, it's easy to end up in a quagmire (yes, I've seen timezone.activate(tz) used in a loop, leading to a random timezone ultimately activated at the end).

So when I was asked update my application, how much did I suffer in chasing down time related bugs? None (mostly). And how is that, you wonder? Did I leverage more global implicit behavior like overriding the computer time? Nope. I had, long ago, already parameterized the application on date.

By parameterizing, I mean adding explicit function parameters. E.g.

def get_orders_for_today(today):
    return Order.objects.filter(date=today)

Instead of:

def get_orders_for_today():
    return Order.objects.filter(date=timezone.localdate())

In my application code, the top level function (interface) accepted a date parameter and passed it down the call tree. The date had been previously passed to the function with timezone.localdate() - I swapped this for reading the date from user input. I verified that there was no implicit behavior based on the date by following its usage. And I was able to get this feature done in an afternoon instead of a week.

Easy testing

Testing also benefits from parameterization, as an alternative to monkeypatching. As an example, at work, we have an optimization problem solver which calls out to a native library and typically runs for several seconds. To test this code, the relevant solver() function could be monkeypatched to skip the processing and return some value. The alternative approach that I used is called the strategy pattern, where the algorithm is passed in as a parameter. Suppose I have the following interface:

class ISolver:
    def solve(problem):
        ...

Then, I can implement the native solver and a stub solver:

class NativeSolver(ISolver):
    def solve(problem):
        time_consuming_solver(problem)

class StubSolver(ISolver):
    def __init__(self, solution):
        self.solution = solution

    def solve(problem):
        return self.solution

Yes, this could be improved with typing and protocols, but that's besides the point here

Finally, I'd parameterize my application code:

def scheduling_problem(problem, solver):
    ... # call solver(problem)

...

solver = NativeSolver()
scheduling_problem(problem, solver)

And to test:

def test_scheduling_problem():
    problem = ...
    solution = ...
    solver = StubSolver(solution)
    scheduling_problem(problem)
    assert ...

Admittedly, I've had less success spreading the gospel on the benefits of the strategy pattern, the core critique being the boilerplate code. That's fair. I have only used this technique a handful of times, most commonly when there are multiple strategy implementations besides the test mock.

Closing thoughts

I recognize this technique isn't revolutionary, as it's really just a simple application of programming principles: don't rely on or mutate implicit or global state. As with most timeless advice, it's often ignored and quite beneficial.