NZ MoyaSystem

ニュージーランド在住のプログラマがあれこれ書くブログ

コメントのいらないプログラムの書き方

Some codes


「プログラムには適切にコメントを書きましょう」
プログラミングの学習を始めると、ほぼ誰もが教わることです。
確かに、適切に書かれたコメントはコードを読みやすくし、理解を助けてくれます。

しかし、そもそもコメントって必要なのでしょうか?
もし、コメントがないと意味がわからないコードがあったとすれば、その書き方に問題があるのではないでしょうか?

コードの読みにくさをコメントで解決するのは、安易な方法に逃げているだけかもしれません。

実は、僕の働いている会社では 「原則としてコメントを書かない」のがルールになっており、それで困ったことはほとんどありません。(100人以上のデベロッパーがいる現場です)
もう入社して2年になりますが、ぶっちゃけコメントって書く必要がないなと思うようになりました。

この記事では、 誰でもコメントのいらないプログラムが書けるようになる方法を丁寧に紹介していきます!

実装する機能の例

例として、映画館のチケット料金を計算する関数を作ってみます。

要求仕様は、

  • 13歳以上はおとな料金で1800円
  • 13歳未満はこども料金で1200円
  • 毎月1日はサービスデー料金で一律1000円

とします。

レディースデーとかクーポン料金とか考えるともっと条件を複雑にできますけど、まずは単純な例からはじめましょう。

関数の名前を決める

まずは関数にどんな名前がふさわしいか考えましょう。
「動詞 + 目的語」の形に統一しておくと誰からみても何をする関数なのかわかる = コメントのいらないプログラムになります。
たまに「kansu...」とか「func...」とかいう命名を見かけることがありますが、「これは関数だよ!」と言われたところで何もうれしくないのでやめましょう。文字数の無駄です。
代表的な例を表にまとめたので参考にしてください。

機能 名前
値を代入する set...
値を取得する get...
計算する calculate...
変換する convert...to...
真偽値を返す is...

今回は「チケット価格を計算する関数」なので、「calculateTicketPrice」としてみましょう。
なお、コードは JavaScript をイメージして書いていきますね。

function calculateTicketPrice ( ) {
}

パラメータを決める

次に関数に渡すパラメータを決めます。
関数の名前で表現されている処理を実現するには、どれだけのパラメータがあればよいか? と考えてみましょう。
今回の例でいえば「お客さんの年齢」「日付」があれば、すべてのチケット価格が計算できます。
ということで、age と date の2つのパラメータを渡すことにします。

function calculateTicketPrice (age, date) {
}

パラメータの名前も、なにを表しているかわかるようにしてくださいね。
くれぐれも「hensu」とか適当な名前をつけたり、同じ変数にぜんぜん違う値を繰り返し代入したりすることのないようにしましょう。

テストを書く

次にユニットテストを書きましょう。
テストは常に更新される仕様書です。
業務ロジックをテストに説明させておけば、関数の仕様をコメントにいちいち書く必要などありません。
関数のテストケースを見れば、それが仕様書になっているからです。
プログラムが仕様どおりに動いているかも常にチェックできるし利点だらけ。
もしユニットテストのない現場で働いているなら、今すぐ上司に直訴して導入してください(まじで)。

ここでは JavaScript のテストフレームワークである mocha と、アサーションフレームワークの chai を使うと仮定して書いてみます。
mocha では日本語でテストグループ名と個々のテスト名を書くことができるので、記述形式がかなり自由です。
が、対象となる条件をはっきりさせるためにも「(条件)なら(動作)」という形式でテスト名を統一するのが良いでしょう。
例としてはこんな感じです。

var assert = chai.assert
describe('チケット価格計算', function(){
    var ADULT_PRICE = 1800;
    var CHILD_PRICE = 1200;
    var SERVICE_DAY_PRICE = 1000;
    it('13歳以上かつ1日でないなら大人料金を返す', function(){
        var priceAge14 = calculateTicketPrice(14, new Date(2018, 1, 2));
        assert.Equals(ADULT_PRICE, priceAge14);
        var priceAge13 = calculateTicketPrice(13, new Date(2018, 1, 2));
        assert.Equals(ADULT_PRICE, priceAge13);
    });
    it('13歳未満かつ1日でないなら子供料金を返す', function(){
        var priceAge12 = calculateTicketPrice(12, new Date(2018, 1, 2));
        assert.Equals(CHILD_PRICE, priceAge12);
    });
    it('1日ならサービスデー料金を返す', function(){
        var priceAge14 = calculateTicketPrice(14, new Date(2018, 1, 1));
        assert.Equals(SERVICE_DAY_PRICE, priceAge14);

        var priceAge13 = calculateTicketPrice(13, new Date(2018, 1, 1));
        assert.Equals(SERVICE_DAY_PRICE, priceAge13);

        var priceAge12 = calculateTicketPrice(12, new Date(2018, 1, 1));
        assert.Equals(SERVICE_DAY_PRICE, priceAge12);
    });
});


