2スペース インデントのススメ|2文字幅インデントの哲学


ソースコードをインデントする際には、4文字幅のタブや空白が使われることが多いですが、2文字幅の人気も根強いように思います。特に熟練のプログラマであるほど、2文字幅による字下げを好む傾向にあるように感じます。今回はそんな2文字幅インデントの有効性や利点・欠点について解説していきます。

目次

スポンサーリンク

2スペースの利点

コードの横幅が小さくなる

4スペースの場合はどうしてもテキストエディタの横幅を広く使ってしまいます。

function goo(foo, bar) {
    return function (array) {
        for (var i = 0; i < array.length; i++) {
            if (foo(array[i])) {
                return bar(i);
            }
        }
    };
}

2スペースならこれがかなり縮みます。

function goo(foo, bar) {
  return function (array) {
    for (var i = 0; i < array.length; i++) {
      if (foo(array[i])) {
        return bar(i);
      }
    }
  };
}

とてもコンパクトな見た目になりました。

最近のプログラミング言語では名前空間の利用やクラスの宣言、クロージャー/ラムダ式の利用によって、コードの階層が深くなる傾向にあるため、2スペース・インデントの必要性は昔よりも高まっているように感じます。

// 極端な例
namespace jp {
    namespace marycore {
        namespace partial {
            class Math {
                public: static auto max(int a) {
                    return ^(int b) {
                        return a < b ? b : a;
                    };
                }
            };
        }
    }
}

眼球運動が抑えられる

コードがコンパクトになる分、眼球の移動も最小限に抑えられます。長時間のコーディングやコードレビュー時に最適なインデント幅となります。

条件式とthen節が明確に区別される

2スペースを用いると、if文の条件式とthen節の表示位置が重なりません。

// 2スペース
if (file.isFileURL() && ← 条件部
    file.exists()) {   
  file.remove();        ← 処理部
  fclose(file);
}

// 4スペース(条件部と処理部の開始位置が揃ってしまう)
if (file.isFileURL() &&
    file.exists()) {   
    file.remove();
    fclose(file);
}

4スペースの字下げによって引き起こるこの垂直方向の“重なり”は、読み手に余計な解釈をさせてしまう恐れがあります。

人によっては以下のようなコードとして解釈してしまうかもしれません。

if (file.isFileURL() &&
    file.exists() &&
    file.remove()) {
    fclose(file);
}

このような余計な解釈は、たとえ一瞬であったとしても行われてはならないものです。

4スペースのこの問題への対応策としては、空行で両処理を明確に分ける方法(改善策1)や&&演算子を前置する方法(改善策3)、空白やインデントでコードを整える方法(改善策2・4・5)等が考えられますが、いずれも賛否両論があり、完璧な方法とは言い切れません。

// 改善策1(もっとも現実的)
if (file.isFileURL() &&
    file.exists()) {
    
    file.remove();
    fclose(file);
}
// 改善策2
if (file.isFileURL() &&
        file.exists()) {
    file.remove();
    fclose(file);
}
// 改善策3
if (file.isFileURL()
    && file.exists()) {
    file.remove();
    fclose(file);
}
// 改善策4
if (   file.isFileURL()
    && file.exists()) {
    file.remove();
    fclose(file);
}
// 改善策5
if ( file.isFileURL() &&
     file.exists() ) {
    file.remove();
    fclose(file);
}
// 改善策6(オールマンのスタイル)
if (file.isFileURL() &&
    file.exists())
{
    file.remove();
    fclose(file);
}
// 改善策7(オールマン絶対使いたくないマンのスタイル)
if (file.isFileURL() &&
    file.exists()
) {
    file.remove();
    fclose(file);
}

2スペースを用いれば、if文のこれらの問題を意識する必要はなくなります。また2スペースは丸括弧が省略可能なSwiftやGo, Rust言語等の次世代プログラミング言語でも同様の効力を発揮します。

if file.isFileURL() &&
   file.exists() {
  file.remove();
  fclose(file);
}

文字数制限に強い

開発現場によってはソースコードの横幅に文字数制限(横幅最大80文字 等)を設ける所もありますが、これが非常に面倒な問題を引き起こします。中途半端な所で改行を行わなければならなくなるのです。

4スペース

....
    ....
        var split = function (string) {
            var array = string.separateBy(
                    "hello"); // 泣く泣く改行
            return array;
        }

特に深い階層でコードを書くほど、文字数超過のリスクが高まります。

また最近はメソッドチェーンやラムダ式の多用、略称を極力使わない明確な命名規則などによって、横幅を広く使うスタイルが主流となっているため、文字数の問題は非常に悩ましいものと言えます。

2スペース

2スペースの場合は4スペースと同じ深さの階層でも、インデント幅の累積は最小限に抑えられるため、文字数制限に引っかかる機会はとても少なくなります。

..
  ..
    var split = function (string) {
      var array = string.separateBy("hello");
      return array;
    }

コードの横幅や階層が深くなりがちな言語(C++, Objective-C, Swift, Java, JavaScript)では、2スペース・インデントの利点が活きやすい傾向にあります。

少しマニアックな利点

C言語やJavaではブロック文という特殊な文を利用できますが、2スペースの場合はこのブロックの書き出しに、コメント文や一般的な処理を書くこともできます。

