ソースコードの分かりやすさって何だろう

結論はない。

背景

最近またGaucheを勉強し始めているのだが、ドキュメントを読むとdatumという表現が良く出てくる。

Special Form: case key clause1 clause2 …
[R7RS][SRFI-87] keyは任意の式です。clauseは以下の形式でなければなりません。

((datum ...) expr expr2 …)
((datum ...) => proc)

http://practical-scheme.net/gauche/man/gauche-refj/Tiao-Jian-Shi-.html#g_t_6761_4ef6_5f0f

datumって何だろうという疑問を持ち、調べると「dataの単数形」だと知る。 Lisp/Scheme界隈では一般的っぽいけど、こっちの世界に詳しくない僕には聞き慣れない言葉だった。 (他にも関数型では一般的?)

一瞬「datumって分かりにくいな」と思ったのだけど、上記の通り一般的な用語なので単純に僕の知識レベルに要因があるだけだとすぐに思い直し、その勢いで最近感じているコードの可読性についてポエムを書いている。

プログラミングにおいてソースコードは書くより読むことの方が多いはずで、チーム開発をしていれば自分が書いたコードより他人が書いたコードを読むことの方が多い。 将来の自分は他人ということであれば、自分が書いたコードですら時間が経てば他人のコードになる。 必然、コードは(パフォーマンス上の理由がなければ)人が読みやすいように書くべきだ。

では「分かりやすさ」とは何だろう。というのをぼんやり考えることがある。 「面白い」「美味しい」と同じように、当人の経験やスキルや価値観に大きく影響するものであり、普遍的な「分かりやすさ」などないのではないか。

そもそも職業プログラマたるもの、わざと分かりにくいコードを書く人などいるのだろうか。どんなコードであれ本人にとってはそれは分かりやすいのではないか。本人が分かりやすいと思っている以上、周りが分かりにくいと伝えてもピンと来ないのではないか。

ソースコードについて

まずコードレベルの細かいところから言えば、例えば条件分岐1つとっても色々な書き方がある。 (以降の例は擬似コードだがハイライトのためにcode syntaxは一応javaで指定している)

// パターンA
function something(type) {
    if (type == "a") {
        return "Aだよ";
    } else if (type == "b") {
        return "Bだよ";
    } else {
        return "なんだろうね";
    }
}
// パターンB
function something(type) {
    var result = "なんだろうね";
    if (type == "a") {
        result = "Aだよ";
    } else if (type == "b") {
        result = "Bだよ";
    }
    return result;
}
// パターンC
function something(type) {
    var type_patterns = [
        "a" => "Aだよ",
        "b" => "Bだよ",
    ];

    // 言語によっては存在しない場合のデフォルト値を指定できるが一応愚直に
    var result = type_patterns[type];
    if (result is not null) {
        return result;
    } else {
        return "なんだろうね";
    }
}

焦点はif文なので関数名、変数名の適当さは無視する。

僕は大概パターンAかCで書く。ロジックを分けたい場合はAで書き、(結果の)データを分けたい場合はCで書くことが多い。が他の判断基準もあるし、最近では後述するように基本的にはチームに合わせる。 ちなみに僕は個人的な好みで言えば最後のelseは書かない方が好きだし、1回だけでシンプルなものであれば条件演算子を使うのが好きだ。

// パターンC
function something(type) {
    var type_patterns = [
        "a" => "Aだよ",
        "b" => "Bだよ",
    ];

    var result = type_patterns[type];
    return (result is not null) ? result : "なんだろうね";
}

条件分岐の例を出したが、コードレベルの流派は他にも色々ある。 ループで書くかmap,each,foldみたいなコレクション操作で書くかとか、状態をクラス変数に持って取り回すか引数でもらうようにするかとか。

どこまでメソッドを切るか、というのもコードレベルの話と言えるかもしれない。 以前は1メソッドは短いほどいいと思っていたけど、メソッド呼び出しがあちこち飛ぶと分かりにくいし、OSSとか見ていると長いけど読みやすいコードもあると知った。当然長くて汚い最悪のコードもある。

OSSの場合はパフォーマンスとかの問題でメソッドを分けていない可能性もあるが、とにかくメソッドの行数と可読性は必ずしも一致しないことを学んできた。もちろん適切にメソッドが分かれていれば分かりやすい。が、それに失敗したメソッドは、愚直に長いメソッドより分かりにくいこともあるのではないかという気がしている。 とはいえ個人的には、それでも長いメソッドの中で処理のコメントを書くならそれをメソッドに切ったらいいのではと思うけど、それは僕の「分かりやすさ」であって、万人の「分かりやすさ」ではないのだと思う。

好みといえばフォーマットが典型的だ。言語によってこれだけ規則が違うのは好みの千差万別さを表している。好き嫌いを廃止して(構文が許す限り)全ての言語でフォーマットが統一されていれば各種ツールとかの再発明も必要ないだろうがそうはいかない。 コードを書く上で気持ちよさは大事だし、フォーマットは気持ちよさに影響するのだ。

Goのように公式でツールがサポートされていなくても、最近はほとんどの言語でフォーマットの規約があるしサポートツールもある。問題はプロジェクトがそれに準拠しているかどうかだ。 イメージというか偏見かもしれないが、スタートアップの場合は「大事なのは分かるけど整備している暇があったらプロダクトコード書く」という感じで後回しにされ、大規模とかSIだとそこまで重要視されていないように思う。 ということで、フォーマット規約をきちんとルール化しているプロジェクトにはあまり出会ったことがない。SIerの頃にJava案件でEclipseのフォーマット設定が共有化されていたような記憶はぼんやりある。