C# や Java などの言語のテストフレームワークでは、テスト関数の名称で条件を表さなければならない場合があります。
そんなときは、関数を「When (条件) Then (動作)」の形式で命名するようにしておくと、コメントなしでも条件が理解できるのでオススメです。
Java のテストフレームワークである JUnit 風に書くとこんな感じになります。

private int ADULT_PRICE = 1800;
private int CHILD_PRICE = 1200;
private int SERVICE_DAY_PRICE = 1000;

@Test
public void whenOlderThanThirteenAndNotFirstDateOfMonthThenReturnAdultPrice () {
    var priceAge14 = calculateTicketPrice(14, new Date(2018, 1, 2));
    assertEquals(ADULT_PRICE, priceAge14);
}

public void whenEqualsToThirteenAndNotFirstDateOfMonthThenReturnAdultPrice () {
    var priceAge13 = calculateTicketPrice(13, new Date(2018, 1, 2));
    assertEquals(ADULT_PRICE, priceAge13);
}


@Test
public void whenYoungerThanThirteenAndNotFirstDateOfMonthThenReturnChildPrice () {
    var priceAge12 = calculateTicketPrice(12, new Date(2018, 1, 2));
    assertEquals(CHILD_PRICE, priceAge12);
}

@Test
public void whenFirstDateOfMonthThenReturnServiceDayPrice () {
    var priceAge14 = calculateTicketPrice(14, new Date(2018, 1, 1));
    assertEquals(SERVICE_DAY_PRICE, priceAge14);

    var priceAge13 = calculateTicketPrice(13, new Date(2018, 1, 1));
    assertEquals(SERVICE_DAY_PRICE, priceAge13);

    var priceAge12 = calculateTicketPrice(12, new Date(2018, 1, 1));
    assertEquals(SERVICE_DAY_PRICE, priceAge12);
}

実装する

テストができたらいよいよ関数を実装していきます!
機能を少しずつ追加して、すべてのテストケースが正常終了するように作っていきましょう。
とりあえず何も考えずに実装すると、こんな感じになります。

function calculateTicketPrice (age, date) {
    if (date.getDate() === 1) {
        return 1000;
    }

    if (age >= 13) {
        return 1800;
    } else {
        return 1200;
    }
}

これでも仕様どおりに動きますが、可読性を考えるとまだ改善の余地があります。
たとえば、
「なんで getDate が1だと特別なの?」
「age >=13 と age < 13 ってどうして場合わけされてるの?」
といった点がコードを読んだだけではわかりませんね。

コード内の「1」や「1000」のように、変数に代入されることなく突然出てくる数字はマジックナンバーと呼ばれています。
マジックナンバーは可読性や保守性を下げるので、原則として避けた方がよいです。
これらをもっと意味のある名前をもつ変数に代入すればさらに読みやすいコードになります。
改善した例がこちらです。

function calculateTicketPrice (age, date) {
    var serviceDate = 1,
        ageOfAdult = 13,
        serviceDayPrice = 1000,
        adultPrice = 1800,
        childPrice = 1200;

    if (date.getDate() === serviceDate) {
        return serviceDayPrice;
    }

    if (age >= ageOfAdult) {
        return adultPrice;
    } else {
        return childPrice;
    }
}

これを読めば、

  • チケットの価格を計算する関数である
  • 年齢と日付をパラメータにとる
  • 毎月1日はサービス料金で1000円
  • 13歳以上ならおとな料金で1800円、それ未満ならこども料金で1200円

ってことが誰でもわかりますよね。

どうです? コメントいらない気がしてきませんか?

(※ 関数の中で宣言されている変数は定数として外部に宣言することもできそうですが、とりあえず今回は関数内変数としました)

コードレビューに出す

コメントのいらないプログラムを書くため、最後に必要なステップがコードレビューです。
自分自身できれいなコードが書けたと思っていても、他人が見て理解できなければ意味がありません。
コメントなしで読めるかどうかを第三者に判断してもらいましょう。

レビューする側も「どんなコードが読みやすいのか」「自分だったらどのように書くか」といった視点をもってプログラムを読んでみると、自分自身のコーディング力の向上にもつながります。

コードレビュー専用のツールはいろいろあるので、現場にあったものを探してみてください。

参考▶コードレビューのツールについての調査 - Qiita
qiita.com

「コメントのいらないプログラムの書き方」のポイント3つ!

今回紹介した方法のポイントをまとめると、次の3つです。

  1. 関数や変数に適切な名前がつけられていれば、コメントはいらない
  2. 仕様がユニットテストですべて記述されていれば、コメントはいらない
  3. コメント無しで第三者にも理解できれば、コメントはいらない

最初は違和感があるかもしれませんが、この方法でコードを書いていくとコードそのものの読みやすさがどんどん改善されていくので、ぜひとも参考にしてみてください!

もっと勉強したい人にはこちらもオススメ▶ 現役プログラマがおすすめするプログラミングスクール3選!