3スペース インデントのススメ ─ 空白3つコーディングへの考察


時代は空白ベースのインデント

プログラマの世界にはコードのインデントにタブ(\t)を使う派とスペース(空白)を使う派がいます。 そしてコードシェアリングが主流の現代では環境依存に対処するべく、スペースの利用が好まれる傾向にあります。 プログラマの間ではコード揃え(コード整列)の重要性も認知され始めているため、今後もスペースによるインデントの必要性は高まっていくでしょう。

スペースで統一してコードを揃えれば、他人の環境で表示がズレることもありません。
ちなみに純粋なタブをインデントとして用いる際のタブはハードタブと呼ばれ、逆にスペースをインデントに用いる際のそれはソフトタブと呼ばれます。
スポンサーリンク

空白派における2スペース派と4スペース派

空白派の中でも実は一般的な4スペース派と2スペース派が分かれています。これはテキストエディタ戦争におけるVim派とEmacs派の戦いに近いものを感じます。しかし私はあえてここで、4スペース派と2スペース派の間に割って入って3スペースの利用を勧めてみたいと思うのです。

4スペースの欠点

そもそも4スペースには欠点があります。 階層の深いコードを書くとテキストエディタの横幅をどうしても余計に広く使ってしまうのです。 また、インデントによってコードが間延びする分、眼球運動が余計に必要になり、読んでいて疲れてしまいます。

if (div == 0) {
    throw "error"
} else {
    return function () {
        with (arguments) {
            var c = 0
            for (var i = 0; i < length; i++) {
                c += arguments[i]
            }
            return c / div
        }
    }
}

そのためインデントに空白2つを採用し、コードをよりコンパクトに見せるという発想が生まれました。 これによりモニタの横幅をほとんど気にする必要がなくなり、また目線の移動も最小限に抑えられ、読み手の負担も軽減するという利点を獲得しました。

if (div == 0) {
  throw "error"
} else {
  return function () {
    with (arguments) {
      var c = 0
      for (var i = 0; i < length; i++) {
        c += arguments[i]
      }
      return c / div
    }
  }
}

2スペースの欠点

しかし空白2つを用いるの方法にも欠点があります。ブレース({})を多用する言語だと読みづらいのです。 PythonやCofeeScriptのようなインデントを主体とする言語であればそれなりに読みやすいのは否定しません。私自身、この手の言語では2スペースを活用することが多いです。

// オフサイドルールをベースとする擬似言語
if (div == 0)
  throw "error"
else
  return function ()
    with (arguments)
      var c = 0
      for (var i = 0; i < length; i++)
        c += arguments[i]
      return c / div

コードの階層が際立って見えます。とても構造的で読みやすいですね。

しかし先ほどのブレースを用いた言語のサンプルと比較してみてください。前回サンプルはかなり窮屈で読みづらいことに気づくはずです。どうやらブレースの終わり括弧(})が階層関係を曖昧にしているようです。else文がブレースに押し上げられ、throw文やreturn文と同一の列に並んでしまっている点にも注目してください。また、階層を認識・区別する際の基準がelse等のキーワードではなく、終わり括弧という曖昧でか細い記号である点にも注目すべきです。

なお以下のようにブレースをより細く薄くすることで、この階層構造の曖昧さがより際立つことがわかります。

if (div == 0) {
  throw "error"
} else if (div > 0) {
  return function () {
    with (arguments) {
      var c = 0
      for (var i = 0; i < length; i++) {
        c += arguments[i]
      }
      return c / div
    }
  }
} else {
  return -1
}

2文字インデントを活用する際には、終わり括弧(})をしっかりと意識する必要があります。ブレース記号のシンタックスハイライトを濃い目に設定したり、ブレースが極端に細すぎないフォントや、ブレースの凹凸が強めなフォントを利用するのもよいでしょう。

また2スペースはネストが深く複雑なコードを書くほど、階層構造が曖昧になり、階層を見失いやすくなるという問題があります(この辺の検証はまた別記事で行う予定です)

