本記事はTim Severien、Mark Brownが査読を担当しています。最高のコンテンツに仕上げるために尽力してくれたSitePointの査読担当者のみなさんに感謝します。
完全に場違いで無意味なコードのコメントを書くのはつまらないと思いませんか?
一番ありがちな間違いの例は、いくつかのコードを変更したあと、コメントの削除や更新を忘れてしまうことです。悪いコメントがあるからと言ってコードそのものは壊れませんが、デバッグ時にどうなるかを想像してください。そのコメントが読まれるとします。そこには何かが書いてあるわけですが、コードはまったく別のことを実行します。おそらく、コメントとコードの違いを解決する時間を無駄にし、最悪の場合、コードがミスリードされてしまう可能性もあるのです!
とはいえ、コメントなしのコードを書くのはおすめできません。私の15年以上にわたるプログラミング経験上、コメントがまったく役に立っていないコードを見たことは一度もないからです。
しかし、コメントを減らす方法はいくつかあります。ただ単にプログラミング言語の特性をうまく利用し、あるコーディングテクニックを使うとコードを明確化できるのです。
この方法を使えば、コードを理解するのがより簡単になるだけではなく、プログラム設計全体の上達につながることは間違いありません。
このタイプのコードは、自己文書化(Self-documenting)と呼ばれることもあります。これから、コーディングに自己文書化のアプローチをどのように取り入れられるか説明します。紹介する例はJavaScriptですが、ほかの言語でも、JavaScriptでの自己文書化のテクニックの大部分を適用できます。
テクニックの概要
プログラマーの中には、自己文書化コードの一部としてコメントを含める人もいます。しかし、記事ではコードに焦点を絞ります。確かにコメントも重要ですが、自己文書化コードは別途説明が必要なほど大きなトピックなのです。
自己文書化コードのためのテクニックは、3つの大きな以下のカテゴリに分類できます。
- 目的を明確にするために使うコードの構造やディレクトリの構造的テクニック
- 関数や変数の命名のような、命名に関わるテクニック
- コードを明確にするために、言語の特性を使用する(または使用を避ける)構文に関わるテクニック
3つのテクニックの多くは、理論上簡単なものです。いつ、どのテクニックを使うかが分かったときが問題なのです。今からそれぞれをどのように処理すべきか、実例を挙げて説明します。
構造的テクニック
構造に関するカテゴリーについて説明します。より明瞭でわかりやすいコードへ構造をシフトさせる方法です。
コードを関数に移動しよう
関数の抽出によるリファクタリングです。つまり、既存のコードを取り出して、新しい関数へと移動します。すなわち、コードを「抽出」して、新しい関数に入れるのです。
たとえば、次のコードが何を実行するのか推測してみてください。
var width = (value - 0.5) * 16;
このままでは何をしているのかあまり明確ではありません。このときのコメントは極めて重要です。または、自己文書化するために関数を抽出できます。
var width = emToPixels(value);
function emToPixels(ems) {
return (ems - 0.5) * 16;
}
唯一の変化は計算式を関数に移動しただけです。関数の名前が何をしているのかを表すので、このコードはこれ以上明瞭化する必要がなくなりました。追加的なメリットとして、ほかの場所でも使える便利なヘルパー関数ができたことになり、この方法は重複を減らすためにも役立ちます。
条件式を関数に置き換えよう
複数のオペランドから構成されるIF構文は、コメントがないと理解が難しいことがあります。明確化するには、上記と同様の方法を適用できます。
if(!el.offsetWidth || !el.offsetHeight) {
}
この条件の目標はなんでしょうか?
function isVisible(el) {
return el.offsetWidth && el.offsetHeight;
}
if(!isVisible(el)) {
}
上のように、コードを関数に移動すると、理解しやすいものになりました。
式を変数に置き換えよう
ある要素を変数に置き換えるのは、コードを関数に移動するのと似ていますが、関数の代わりに、シンプルな変数を使います。
IF構文を使った例をもう一度見てみます。
if(!el.offsetWidth || !el.offsetHeight) {
}
関数を抽出する代わりに、変数を導入することでより明瞭化できます。
var isVisible = el.offsetWidth && el.offsetHeight;
if(!isVisible) {
}
関数を抽出するよりも良い方法です。関数を抽出する代わりに変数を導入する方法を使うのは、たとえば、明瞭化したいロジックが1か所のみに使用されるような、特定のアルゴリズムに限られるような場合です。
関数を抽出する代わりに変数を導入する方法がもっとも一般的に使われるのは、数式においてです。
return a * b + (c / d);
上の式は、数式を分割することで明確化できます。
var multiplier = a * b;
var divisor = c / d;
return multiplier + divisor;
私は数学があまり得意ではないのですが、上の例を、何か意味のあるアルゴリズムであると想像してください。いずれの例でも、重要な点は、理解するには難しいと思われるコードに意味を追加したいとき、複雑な式を変数に移動することです。
クラスおよびモジュールのインターフェイス
クラスおよびモジュールのインターフェイス——パブリックメソッドとプロパティ——は、その使用法に関するドキュメントとして機能できます。
例を以下に示します。
class Box {
setState(state) {
this.state = state;
}
getState() {
return this.state;
}
}
このクラスは、いくつかのほかのコードも含んでいます。パブリックインターフェイスをどのようにドキュメント化するかを説明するために、あえて例をシンプルにしています。
このクラスの使用方法が分かりますか? おそらく少しは役に立つでしょうが、あまりはっきりとはしません。
両方の関数には合理的な命名がなされています。つまり、なにを実行するかという内容は命名により明らかです。それにもかかわらず、どのように使うかを考えたときにはっきりとしないのです。おそらくもっと多くのコードやクラスを把握するためのドキュメントを読む必要がありそうです。
例を次のように変更するとどうなるでしょうか。
class Box {
open() {
this.state = 'open';
}
close() {
this.state = 'closed';
}
isOpen() {
return this.state === 'open';
}
}
使い方が分かりやすくなったと思いませんか? パブリックインターフェイスだけを変更したことに注意してください。内部表現はまだthis.stateプロパティと同じです。
これでどのようにBoxクラスが使われているのか分かったでしょうか。最初のバージョンは、関数内できちんとした命名がなされていたにもかかわらず、全体としてはまだ紛らわしかったのです。しかし、このようにシンプルにするだけで、とても大きな影響を与えられるのです。常に全体像というものを考える必要があります。
コードのグループ化
異なる部分のコードをグループ化すると、ドキュメントとしても機能します。
たとえば、変数を宣言するときには一緒に使うべき変数をできるだけ近くに配置し、グループ化するように常に心がける必要があります。
コードのグループ化は、コードのさまざまな部分の関係を示すために使うことができ、将来的に変更が生じた際、誰でもどの部分に触れたら良いのかが簡単に見つけられるようになります。
次の例で見てください。
var foo = 1;
blah()
xyz();
bar(foo);
baz(1337);
quux(foo);
パッと見て、fooが何回使われているか分かりますか? 次と比べてみてください。
var foo = 1;
bar(foo);
quux(foo);
blah()
xyz();
baz(1337);
使われているfooがすべてグループ化されているので、コードのどの部分に依存しているのかが簡単に分かります。
pure関数を使おう
pure関数を使うと、状態に依存する関数よりも理解が簡単になります。
pure関数とはなにでしょうか? 同じパラメーターで関数を呼び出した場合、常に同じ出力が得られるならば、それがいわゆる「純粋な(pure)」関数と呼ばれるものです。つまり、pure関数は時間、オブジェクトプロパティ、Ajaxなど状態への依存や、いかなる副作用があってはならないことを意味します。
pure関数は出力に影響を与えるいかなる値も明示的に渡されるので、比較的理解がしやすいものです。すべて見ただけで分かるので、ある関数がどこから来ているのか、結果にどう影響を与えるのか、などを知るためにあちこち調べる必要はありません。
pure関数が自己文書化コードをより多く作る別の理由として、出力が信頼できることが挙げられます。なにがあろうとも、pure関数は常に入力されたパラメーターの内容のみに基づいて出力を返します。外部には影響を及ぼすことはないので、予期しない副作用を引き起こすこともなく、信頼のおけるものなのです。
よくある間違いの例の1つに、document.write()があります。経験豊富なJavaScriptの開発者はdocument.write()を使ってはいけないことをよく分かっているのですが、多くの初心者は使おうとしてつまずきます。たまには、うまくいくこともあるのですが、別のとき、ある状況下においては、ページ全体をサッパリと消し去ることがあるのです。これが副作用です!
SitePointに掲載された『Functional Programming: Pure Functions』の記事は、pure関数についてより良い説明をしているので参考にしてください。
ディレクトリとファイル構造
ファイルやディレクトリを命名するときは、プロジェクトで使用したのと同じ命名規則に従ってください。プロジェクトの明確な規則がない場合は、選択した言語の標準に従います。
たとえば、UIに関連する新しいコードを追加する場合は、プロジェクト内にある同様の機能がある場所を見つけ出します。UIに関連するコードがsrc/ui/に配置されているなら、同様にsrc/ui/に配置してください。
すでにプロジェクト内のコードのほかの部分について理解していることを前提に、簡単にコードを見出したり、目的を示したりできるものです。すべてのUIが同じ場所にあるので、結局のところUI関連のコードとなるのです。
命名
コンピューターサイエンスにおける2つの難題についての有名な言葉があります。
コンピューターサイエンスにおける難題はただ2つだけ。キャッシュの無効化と命名です。— Phil Karlton
それでは、自己文書化コードでの命名をどのようにすべきか、説明していきましょう。
関数をリネームしよう
関数の命名は、通常、それほど難しくはありませんが、それでもいくつか従うべきルールがあります。
- “handle”や“manage”のような曖昧な言葉を使用しないでください。handleLinks()、manageObjects()などはどのように作用するの不明確です
- 具体的な動詞を使います。cutGrass()、sendFile()のような具体的になにかを実行する関数にします
- 戻り値を示します。getMagicBullet()、readFile()は常に実行するものではありませんが、意味をなす場所では役に立ちます
- 強い型付を持つ言語は、戻り値を示すのに役立つように、型シグネチャが使えます
変数をリネームしよう
変数に関しては、2つの大まかな方法があります。
- 単位を示します。数値のパラメーターがある場合は、想定される単位を含めることです。たとえば、widthの代わりのwidthPxは、なにかほかの単位ではなくピクセルが単位だと示しています
- ショートカットは使わないようにします。ループのカウンターを除いて、aやbなどは許容された命名ではありません。
確立された命名規則に従う
コードは同じ命名規則に従います。たとえば、特定の種類のオブジェクトがあれば、同じ名前で呼ぶようにします。
var element = getElement();
いきなり「node」と呼び始めてはいけません。
var node = getElement();
コード内で別の場所でも同じ規則に従えば、コードを読む人は誰でも他の場所で意味していた内容から、確信のある推測で意味が分かります。
意味のあるエラーを使おう
Undefinedはオブジェクトではありません!
誰もが好きなものです。次のJavaScriptの例に従い、コードが投げかけるいかなるエラーに対しても、意味あるメッセージが存在することを確認します。
エラーメッセージを意味あるものにするにはどうすればよいのでしょうか?
- なにが問題なのかが記されているべきです
- 可能であれば、エラーの原因となった任意の変数の値やその他のデータを含めるべきです
- 重要なポイントですが、なにが間違いにつながったのかをすぐに見つけ出せるようなエラーにする必要があります。したがって、関数がどのように作用するのかドキュメントとして機能するべきです
構文
自己文書化コードの構文に関連するメソッドは、もう少し言語固有にできます。たとえば、RubyやPerlであれば、いろいろな種類の複雑な構文のトリックを使えるのですが、JavaScriptでは一般的に避けるべきです。
では、JavaScriptではどうなるか説明します。
構文のトリックは使わないようにしよう
複雑なトリックは使わないことです。コードを読んだ人を混乱させるような例を挙げます。
imTricky && doMagic();
同じ意味ならこのように書き換えたほうが、よっぽどまともなコードになります。
if(imTricky) {
doMagic();
}
常に後者の形を使いましょう。構文トリックは誰の役にも立ちません。
名前付き定数を使い、マジックナンバーは避けよう
コードに特別な値(数値や文字列値などのような)がある場合、代わりに定数を使うことを考えてください。コードが今ははっきりしているように見えても、1、2か月後に見直してみると、たいてい、なぜ特定の数値があるのかが、誰にも分からなくなるものです。
const MEANING_OF_LIFE = 42;
(ES6を使っていなければ、varを使用すれば同じように作用します)
ブーリアン型フラグの使用は避けよう
ブーリアン型フラグは、理解が難しいコードの1つで、このようなものです。
myThing.setData({ x: 1 }, true);
trueは何を意味しているのでしょうか。setData()のソースを調べて見つけない限り、絶対に解決しないのです。
代わりに、別の関数を追加するか、既存の関数の名前を変更します。
myThing.mergeData({ x: 1 });
このように書くと実行内容がすぐに分かるようになります。
言語特性を有効に使おう
あるコードの意図をより良く伝えるため、選んだ言語の特性を使うこともできます。
JavaScriptでの良い例は配列を反復する方法です。
var ids = [];
for(var i = 0; i < things.length; i++) {
ids.push(things[i].id);
}
上のコードでは、新しい配列の中にIDのリストを集約しています。ただし、知っておくべきは、ループのボディ全体を読む必要があるということです。map()を使った例と比較してみます。
var ids = things.map(function(thing) {
return thing.id;
});
上の例の場合、 新しい配列でなにかを生成するとすぐに分かります。ループロジックが複雑な場合、特に便利な手法です。list of other iteration functions on MDNも参照してください。
JavaScriptでよくある別の例は、constキーワードです。
多くの場合、絶対に変更しない値の場所の変数を宣言します。よくある一般的な例は、CommonJSモジュールをロードするときです。
var async = require('async');
これを絶対に変更しないという意図をもっとはっきりできます。
const async = require('async');
さらにメリットがあり、誰かが誤って変更した場合、エラーが表示されます。
不適切な例
これらの方法を用いると、よりよいものにできるでしょう。しかし、いくつか注意しなければならないこともあります。
短い関数にするための抽出すること
なかには、ごくごく短い関数の使用をすすめる人もいますし、短い関数を1つずつ抽出するのは、お手のもでしょう。しかし、コードがどれだけ分かりやすいかという点では悪い影響を及ぼしかねません。
たとえば、いくつかのコードをデバッグするとします。関数a()、次にb()、それからc()というように使っていきます。
短い関数は優れていて理解しやすいものですが、1か所でのみ関数を使用している場合は、「式を変数に置き換えよう」の項目の方法を代わりに使ってみてください。
無理強いしないこと
一般的に、絶対正しい方法というものはありません。ふさわしいアイディアだと思えない場合は、決して無理をしないことです。
最後に
自己文書化コードを作ることは、コードの保守性が向上する長い道のりです。コメントを付けるということは、後に保守が必要ということです。可能な限りコメントを排除するのが良いでしょう。
しかし、自己文書化コードは、ドキュメントやコメントに置き換わるものではありません。たとえば、コードは意図の表現が制限されているので、良いコメントもまた必要なのです。ライブラリーが非常に小さなものでない限り、コードを読み込むのは現実的ではないですし、APIドキュメントはとても大切なものなのです。
(原文:15 Ways to Write Self-documenting JavaScript)
[翻訳:皐月弥生]
[編集:Livit]