インデントが深くなるプログラム

関数の出口を一つにするべきだという論議に関して書いてある関数の出口の数について - Unknown::Programmingの例が少し気になりました。

この記事では、出口を一つにするべきという意見に、「インデントが深くなりやすい」というデメリットを挙げながら反対しているのですが、そこで以下のようなサンプルを出してます。

=head2 func()

 適当な関数の仕様
 
引数が渡されなかったら戻り値なし。
引数が未定義だったら 0 を返す。
引数が数値じゃなかったら 0 を返す。
引数が10000以上だったら 0 を返す。
引数が0だったら 1 を返す。
引数が偶数だったら 2 を返す。
引数が奇数だったら 3 を返す。

=cut

sub func {
    my ($param) = @_;
    my $ret; # 戻り値用の変数

    if( @_ ) {
        if( defined $param ) {
            if( $param =~ /^\d+$/ ){
                if( $param < 10000 ) {
                    if( $param == 0 ) {
                        $ret = 1;
                    }else{
                        if( $param % 2 == 0 ){
                            $ret = 2;
                        }elsif{
                            $ret = 3;
                        }
                    }
                }else{
                    $ret = 0;
                }
            }else{
                $ret = 0;
            }
         }else{
            $ret = 0;
        }
    }

    return $ret;
}

確かに汚い。で、修正版はこうだという。

sub func {
    my ($param) = @_;

    return if !@_;
    return 0 if !defined $param;
    return 0 if $param !~ /^\d+$/;
    return 0 if $param >= 10000;
    return 1 if $param == 0;
    return 2 if $param % 2 == 0;
    return 3;
}

言いたい事は分からなくもないのだけど、これだと例が不適切でしょう。
この件、retを一つにして整形すると、こうなります。

sub func {
	my ($param) = @_;
	my $ret; # 戻り値用の変数

	if (!@_) {
	} elsif (!defined $param) {
		$ret = 0;
	} elsif ($param !~ /^\d+$/) {
		$ret = 0;
	} elsif ($param >= 10000) {
		$ret = 0;
	} elsif ($param == 0) {
		$ret = 1;
	} elsif ($param % 2 == 0) {
		$ret = 2;
	} else {
		$ret = 3;
	}
	return $ret;
}

どうでしょう。
returnを使ったときよりは見づらいかもしれませんが、それでも最初の例よりはマシで、出口を一つにするかしないかによらずにしてこのようなコードを目指すべきでしょう。

では、出口を一つにするとどういうときにインデントが深くなるのか。
単純な例で指すと、elseに対して実行するべき共通のなにかがあり、それを纏めて記述したいときでしょう。
例えば、上に似せて書くとこういうことです。

sub func {
	my ($param) = @_;
	my $ret;

	if (!@_) {
	} elsif (!defined $param) {
		$ret = 0;
	} elsif ($param !~ /^\d+$/) {
		$ret = 0;
	} else {
		# 1
		my $x = $param * 3; # do something
		if ($x >= 10000) {
			$ret = 0;
		} else {
			$ret = 1;
		}
	}
	return $ret;
}
<||
擬似的なので、まだ纏められるわけですが、そこはカンベン。

つまり、#1のelseのくくりで共通した処理 #do something があると、インデントが深くなるわけです。
更に言うなら、その#do somthingは、もし条件分岐に使わないならば処理を各分岐に記述しても問題がなく、そうなるとやっぱりインデントは深くならない。
となると、強制的にインデントが深くなるようにするには、条件分岐で#do somethingの結果を使う必要があるでしょう。

ちなみにコレをreturnを使って整えるとこうなります。
||>
sub func {
	my ($param) = @_;

	return if !@_;
	return 0 if !defined $param;
	return 0 if $param !~ /^\d+$/;

	# 1
	my $x = $param * 3; # do something
	return 0 if $x >= 10000;
	return 1;
}