スポンサーリンク

そこで3スペース

3スペースは4スペース、2スペース両者の欠点を克服し、それぞれの利点を活かすことができるバランスの良い解決策です。

if (div == 0) {
   throw "error"
} else {
   return function () {
      with (arguments) {
         var c = 0
         for (var i = 0; i < length; i++) {
            c += arguments[i]
         }
         return c / div
      }
   }
}

2スペース並のコンパクトさと、4スペース並の階層構造の明確化を実現することができました。

閉じタグとの相性

Ruby言語やLua言語のendキーワードやJavaScriptの特殊な閉じタグ記法});との相性も良いように思います。

def hello(world)
   if world
      puts("world")
   end
end

このようにインデントの幅がendキーワードと同一幅になっている点が特徴的です。このendキーワードはコードの階層構造を認識する際の基準にもなりますし、また規則的で見た目の良いコードになるという利点もあります。

これはラムダ式やクロージャの利用によって生じる});という記述においても同様です。

// JavaScript
f(function () {
   return g(function () {
      console.log("world");
   });
});

// Swift
self.cancelButton = {
   var v = Button()
   v.title = "Cancel"
   return v
}()

3文字幅インデントは無名関数とその値渡しを多用する現代プログラミングとの相性も良いように感じます。

モダン言語との相性

3スペース・インデントはSwiftのif let記法との相性がとても良いようです。ソースがよりドキュメント的になり読みやすくなります。

if let ary = get() {
   
   let foo = ary.first
   let bar = ary.last
   print("\(foo) .. \(bar)")
}

if文内の変数宣言とブロック内の変数宣言が綺麗に揃っているところがとても規則的ですね。

Swift言語やGo言語,Rust言語等、丸括弧が省略可能なモダンな言語との相性はかなり良いのかもしれません。

if golang != rust {
   golang = "こんにちは世界"
}

4スペースがC言語時代のベストプラクティスだとすれば、SwiftやRustの時代にはまた別のインデント数が求められるのでは無いでしょうか。

// C/C++, Java
if (self.name != nil) {
    self.name = nil;
}

// Swift, Rust, Golang
if self.name != nil {
   self.name = nil;
}

条件部と処理部が重なってしまう問題

if文条件部の丸括弧が省略可能な言語で3スペースを利用すると、条件部と処理部のコードが同一の列に表示されることになります。

if value
   print(value)

これは演算子の末尾で改行を行うスタイルでコーディングを行う際に、条件部の式と処理部の式の区別が付きにくくなる問題を引き起こします。

if foo(value) &&
   bar(value)
   baz(value)

次のような対応が求められます。

// 対応案1
if foo(value) &&
   bar(value)
   
   baz(value)
// 対応案2
if foo(value)
   && bar(value)
   baz(value)

もっとも3文字以上のキーワードではこのような問題が引き起こりません

def method(next)
    throws(IOException, NullPointerException)
   for next = next.next()
       where next.canUpdate()
      if not next.update() break

最後に

プログラマの性格上、奇数を用いるというのはかなり抵抗があるかもしれません。切りの悪い数値を生理的に受け付けないという人がほとんどだと思います。ですが、ブレースベースの言語を2スペースで読む際に感じる窮屈さや歯がゆさに比べれば、それほど突飛な発想でもないように思えてきます。

人によっては2スペースでも慣れれば問題なく読めるようになりますが、4スペースから2スペースへの移行はかなり敷居が高いと思われます。そのつなぎとして3スペースを使うという選択も良いかもしれません。

2スペースでは狭すぎて、4スペースでは広すぎると感じるプログラマにとって、両者の間を取った3スペースはまさにベストなインデント幅と言えます。また、職場における4スペース派と2スペース派の争いの妥協案として採用するも良いかと思います。

なお、個人的には書籍やサンプルコードでの利用にも最適だと感じています。3スペース・インデントは4スペースに慣れている人と2スペースに慣れている人の両者を上手くカバーできる唯一のスタイルなのです。