他には命名規則にも好みは表れる。例えば何かの配列を表す場合、僕はnameListよりはnames(複数形)を好む。でも人によってはListにした方が(多少文字数は増えても)パッと見て分かりやすいだろう。namesだともしかしたらnameと読み間違えるかもしれない。静的型付けだったらそんなミスは起こらないかもしれないが。 また昔はgetXXXとしていたが最近では単にXXXでも別に良いと思うようになった。呼び出している側で代入していれば戻り値を返すのは明らかだ。

アーキテクチャや開発手法について

トランザクションスクリプトが好きな人がいればそうじゃない人もいる。OOPやDDDが好きな人もいればそうじゃない人もいる。UMLで設計する人もいれば口頭やplain textだけの設計で終える人もいる。 アジャイルウォーターフォールといった開発手法もそう。

ここではアーキテクチャ/手法の良し悪しではなくて、あくまでチームメンバーにとって「分かりやすさ」は違うという観点で話している。 どれだけそのアーキテクチャが理論的に優れていても、正しくても、実行・メンテする人が理解していなければ負債でしかない。 難しいアーキテクチャを採用すべきではないということではなく、チームメンバーによって最適解は変わるのではないかという話。

個人的にはちょっと前に読んだ「ユースケース駆動開発実践ガイド」が面白かったのでこれで開発してみたいけど、自分が長期的に開発していくプロジェクトでなければ選択することはまずないだろう。

ドキュメント(コメント)について

プログラミング覚えたての頃はコメントがないと辛かった。知人に「コード読むのに邪魔だからJavadocを全て消すようなプログラム書いた」という人がいてその感覚を理解出来なかった。

ある程度プログラミング出来るようになってくると、今度はコメントはいらないと思うようになった。「結局コードを読むから」というのが理由だった。 当時はほとんど業務コードしか読んでいなかったせいもあって、コメントが正しいことを保証するには結局コードを読む必要があり、コメントに意味を感じなかった。

今では「必要かつ最小限のコメントは書くべき」という立ち位置になっている。リーダブルコードでコメントへの考えが変わったのもあるし、少しずつOSSのコードも読むようになってきて確かにコメントが理解の助けになることを感じてきた。 それまではせいぜい「どうしてもコメント書くならHowじゃなくてWhyのみ書け」という思いだったが、それだけじゃなくて例えば使用例などがあっても良い。 結局「理解の助けになる」なら何でもいいのだ。リーダブルコードにそんなことが書いてあったような気がするし書いてなかったかもしれない。忘れた。

最近、数年前の自分のコードをいじる必要があったが構造とか意図とか全然覚えていなくて、億劫ながらコードを開いたらコメントが随所に書いてあって助かった。たぶんリーダブルコード読んだ後でコメントは大事という認識が強いうちに開発したのだと思う。当時の自分を褒めてあげたい。

そんなコメントもまた「理解の助けになる」の基準は人によって異なるという問題がある。ある人にとっては自明で余計なコメントでも、ある人にとってはそれが助けになるかもしれない。

先人の知恵

リーダブルコードやClean Codeといった書籍もあるし、ネット上に「分かりやすいコード」の情報はたくさんある。 それらに共感できるスキル/マインドセットを持った人だけでチームが組まれているなら、それを基準にすればいいかもしれない。

でもたぶん、そんな簡単なことじゃない。 例えばこれは細かい例だけど=の位置を揃えるというのがある。

var name    = "xx";
var age     = 10;
var address = "yyy";

僕は最初こういうコードを見た時「うっ」となったけど、今ではこっちの方が見やすいと思っている。けど昔の僕のようにこれが見辛い人もいる。 または他の細かい例だと、僕は「ifの中身が1行なら{}を書かない」というのが嫌いだ。

...前処理
if(pred)
    return "hoge"
...後処理

一方でrubyなどにあるような後置ifで書くのは好きだ。

...前処理
return "hoge" if pred
...後処理

今の僕はこの通りだが、前述の=揃えのように好みは変わるかもしれない。 今日自分にとって分かりやすいと思って書いたコードが、将来の自分には分かりにくいコードになっているかもしれない。 つまり先人の知恵に沿って分かりやすさを意識したコードを書いても、それが本当に長期に渡って分かりやすい保証はないのではということだ。

理想論では、そうならないように少しずつ日々のコードをリファクタすればいいのだろう。変わっていく感性/知識にコードを追従させればよい。 でも実際は日々の業務は山盛りでそこまで余裕はないし、それは面白い作業ではないし、何より1つのシステムと共に死ぬことはなくて遠くない未来に別のプロジェクトや会社に移動する。

どうするか

大きくはアーキテクチャから小さくはコードのコメントの書き方まで色々あって、人によって分かりやすさは異なる。 となると何を基準に開発するべきか。

ここ数年の僕のスタンスは「今後そのプロジェクトを長期保守するであろう人に合わせる」ことにしている。 まず既存のコードを軽く読み、コードの雰囲気を知る。それから自分の担当部分のコードを書く時は、8割ぐらいはその雰囲気に合わせる。2割ぐらいは自分の好みを混ぜる。 そこでもしレビューで修正依頼が来たとしても、よほどの理由がない限りは指摘事項に合わせる。そして大概の場合、よほどの理由はない。

同様に他の人のコードをレビューする時も、昔は細かく「僕はこう書く方が好み。なぜなら…」みたいなことも書いたが、今ではほとんどしない。

コードをたくさん読むのはメンテ(保守/機能追加)する人だ。メンテする人が一番大事だ。彼らがストレスなく読み書き出来なければ、どんなに自分にとって分かりやすいコードでも意味がない。 「こう書いたらどうですか」と提案することもあるけど最終判断はその人に任せる。

おわりに

書いてから読み直して気付いたけど、「分かりやすさ」と「読みやすさ」を一緒にして考えている。 これらは別々の話ではないかという気もする。一緒かな。