Argument Forwarding and Recycled Configs

Recently I’ve come across an anti-pattern in JavaScript and front-end development but is by no means limited to those areas of programming. The problem arises when the parent class attempts to handle logic unique to one of its subclasses.

The omni-parent class

For the rest of this post let’s consider the following example as a means of highlighting the problem and what can go wrong: say you’re trying to create a class to describe all types of cars. It would be very easy to start writing this class with a loose idea of what its methods will ultimately look like.

class Car {
constructor (config) {
// How should we configure our car?

Surely any car will have generic descriptors like year built and weight to keep things consistent regardless of car make and model. Maybe we also assume that we want all cars to be able to accelerate:

class Car {
constructor (config) {
this.year = config.year;
this.weight = config.weight;
    accelerateTo (newVelocity) {
// acceleration logic

However, there are properties and methods that don’t apply to all cars. Say we know some cars will have a rotary engine and others will have a more traditional piston engine. Each engine type accelerates in a different way, but we’re not sure how or what other config properties might be used to handle the acceleration. The last thing we want to do is pass the config to our accelerateTo method as a catchall argument:

// don't do this, ever
accelerateTo (newVelocity, config) {
if (config.engineType === 'piston') {
this.handlePistonAcceleration(newVelocity, config);

There are a couple of ways to properly handle implementing our accelerateTo method. We could write a default acceleration method in our parent Car class and then override that method in each subclass where needed, or we could hold off on implementing any such method on the parent and only write it into each subclass.

The common thread in each relies on requiring a subclass to be defined before we handle all such cases.

Class/subclass definition

Problems like this stem from wanting to handle what we perceive to be edge cases when they are in fact use cases for an entire subclass. Cars make it easier to think about this problem for the reason that car features are (generally) broken down by make and model. If we tried to account for every possible feature a car might have in our parent class the resulting code would smell pretty foul.

Instead features can be bundled into a specific subclass (make) with optional features bundled into a subclass of the subclass (model).

Another way we can paint ourselves into a corner is to start coding with no such notions of classifying any objects and only write general purpose utility methods. Utility methods aren’t bad, but they should take only the bare amount of arguments they need with each argument as thin as possible.

If at any point you find yourself with a config object as one of those params, you probably need to refactor into well-defined classes.

Still not convinced? Think of the unit tests!

Imagine unit testing methods fed generic config params. My god, the horror! If this config object housed three different optional properties, already we have 2³ = 8 different unit test cases just on proper existence handling. Furthermore, for each additional optional prop we support in this way we potentially double the number of existing test cases (one set with new prop, one set without).

In conclusion, this is a specific problem that stems from an incomplete or fluid concept of the objects being created and modified by our code. Can’t emphasize how much of this can be avoided by dedicating enough time to requirement gathering and design phases of a new project.

Show your support

Clapping shows how much you appreciated Aaron Dilley’s story.