Don't think of short functions in terms of absolute LOCs number. It is irrelevant, because:
void demo(int a)
{
if (a < 0)
{
this.dealWithNegative();
}
for (int i = 0; i < a; i++)
{
yield this.doSomething(i);
}
}
and:
void demo(int a) {
if (a < 0) this.dealWithNegative();
for (int i = 0; i < a; i++) yield this.doSomething(i);
}
are the same in terms of complexity: the second variant is not three times better because of its LOC of 4 lines versus 12 lines in the first example. (Actually, the second example is much more error prone, and so worse, but this is a different subject.)
Think of short functions in terms of how long would it take you to understand them. When you start working with a method, you tell:
“Oh heck, it would take me two hours to figure out what is all this code about.”
and not:
“Well, there are 124 lines of code, so it should take me from 96 to 112 minutes to figure this code out.”
right?
While bigger LOC usually leads to methods which take more time to understand, there is no strict correlation between two factors. For example, how long would it take you to understand a method which maps a value to another, containing 50 values (and so 53 LOCs)? Would that change if the map contains only 10 values? What about 300 values?
Guard clauses don't necessarily make methods longer, because they are simple to understand and don't take too much of your time when you prepare to work with the code. They may not even increase the absolute number of LOCs, because most stuff they deal with is otherwise scattered in your code. For example, what is simpler:
The code with guard clauses:
void demo(int a, int b)
{
if (a <= 0) throw new ArgumentOutOfRangeException(...);
if (b < a) throw new ArgumentException(...);
var c = b / a;
this.doSomething(c);
}
or the contrived logic of the similar code without them:
void demo(int a, int b)
{
if (a != 0)
{
// The division is safe: we won't have division by zero here.
var c = (double)b / a;
if (c < 1.0)
{
// We shouldn't have `c` inferior to 1.
throw new NotImplementedException();
}
this.doSomething((int)c);
}
else
{
throw new ArgumentOutOfRangeException();
}
}
ThrowIfBitmapNotGray8(bitmap)
, which subsequently callsThrowIfBitmapNull(bitmap)
. In some cases, one can reduce the number of guard clauses down to one per argument, which I think is the minimum possible. Remember that there are other meaningful validations other than null checks.