Object Oriented Programming/State is evil

"State" is Evil!

edit

Something that we don't see reiterated enough: State (as opposed to change) is evil! Or, (perhaps better said) maintaining unnecessary state is the root of all (er... many) bugs. Let's look at an example, now in Ruby:

  1. We're displaying something over time, so we're dealing
  2. with both pixels and seconds. Based on the zoom level,
  3. there is a correlation between them: pixels per second,
  4. or PPS.

Class Timeline

{
 PPS;
 current_position_in_seconds;
 current_position_in_pixels;
public:
 GetPosInSeconds() {current_position_in_seconds;}
 SetPosInSeconds(s) {current_position_in_seconds = s;} # oops, we're out of sync
 GetPosInPixels() {current_position_in_pixels;}
 SetPosInPixels(p) {current_position_in_pixels = p;} #oops, we're out of sync

} In this example, we're maintaining gratuitous state — which is to say, we're holding the position in both seconds AND pixels. This is convenient, and important for users of this class, but we've messed up the encapsulation here. Whenever you set the value in one unit, you've destroyed the correctness for the other unit. Okay, you say, here's a simple fix:

  1. We're displaying something over time, so we're dealing
  2. with both pixels and seconds. Based on the zoom level,
  3. there is a correlation between them: pixels per second,
  4. or PPS.

Class Timeline {

 PPS;
 current_position_in_seconds;
 current_position_in_pixels;

public:

 GetPosInSeconds() {current_position_in_seconds;}
 SetPosInSeconds(s)
      {
         current_position_in_seconds = s;
         current_position_in_pixels = s*PPS;
       }
 GetPosInPixels() {current_position_in_pixels;}
 SetPosInPixels(p)
      {
         current_position_in_pixels = p;
         current_position_in_seconds = p/PPS;
      }

} This fixes the obvious error with the previous example, but you've created a lot of work for yourself. Now you have to keep every calculation in every method in the Timeline class duplicated to maintain consistency. There could be dozens more where this came from (trust me). And what about when you add in other units, say inches? You have to duplicate all of that again. Presumably, you see where this is heading: calculated properties, logical properties, virtual properties, whatever you like to call them. Let's see this example again:

Class Timeline {

 PPS; # Pixels Per Second
 IPS; # Inches Per Second
 current_position_in_seconds;

public:

 GetPosInSeconds() {current_position_in_seconds;}
 SetPosInSeconds(s) {current_position_in_seconds = s;}

 GetPosInPixels() {current_position_in_seconds*PPS;}
 SetPosInPixels(p) {current_position_in_seconds = p/PPS; }

 GetPosInInches() {current_position_in_seconds*IPS;}
 SetPosInInches(i) {current_position_in_seconds = i/IPS; }

} Now, we're presuming that the conversion factors of PPS and IPS are set correctly for simplicity, but otherwise we've vastly simplified our problem. In twenty other functions we now write, we only have to worry about seconds, there's no consistency problems to worry about. Additionally, if we need to change our base units from seconds to pixels, the Timeline users need never know.