とりあえず動けばいい、の精神でコードを書ける個人開発とは違い、仕事やオープンソースプロジェクトにおけるコーディングでは、「他人が読むコード」を意識して書く必要があります。
他人が読むのですから、もちろんわかりやすいコードでなくてはなりません。でも、「わかりやすい」とは何でしょう。どうしたら実現できるのでしょう。
この記事では、他人が読む可能性があるコードを書くときに気をつけていた方がいい事項について、いくつか紹介します。
まえがき
私が社会人になって数ヶ月が経ちました。会社では自分でコードを書くのはもちろん、他人の書いたコードをいじるという、今まであまり経験していない作業をすることもあります。
そしてその中で使われている言語にはJavaScriptも含まれます。JavaScriptは平易な言語であり、クライアントサイドでもサーバサイドでも採用できるので、様々な場面で使用されています。
さすがプロだけあって、綺麗なコードを渡されて感心することもあります。ですが中にはとても豪快なコードを書く人もいます。個性があるのは結構なのですが、これは共同の仕事ですので、できれば個性を抑えたおとなしいコードを書いてほしいと思うこともあります。
しかし、他人が読むことを意識してコードを書くのは、そう簡単ではありません。そこでこの記事では、いくつかのコツを紹介したいと思います。
他人が読むコード
自分が書いたコードは、自分だけが読むとは限りません。仕事の同僚、取引先の人、あるいは全く見ず知らずのプログラマが読むかもしれません。また、「将来の自分」も一種の他人として見ることもできるかもしれません。未来の自分は、今の自分が書いたコードのことなんて忘れてしまっているのですから。
自分と他人は違う存在なわけですから、自分の書いたコードの意図が完全に伝わるとは限りません。それどころか10%も伝わるか怪しいところです。そして、意図を理解できないコードに対して機能の追加・変更を加えると、どうしても時間がかかってしまいますし、バグが混入する可能性も高くなります。
もしコードの意図が十分に伝わっていれば、機能の追加も容易ですし、バグを発見しやすくなります。「このコードはなぜこういうふうに書かれているのか」が伝わると、品質を底上げすることにつながります。
そこまで他人に気をつかっている余裕はない、という時もあるでしょうが、余裕があれば他人が読むことを意識してコードを書いてみましょう。
他人に読んでもらうコードを書くコツ
無駄にライブラリを使わない
他人が読むコードにおいてもっとも大事なことは、無駄にライブラリを使わないことです。より正確に言えば、標準機能で実現できることにライブラリを使わない、ということです。
たとえばjqueryやlodashなどが有名ですが、近年ではこれらの機能はJavaScriptの仕様に取り込まれていて、ライブラリを使う必要はありません。
// jqueryによる要素の選択
const element = $('.selector');
// JavaScriptの標準APIによる要素の選択
const element = document.body.querySelector('.selector');
これらのことにライブラリを使うと、挙動が予測しにくくなります。標準APIであれば、MDNのような高品質でドキュメント化されたページがいくつかありますし、最悪の場合でも仕様書をあたれば挙動が理解できます。ですがサードパーティのライブラリを使うと、十分な品質でドキュメント化されてなかったり、標準APIと仕様が違ったりして、挙動の予測が難しくなります。
特にjqueryは現代において全く使う必要がなく、使えば混乱をもたらします。不要なライブラリは使わず、標準の機能を使うようにしましょう。
マジックナンバーには名前をつける
ハードコーディングされている謎の数字のことをマジックナンバーと言います。
// この「100」は何?
const pages = Math.ceil(records.length / 100);
マジックナンバーは読み手を大きく混乱させることになります。できるだけ値には名前をつけるようにしましょう。
// ああ、1ページあたり100件ってことね!
const maxRecordsPerPage = 100;
const pages = Math.ceil(records.length / maxRecordsPerPage);
もちろん名前をつけなくてもいい数字もあります。例えば角度をラジアンに変換するとき、「180」に名前をつける必要はないでしょう。
// ここで180の意味がわからない人はいないでしょう
const radian = degree * Math.PI / 180;
識別子を短くしない
識別子(関数名、変数名など)をむやみに短くしないというのも大事です。これはC言語やPythonに慣れ親しんでいる人がしがちなのですが、現代において変数名を短くする必要性は全くありません。長い識別子もエディタやIDEが正確に補完してくれるからです。
名前が省略されていると、本来の名前を推測する必要が出てきます。そうすると無駄な時間がかかりますし、最悪の場合は全く違う理解をしてしまうことがあります。
例えば以下のようなコードがあるとします。このときこれらの変数が何を意味しているか、すぐにわかるでしょうか?
// この変数はどういうこと?
const cx = 5;
const cy = 10;
これらの変数についている「c」は「center」でしょうか、あるいは「camera」でしょうか。それとも別の何かでしょうか。コードの別の部分を読めばはっきりするかもしれませんが、理解するのになかなかのコストがかかります。
この程度の単語を省略する必要はありません。名前ははっきりと書きましょう。
// 意味がはっきりした!
const centerX = 5;
const centerY = 10;
関数名についても同じです。多少長くなってでも正確な名前付けを徹底すべきです。
// 関数名から機能を把握しにくい!
function fetchTrk(id) {
return fetch('./api/tracking/' + id);
}
// これなら多少わかりやすい
function fetchPersonTrackingData(personId) {
return fetch('./api/tracking/' + personId);
}
正確な名前は正確な理解につながります。名前を省略するメリットより、長くなってもわかりやすく書くメリットの方が大きいので、識別子の省略はしないようにしましょう。
しかし、省略してもプログラマが容易に理解できるものもあります。例えば「str」は「string」の略だとすぐにわかりますし、「doc」は「document」の略だとわかります。こういった場合は省略してもかまわないでしょう。
また、スコープが短い変数に短い名前をつけるのも問題ありません。スコープが1行や2行程度なら1文字の変数名でもよいでしょう。
コメントを書く
初歩的な事柄であるわりに効果が大きいのがコメントです。コメントは、コードを読む人が意味を理解するための大きな助けになります。
// テキストを読み込んで行ごとに分けるが、空行は無視したいのでfilterにかける
const rawText = await fetch('sample.txt').then((response) => response.text());
const lines = rawText.split(/[\r\n]+/).filter((line) => line !== '');
コードを見ただけではわかりにくい箇所も、コメントを入れることでわかりやすくなります。
教本などでは「WhatではなくWhyを書け」と書いているものもありますが、わかりやすくなるコメントならなんでも自由に書いてください。
// この関数はサーバと通信して音楽を検索する
async function findMusics(query) {
}
コメントにより「この関数はサーバと通信する」という事実が判明しました!プログラムを深く読み込まないと把握できない要素に関する情報がコメントで書かれていると、非常に助かります。
また、定数にコメントをつけるのも有効です。
// 描画処理が重いので、1ページあたり50件程度に抑えるのがいい
const MAX_RECORDS_PER_PAGE = 50;
このコメントにより、値が100や200ではまずいということがわかります。
ただし無意味なコメントもあります。
// ボタンをクリックしたときの処理
function onClickButton(event) {
}
この例では、コードのシグネチャから得られる情報とコメントから得られる情報があまり変わりません。コメントはできるだけ情報が増えるように書きましょう。
変数の宣言にvarではなくletとconstを使う
JavaScriptでは、変数の宣言には3種類のキーワードが使えます。その中でもvarを使う人が多いですが、できればletとconstを使いましょう。varは古いキーワードなので使う必要はありません。
letは変数の値が後から変更される可能性を明らかにし、constは値が後から変更されないことを示します。これによって、どの変数の値を追えばいいのか、または追わなくていいかということが明確になります。
// この値は後から変更されるのか、されないのかがわからない!
var radius = 3;
// この値は後から変更されるということがわかる
let currentPage = 3;
// この値は固定であることがわかる
const maxItems = 10;
また、letとconstはスコープに関して素直な性質を持つので、変数をとても扱いやすくなります。より詳しくは「JavaScriptにおけるvar/let/constの使い分け」をご覧ください。
高階関数を正確に使う
高階関数(関数を引数にとる関数)はプログラミングの自由度を大幅に広げてくれます。しかしそれゆえに不適切な使い方もできてしまいます。
例えばArrayオブジェクトはいくつかの高階関数を持っています。そして、そのいずれも要素をイテレートするという共通点があります。そのため不適切な使用方法をとる人がたまにいます。例えば以下のようにです:
const numbers = [5, 8, 2, 10, 7];
// numbersに10以上の数が含まれているか調べる
let found = false;
numbers.some((v) => {
if(v >= 10) {
found = true;
}
});
someメソッドは、本来は配列の中に渡されたテスト関数を通過するものがあるかどうかを調べるメソッドです。ですが、ここでは単に要素のイテレーションに使われています。
こういった不適切な使い方はコードの読み手に不要な負担をかけることになります。今回の場合はfor文やforEachメソッドを使用する、またはsomeメソッドを正確に使用するのが適切です。
const numbers = [5, 8, 2, 10, 7];
// numbersに10以上の数が含まれているか調べる
const found = numbers.some((v) => v >= 10);
メソッド本来の使い方を無視してコードを書くと、理解するのに時間がかかるコードになってしまいます。それを避けるためにも、メソッドの本来の意味と使用方法については十分調べておきましょう。
デフォルト引数はオブジェクトにする
JavaScriptにはデフォルト引数という機能があります。主に関数にとって必須ではない引数を省略可能にするのに使われます。
1個か2個程度の引数なら問題にはならないのですが、数が増えてくるとなかなか複雑なインターフェイスになってしまいます。
// 複数のデフォルト引数を使っている
function findBookRecords(queryString, useCache = true, maxRecords = 50, caseSensitive = false) {
// 取得処理
}
// どの引数が何を意味しているのかわかりにくい
const bookRecords = findBookRecords('JavaScript', true, 10, true);
引数の意味がわかりにくいですし、順番もよくわかりません。関数の引数を理解するために、定義している場所を探さないといけなくなります。
こういったときは、デフォルト引数をひとつのオブジェクトにしてしまうと読みやすくなります。
// デフォルト引数をオブジェクトにしている
function findBookRecords(queryString, options = {}) {
const useCache = ('useCache' in options) ? options.useCache : true;
const maxRecords = ('maxRecords' in options) ? options.maxRecords : 50;
const caseSensitive = ('caseSensitive' in options) ? options.caseSensitive : true;
// 取得処理
}
// 引数の意味がわかりやすく、順番にも左右されない
const bookRecords = findBookRecords('JavaScript', { useCache: true, maxRecords: 10, caseSensitive: true });
こうすることで関数呼び出しが読みやすくなりますし、順番にも左右されなくなります。また拡張性も高くなるので、将来的に変更があってもすぐに対応できます。
ネストを浅くする
深いネストは諸悪の根源です。ネストが深いと人は流れを理解できなくなり、破滅します。普通の人間が理解できるネストはせいぜい2重ネストが限度で、3重ネストになるとほとんどの人が意味を理解できなくなります。
// これはいったい何をやっているの?
const intersection = [];
for(const record of recordList) {
for(const dbRecord of dbRecordList) {
if(record.id === dbRecord.id) {
intersection.push(record);
}
}
}
これぐらいならまだ読めますが、実際はもっと複雑な処理であることが多く、ネストもさらに深いものになりがちです。
ネストが深くなるときは関数として切り出すか、様々な工夫をしてネストを減らしてみましょう。
// ネストなし!
const dbHasSameRecord = (record) => dbRecordList.some((dbr) => record.id === dbr.id);
const intersection = recordList.filter(dbHasSameRecord);
さいごに
他人に読んでもらうためのコード、つまり「読みやすいコード」を書くのは、そう簡単なことではありません。何も考えずにコードを書いていると、なかなかにものすごいコードが出来上がってきます。
しかし、読みやすいコードを書くためのコツ自体は、どれもシンプルです。要は「書き方を簡潔にする」を、言葉を変えて何度も繰り返してるだけなのですから。ですが単純だからといってバカにしてはいけません。単純な努力の積み重ねが、最終的に綺麗なコードを生み出すのです。
仕事でコードを書いていると、綺麗なコードを書くための余裕がないかもしれません。それでも、ほんの少しでも余裕があれば「これは他の人がメンテナンスすることになるかもしれない」ということを考えて、読みやすいコードを意識してみましょう。