int main() {
  { // 最大値
    int val = 99;
    printf("%d", max(0, val));
  }
  
  { // 最小値
    int val = 99;
    printf("%d", min(0, val));
  }
  
  { char *s = strdup("hello");
    puts(s);
    free(s);
  }
}

もちろん4スペースでも記述自体は可能ですが、コメント部と処理部の書き出しが揃わないため、記法の一貫性が損なわれやすいという特徴があります。

{ // 最大値
    int val = 99;
    printf("%d", max(0, val));
}
{   // 最小値
    int val = 99;
    printf("%d", min(0, val));
}
{    char *s = strdup("hello");
    puts(s);
    free(s);
}

2スペースの欠点

階層を見失いやすい

しかし2文字インデントにも欠点はあります。階層構造が曖昧になってしまうのです。

ネストが浅くまた分量の少ないコードを書く分にはそれほど問題にはならないのですが、ネストが複雑でブロック内のコード行数が多く縦に長くなるようなコードでは、階層構造が視覚的に曖昧になります。

自身もよく2スペースを用いたコードを保守することがありますが、自分が今どこの階層のコードを追っているのかがわからなくなってしまうようなことがよく起こります。確認のためにコード上部に視線を移しますが、インデントが詰まっているため、なかなか複数ある階層の区別がつきにくく、モニターに定規を当てたくなるほどもどかしい思いをすることもしばしばです。

2スペースによるインデントはネストが深く複雑になるほど、階層感覚を失いやすくなるというデメリットがあるのです。

1スペースでの例

少し極端な例ですが、1スペース・インデントを利用すると、この階層構造の曖昧さが顕著に現れます。

  ........ {
   Flip bar = ........;
   if (maybe) { // A
    if (isFoo) { // B
     unless (_piyo == "foo") { // C
      calbee("foobar", rg, Star(bar.pow + 4, 0), "Bar Foo")
     } elsif (_piyo == "var" || _piyo == "end") {
      calbee("varlet", bar, Star(bar.pow + 4, 0), "Opera Town")
     } elif (bar.length) {
      calbee("letbar", bar, Star(bar.pow + 2, 0), "stronger")
     }
    } elseif (~bar.flags) {
     callee((self.isGoogle ? "..." : "---"), bar, Star(bar.pow + 3, 0), "")
     // inside of B?
    }
   } elif (bar) {
    const arrow = isHoge ? "varlet" : (self.isApple ? "VarLet" : "Varlet");
    callbe(arrow, bar, Star(bar.pow + arrow.len, 0), "Var Let")
   }
  } // end of A?

インデント幅が短くなればなるほど、階層構造の把握が困難になり、コードが読みづらくなることが分かるかと思います。

階層構造の把握に余計な神経を使う

これはつまり、2スペースのようなインデント幅が極端に短いものほど、階層構造の把握に多大な神経を使うようになるということでもあります。階層間の関係性が見出しづらくなったり、気づきにくくなったりもします。

その苦労は、小さな針の穴に糸を通す作業と似ているように思います。インデント幅はまさにこの針の穴の大きさです。針穴が小さければ小さいほど、糸を通す際に余計な神経を使うでしょう。インデント幅もこれと同じです。インデント幅が小さくなればなるほど、階層間の状態や関係性が把握しづらくなります。人によってはこれらをストレスと感じてしまうかもしれません。

2文字幅インデントのススメ

普段4スペースに慣れているプログラマが開発で2スペースの利用を強いられてしまうことがよくありますが、ひょっとしたら彼らは無意識の内にストレスを抱え込んでしまっているのかもしれません。

2スペという名のハラスメントに耐えるためには、普段から2スペースに慣れ親しんで耐性をつけるほかありません。2文字幅インデントはもはや教養です。世のプログラマなら一度は2スペースを嗜んでおきたいものです。

3文字幅インデントのススメ

2スペース派と4スペース派の間を取って3スペース分のインデント幅を採用するという案も考えられます。

3スペース インデントのススメ

2スペース活用時の注意点

2文字幅インデントの利用には、階層の深いコードや階層構造が複雑なコードを更に読みづらくしてしまうというデメリットがあります(# 2スペースの欠点)。そのため、2スペース利用時には可能な限り階層が深くなりすぎない簡潔なコードを意識する必要があります。

そのためには関数化やサブルーチン化によって余計な処理を分散させることが求められます。一つの関数内で記述できる行数を制限して関数化を促すのもよいでしょう。一つの関数内で使用できる階層の深さを制限することも有効です。

また関数の結果を即時に返すような工夫をすると、階層が浅くなります。余計な一時変数の利用も廃止できます。

// Before
boolean isNotEmpty(String s) {
  boolean result = false;
  if (s != null) {
    result = s.length() != 0;
  }
  return result;
}

// After
boolean isNotEmpty(String s) {
  if (s == null) return false; // 戻り値の即時返却
  return s.length() != 0;
}

2スペース インデントは、コードの簡潔性や分量をきちんと管理できる優秀なプログラマが使わなければ、真の威力を発揮できないという性質があるように思います。2スペースはある意味、上級者向け・玄人向けのスタイルだとも言えそうです。

広告

関連するオススメの記事