Tuesday, January 17, 2012

Make Fewer Decisions, Write Less Code

I have two suggestions for aspiring C++ programmers:

  • Make fewer decisions.
  • Write less code.

These might seem counterintuitive at first, so let me explain.

Let's say you have a setter method for an object.

class MyObject {
public:
 void setSize(int);
private:
 int size;
}

When you implement that method, how do you name the argument? Here is one way:

void MyObject::setSize(int size) {
 this->size = size;
}

You may have also seen "int _size" or "int inSize", or maybe MyObject has a variable "mSize" instead, or any number of other combinations. I personally do it the above way because I have a thorough understanding of variable scope, and this happens to minimizes the number of unique symbols.

The important thing isn't which approach you pick, it's that you consistently use that solution. Each time you see a setter method, you should be able to write out those two lines without thinking twice. In other words, when you come across a mundane problem you should always have the same solution. You shouldn't even have to make a decision: every time there is more than one way to do something, pick the way that works most of the time, and always use it.

As another example, perhaps you have a collection of things you want to loop through. Here is the first thing I write:

for(int i = 0; i < n; i++)

Code formatting is a kind of recurring decision. What is the smallest set of rules you can formulate for the way you format code? These are two for the above line:

  • Binary operators are surrounded by spaces.
  • Semicolons are followed by spaces.

By minimizing the number of rules you have to follow, and having them cover as many situations as possible, you can reduce the amount of decisions you have to make. Maybe the above rules can be further simplified by removing the word "binary"?

Similarly important are your design pattern choices, all the way down to variable and enumeration idioms:

After writing the above line, you can define "n" on the line above. You'll probably need it again later. And sometimes you can even use something like boost to write the whole thing with a single idiom.

If you're ever asking yourself how your code should be formatted or whether to use < or <= for a loop condition, you're probably wasting time that could be better spent on high level decisions. When I'm using Xcode I use its terrible auto-indent feature to make sure my code is consistent, even though I aesthetically disagree with some of its decisions — it's the fastest way of normalizing my code.

Besides making fewer decisions (and, in the process, writing more consistent code), it's important to write less. Writing less means finding creative ways to use fewer symbols, fewer objects, fewer control statements, fewer loops. Writing less should never make things more complicated. Consider the two functions below:

bool both(bool a, bool b) {
 if(a) {
  if(b) {
   return true;
  }
  if(!b) {
   return false;
  }
 }
}
bool both(bool a, bool b) {
 return a && b;
}

It's an extreme example, but the point is: when you write less code, there are fewer opportunities to make mistakes (there is actually a mistake in the first one, can you see it?). In the situation above, you can simplify your code with truth table analysis. To sum the numbers from 1 to n you can (arguably) simplify your code with recursion, or the more efficient analytic version:

int sum(int n) {
 int sum = 0;
 for(int i = 1; i <= n; i++) {
  sum += i;
 }
 return sum;
}
int sum(int n) {
 if(n > 0) {
  return 1 + sum(n - 1);
 } else {
  return 0; 
 }
}
int sum(int n) {
 return (n * (n + 1)) / 2;
}

For each of those techniques, how many ways could they be wrong? Which one is the simplest?

6 comments:

Anonymous said...

Listen to Kyle, he is wise.

These lessons also generalize well outside of programming.

KarlK said...

Maybe you should add the info that you shouldnt use
for(int i = 0; i < n; i++)
when deleting something in the array.

ehren said...

The use of conditional operator is nice for brevity, and can return nested if/then statements in one line. Here representing a limit function:

return ( in < 0 ) ? 0 : ( in > n ) ? n : in;

Marek said...

Makes me think of this: http://c2.com/cgi/wiki?SubtractLinesOfCode

Kyle said...

jason: you're kind :) i was going to mention a story about alexander the great being one of the first real pre-battle wargamers, and in that sense he was also following "make fewer decisions".

karlk: that's a good way to demonstrate when "fewer decisions" triumphs over "less code". i'd write:

for(int i = 0; i < n; i++)
int r = n - i - 1;

even though for(i = n; i >= 0; i--) is less code, the above requires fewer decisions. but good array item removal techniques is a larger discussion.

ehren: i love conditional operators, but i would add that they don't reduce complexity -- just character count. i would write your limit as min(max(x, 0), n)

marek: that's exactly it. they've hit the nail on the head more succinctly than i can.

Kyle said...

karlk, the first time, i wrote:

for(int i = n; i > 0; i--)

then i deleted the comment and corrected it:

for(int i = n; i >= 0; i--)

but i just realized it's still wrong.

for(int i = n - 1; i >= 0; i--)

there are so many ways to write that one incorrectly :)