時代は空白ベースのインデント
プログラマの世界にはコードのインデントにタブ(\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スペース インデントのススメ #制御文やブロック間の関係性が理解しずらくなる
また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スペース */
class Java {
int a;
// int b;
int c;
}
- (void)Objective_C {
NSLog("a");
// NSLog("b");
NSLog("c");
}
/* 2スペース */
class Java {
int a;
//int b;
}
関連: コメント・テクニック集 & コメント修飾技法 #コード整列テクニック
モダン言語との相性
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
丸括弧を用いた制御文との相性
3文字幅インデントで丸括弧を用いた制御文を利用する場合には前項のような問題が発生しません。
if (isnan(m_currentHealth) ||
m_currentHealth <= 0.f) {
m_currentHealth = 0.f;
}
なお、4文字幅インデントでは条件部と処理部の開始位置が揃ってしまうという特性がありますが、これをデメリットと感じるか、メリットとして感じるかは人それぞれかと思われます。
// 変数名`m_currentHealth`が同じ列に表示されている
if (m_currentHealth <= 0.f) {
m_currentHealth = 0.f;
}
3文字幅インデントではこの特性が失われてしまう点に注意が必要です。
if (m_currentHealth <= 0.f) {
m_currentHealth = 0.f;
}
もっともこちらの場合は、条件部と処理部が異なる階層にあることが明確となるため、人によってはこちらのほうが、わずらわしさがなく、読みやすいと感じる場合があるかもしれません。
ちなみに5文字以上のインデント幅を用いると、階層関係がより直感的になります。
// 制御文の条件部と処理部の位置関係によって階層構造を認識することが可能となる
if (m_currentHealth <= 0.f) {
m_currentHealth = 0.f;
}
最後に
プログラマの性格上、奇数を用いるというのはかなり抵抗があるかもしれません。切りの悪い数値を生理的に受け付けないという人がほとんどだと思います。ですが、ブレースベースの言語を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文字 | ソフトタブ |
Ruby | 2文字 | ソフトタブ |
コラム/エッセイ | ユーザ層やコード分量に合わせる | ソフトタブ |
字下げスタイルと可読性
「オフサイドルールは階層が際立ちとても読みやすくなる」という趣旨の説明をしましたが、これは必ずしもブレースを用いた表現よりも優れているという意味ではありません。
// オフサイドルール
if cond
print "then"
else
print "else"
// ブレースと字下げによる表現
if cond {
print "then";
} else {
print "else";
}
オフサイドルールを採用した言語であっても、ブロック内の行数が広く複雑になれば階層構造の把握は難しくなります。そのようなケースではむしろブレースを用いた字下げ表現のほうが理解しやすいコードになる場合もあります。
というのもブレースを用いたフォーマットは階層の始まりと終わりが明確になるという特徴があります。ブレースの始め括弧{
と終わり括弧}
が構造把握の際の指標になるのです。また{
や}
という一貫した記号である点も重要です。
ブレースを用いたフォーマットは、「字下げ」と「括弧」という二重の情報を用いることができるという意味においては、多様性を兼ね備えた優れたフォーマットという見方もできます。
「字下げ」は直感性や見分けやすさ、「括弧」は具体性や論理性を生み出します。オフサイドルールではこの後者の部分が少し弱いように思います。しかしその分、字下げのメリットが生きるため、ブレースの特性を字下げの特性でカバーすることも可能になります。
オフサイドルールは右脳的、ブレースによる字下げ表現は左脳的とも言えそうです。
なお、インデント幅が極端に狭い2文字幅インデントでは、この「字下げ」の特性が抑えられてしまいます。これを補うためには「括弧」の特性に強く依存する必要があり、これは先程の「終わり括弧(})をしっかりと意識する」という説明にも繋がっていきます。
オフサイドルールもブレースによる表現も、いずれも字下げのメリットを享受するためには、最低でも3文字幅以上のインデント数が必要になるように思えます。
オフサイドルールの可読性とコロンに関する考察
Python等の言語には、文の末尾に記述された:
記号を意識することで、条件部と処理部の区別が容易になるという面白い特徴があります。
// 疑似言語
value = function_a()
if v == function_b()
print function_c()
// Python
value = function_a()
if v == function_b(): // 制御文であることが直感的に理解できる
print function_c() // コロンの後なので、字下げされていることがすぐに分かる
疑似言語の例では、初見ではfunction_b()
とfunction_c()
の両処理が同一のブロックに記述されているように見えてしまうという恐れがありますが、コロンを用いたPython側の例ではそのような心配がありません。
疑似言語の例では、function_c()
の行の先頭に記述されたインデントを認識して初めて、両関数呼び出しが異なるブロックで行われたものだということを理解することになります。これは実に効率が悪いです。視線を左にずらしてインデントを確認するという余計な作業が必要になるためです。
その点、Python側の記述は優れています。:
記号が後置された行の次の行ではインデントによる字下げが行われていることが明確であるため、function_b():
とfunction_c()
の処理が異なる領域に記述された処理だということが直感的に理解できます。
Pythonでは「コロン」を用いることによって、一般的なフリーフォーマット言語に見られる「始め波括弧」と極めて同等の論理性を体現しているという見方ができます。これが意図されたものなのかどうかはわかりませんが、結果としてPythonは直感性と論理性を上手く両立した言語と成し得たわけです。
逆にCoffeeScriptはこの辺の発想がまったく考慮されていないように感じます。見た目は良い言語なのですが、読みづらいと感じることが多いです。
ピンバック: ダークサイド Objective-C コーディング規約 | MaryCore
ピンバック: ヨーダ記法のススメ 〜比較対象を左辺に記述するメリット〜 | MaryCore