Towards Explicit State

I recently, at work, made a postcode look-up component that worked in the manner you’d expect: enter a postcode, click “find address”, select the correct address from a list, finally show the parts of the address in textfields. This was written as a component in Angular 1.5, so the controller for it looked something like:

import AddressLookupService from '../../services/address-lookup.service';
import { IAddress } from '../../interfaces/address';

export default class {
  static $inject = ['addressLookupService'];

  // internal state
  postcode: string;
  choices: { id: string; text: string; }[];
  selectedId: string;
  address: IAddress;

  // callback bound to the parent
  onChange: (obj: { address: IAddress }) => void;

  constructor(private addressLookupService: AddressLookupService) {}

  // called when user clicks "enter address manually" button
  manualEntry() {
    this.address = {
      line1: '',
      line2: '',
      townCity: '',
      postcode: '',
    }
  }

  // called when user clicks "find address" button
  findAddress() {
    this.addressLookupService.find(this.postcode)
      .then(response => {
        // clear previous choice
        this.selectedId = null;

        // setting address to null hides the textfields
        this.address = null;

        // setting choices to non-null shows the list of options
        this.choices = response.items.map(x => ({id: x.id, text: x.name}));
      });
  }

  // called when list item is selected
  selectAddress() {
    this.addressLookupService.retrieve(this.selectedId)
      .then(response => {
        // stop showing choices
        this.choices = null;

        // show the chosen address
        this.address = {
          line1: response.line1,
          line2: response.line2,
          townCity: response.townCity,
          postcode: response.postcode,
        }

        this.addressChanged();
      });
  }

  // called when address selected and when textfields change
  addressChanged() {
    this.onChange({ address: this.address });
  }
}

Within this there are a few subtle connections between the template and whether variables are null/non-null. To make them clearer we can break up how this controller stores data in to distinct states.

These states can easily be modelled in Typescript through tagged unions.

interface Start {
  state: 'Start';
}

interface PostcodeEntered {
  state: 'PostcodeEntered';
  choices: { id: string; text: string; }[];
  selectedId: string;
}

interface AddressSelected {
  state: 'AddressSelected';
  address: IAddress;
}

type State = Start | PostcodeEntered | AddressSelected;

Now it is clear that if an address is being displayed we should not show a list of choices, because we don’t have a list to show! The controller can now be refactored to use this State type.

export default class {
  static $inject = ['addressLookupService'];

  // since postcode is always shown it is declared in the controller, not State
  postcode: string;
  state: State;
  onChange: (obj: { address: IAddress }) => void;

  constructor(private addressLookupService: AddressLookupService) {}

  manualEntry() {
    this.state = {
      state: 'AddressSelected',
      address: {
        line1: '',
        line2: '',
        townCity: '',
        postcode: '',
      },
    };
  }

  findAddress() {
    this.addressLookupService.find(this.postcode)
      .then(response => {
        this.state = {
          state: 'PostcodeEntered',
          choices: response.items.map(x => ({id: x.id, text: x.name})),
          selectedId: null,
        };
      })
  }

  selectAddress() {
    if (this.state.state === 'PostcodeEntered') {
      this.addressLookupService.retrieve(this.state.selectedId)
        .then(response => {
          this.state = {
            state: 'AddressSelected',
            address: {
              line1: response.line1,
              line2: response.line2,
              townCity: response.townCity,
              postcode: response.postcode,
            },
          };

          this.addressChanged();
        });
    }
  }

  addressChanged() {
    if (this.state.state === 'AddressSelected') {
      this.onChange({ address: this.state.address });
    }
  }
}

This maybe doesn’t look much clearer than before – and of course there are those two methods that rely on being in a certain state – but the relationship between state and the template is much clearer since we no longer have to change what is displayed depending on if certain variables are null or not… hoping that in the future everyone remembers to set the correct variable to null in the correct place.

<div ng-if="$ctrl.choices !== null">...</div>
<div ng-if="$ctrl.address !== null">...</div>

Instead the template can make use of explicit state names and know which values will be defined.

<div ng-if="$ctrl.state.state === 'PostcodeEntered'">...</div>
<div ng-if="$ctrl.state.state === 'AddressSelected'">...</div>

When you don’t have the reassurance you get when writing TSX or Elm, having obvious looking templates that are not dependent on certain variables makes changing things in the future much easier.

Eat. Sleep. Meh. Repeat.

Look, I get it — ▮▮▮▮▮▮▮ is a ▮▮▮ers conference, and the “eat, sleep, ▮▮▮, repeat” phrase is intended to be a clever way (albeit a completely unoriginal one) of saying “▮▮▮ing is awesome and we want to do it all the time!” I appreciate the enthusiasm, I do.

But there’s a damaging subtext, and that’s what bothers me. The phrase promotes an unhealthy perspective that ▮▮▮ing is an all or nothing endeavor — that to excel at it, you have to go all in. It must be all consuming and the focus of your life.

Such bullshit. In fact it’s the exact opposite.

At ▮▮▮▮▮▮▮ I work with some of the best ▮▮▮ers in the world. It’s no coincidence that they all have numerous interests and talents far outside of their ▮▮▮ing capabilities.

Whether it’s racing cars, loving art, reading, hiking, spending time in nature, playing with their dog, running, gardening, or just hanging out with their family, these top-notch ▮▮▮ers love life outside of ▮▮▮.

That’s because they know that a truly balanced lifestyle — one that gives your brain and your soul some space to breath non-▮▮▮ing air — actually makes you a better ▮▮▮er.

Life outside of ▮▮▮ helps nurture important qualities: inspiration, creative thinking, patience, flexibility, empathy, and many more. All of these skills make you a better ▮▮▮er, and you can’t fully realize them by just ▮▮▮ing.

“Eat, sleep, code, repeat” is such bullshit (Signal v. Noise)

I guess if you extend the argument that less programming makes you a better programmer, then the best programmer is someone who has never programmed.

Not that I disagree that spending all of your spare time programming is a bad use of your time, but that, surely, the best programmer is the one who practices most. The key point is that since there are few interesting problems for programmers to solve, probably fewer than there are programmers who can/want to solve them, there is no point aiming to be the best.