Book cover

Misbehaving

Why Coding Needs to Go Beyond Solving the Problem at Hand

Imagine buying a new car, reading the driver’s manual, and taking it on a joyride into the sunset to enjoy its new features in action. Then, unbeknownst to you, the car slows down whenever you look at the speedometer. You take it back to the dealership, like any rational buyer, and request a replacement. The shrewd salesperson who sold you the car tries to explain that what you experienced is actually a feature: The dashboard activates only when someone looks at it to reduce windshield reflections, especially at night. Still baffled, you question the link between the query of speed and the command to reduce it; the savvy salesperson expounds that it’s for your own safety: It’s a side-effect of checking the speed; after all, you have to take your eyes off the road to check the speed, right?

Understandably furious, you take matters into your own hands and decompile the software that controls the car to find the following:

public class Speedometer {
  private StatefulOdometer statefulOdometer;
  private Controller controller;
  
  /**
   * Gets the current speed and applies the brakes if activated by eye tracking.
   * @return current speed.
   */
  public double getSpeed() {
    OdometerDelta delta = this.statefulOdometer.getDeltaSinceLastReading();
    double speed = delta.getDisplacement() / delta.getTimeElapsed(TimeUnit.HOURS);
    
    if (this.isActivatedByEyeTracking()) {
      this.controller.applyBreaks();
    }
    return speed;
  }
...

Busted! Now, you’re on a mission; you hunt down all calls of getSpeed and, unsurprisingly, you find out that the vast majority of callers didn’t expect said method to have a side-effect. You don’t blame the callers because neither the method’s signature nor its name indicates that it might do anything else besides returning the current speed!

A few weeks later, you manage to track down the developer who wrote that code. After an extremely awkward introduction, you explain how side-effects can have undesirable ramifications and that coding must fulfill requirements beyond solving the problem at hand; to list a few:

Appalled, the developer argues that documentation clearly states what the method does. Since one cannot force callers of a method to read its documentation, you easily refute this argument. That’s why names matter a lot; one can only assume one thing about a method called getSpeed in Java — it’s a getter.

Another argument comes to mind: a Speedometer has a reference to a Controller, so it’s acceptable to change its state; have you a rebuttal? You point out that allowing extraneous access between objects is a design deficiency. Applying the Interface Segregation Principle makes it crystal clear which Controller methods a Speedometer can call. Since the role of said sub-interface doesn’t entail changing state, it should be renamed to ImmutableController to reflect that. If the programming language allows for compile-time enforcement of such constraints, like the const keyword in a function’s declaration in C++.

In a last attempt to maintain the status quo, the developer argues that every caller of getSpeed has to re-implement the rest of the logic (i.e., test for the eye-tracking activation and apply the breaks if needed) because that was the requirement. You refer to the Single Responsibility Principle and point out that said behavior is the responsibility of another class; you want classes to be like LEGO bricks: small, robust, and easy to re-purpose as the requirements change (because they will).

After the long discussion, you rejoice as the developer agrees to make the changes and you get to enjoy your new car without side-effects. As you drive into the sunset, you reflect on the takeaways: make smaller things; each is responsible for only one thing; and, most importantly, care for your code or fear that the next person to read it knows where you live.