まとめ

4スペース

  • コードが間延びする
  • 読んでいて疲れる

2スペース

  • 階層構造がいまいちハッキリとしない
  • ブレース主体の言語だとその問題が顕著に現れる

3スペース

  • 2スペースと4スペースの良いとこ取り
  • モダン言語との相性が良い
  • コードの見た目が整いやすい

補足

サンプルコードでの利用について

三文字インデントをサンプルコードで利用するべきか否かについてですが、ネット上に公開するサンプルコードについては、利用者がコピー・アンド・ペースト後のコードを自身の環境に合わせて調整する手間を生んでしまうため、慎重に採用する必要がありそうです。

もっともこの問題は「二文字インデント ⇔ 四文字インデント」間でも起きる問題なので、気にするほどのことでは無いかもしれません。むしろユーザ側としては「二文字 ⇔ 四文字」間の調整よりも、「三文字 → 四文字」や「三文字 → 二文字」へ調整のほうが手間は少なくなるかもしれません。もっともマクロや一括置換を用いれば手間は変わらないのですが。

三文字幅の採用に迷うくらいなら、2016年現在最もユーザ数の多い四文字幅に合わせるのも手です。またはモバイル環境を意識して二文字幅に統一するのもよいかもしれません。ハードタブで書いてCSSのtab-sizeプロパティでタブ幅を調整する方法も考えられます。

または周りの環境や文化に合わせて使い分けるのも良いかもしれません。参考までに私は以下のような使い分けをすることが多いです。

用途文字幅インデント方法
Java───ハードタブ
C言語4文字ソフトタブ
C++2文字ソフトタブ
Ruby2文字ソフトタブ
コラム/エッセイユーザ層やコード分量に合わせるソフトタブ

字下げスタイルと判読性

「オフサイドルールは階層が際立ちとても読みやすくなる」という趣旨の説明をしましたが、これは必ずしもブレースを用いた表現よりも優れているという意味ではありません。

// オフサイドルール
if cond
  print "then"
else
  print "else"

// ブレースと字下げによる表現
if cond {
  print "then";
} else {
  print "else";
}

オフサイドルールを採用した言語であっても、ブロック内の行数が広く複雑になれば階層構造の把握は難しくなります。そのようなケースではむしろブレースを用いた字下げ表現のほうが理解しやすいコードになる場合もあります。

というのもブレースを用いたフォーマットは階層の始まりと終わりが明確になるという特徴があります。ブレースの始め括弧{と終わり括弧}が構造把握の際の指標になるのです。また{}という一貫した記号である点も重要です。

ブレースを用いたフォーマットは、「字下げ」と「括弧」という二重の情報を用いることができるという意味においては、多様性を兼ね備えた優れたフォーマットという見方もできます。

「字下げ」は直感性や見分けやすさ、「括弧」は具体性や論理性を生み出します。オフサイドルールではこの後者の部分が少し弱いように思います。しかしその分、字下げのメリットが生きるため、ブレースの特性を字下げの特性でカバーすることも可能になります。
オフサイドルールは右脳的、ブレースによる字下げ表現は左脳的とも言えそうです。

なお、インデント幅が極端に狭い2文字幅インデントでは、この「字下げ」の特性が抑えられてしまいます。これを補うためには「括弧」の特性に強く依存する必要があり、これは先程の「終わり括弧(})をしっかりと意識する」という説明にも繋がっていきます。

関連

インデントがスペース3つのプログラマ「桜ねね」に関する調査報告

広告

関連するオススメの記事

3スペース インデントのススメ ─ 空白3つコーディングへの考察」への2件のフィードバック

  1. ピンバック: ダークサイド Objective-C コーディング規約 | MaryCore

  2. ピンバック: ヨーダ記法のススメ 〜比較対象を左辺に記述するメリット〜 | MaryCore

コメントは停止中です。