Strings of Life

PHP/Phalcon/MySQL/JavaScript/RegExp/Ruby/Perl/ActionScript

2013年10月






「コンピュータ」の一歩手前、情報理論がメイン。

情報理論とは、「データ(容れ物)」と「情報(中身)」の区別とか、情報を運搬するチャネルの太さだとかいったお話のこと。

第3章のデーマである有限オートマトンも、コンピュータ理解の基礎ではあるけど、現代のコンピュータに直結するわけではない。

そこからいきなり、第4章でメモリの仕組み(参照局所性)の話になる。はっきり言って唐突なのだけど、この第4章が一番面白かった。「処理を高速にしたい」「部品は安く済ませたい」という相反する要求を叶えるため、コンピュータのメモリでは魔法のような処理が行われている。

TAP(Test Anything Protocol)とは、テストを簡潔に記述するための書き方(プロトコル)のことです。

プロトコルというと難しそうですが、実際はとても簡単。標準出力に、以下のような出力を行うプログラムを作成すればよいのです。

    1..4
    ok 1 - Input file opened
    not ok 2 - First line of the input valid
    ok 3 - Read the rest of the file
    not ok 4 - Summarized correctly # TODO Not written yet 

要点は
(1) 最初の行に、テストの件数を表示する
(2) 2行目以降に、テスト結果(ok/not ok)・テスト番号・説明を表示する
以上。

なお、2行目以降の必須項目はテスト結果(ok/not ok)だけです。
テスト番号は無くてもエラーにはならないものの、あったほうがよい(実行予定テストの総数と、実際に走ったテストの総数が異なっている時に警告が出る)。
説明は、後で読む人のためにも書いておいたほうがよいです。

それでは実際に書いてみます。


tap.php
<?php

/**
 * @param int $number_of_tests
 */
function test($number_of_tests = 0)
{
    if ($number_of_tests > 0) {
        echo '1..', $number_of_tests, PHP_EOL;
    }
}

/**
 * @param        $got
 * @param        $expected
 * @param int    $test_number
 * @param string $description テストの説明
 * @param string $directive   「TODO」又は「SKIP」+ TODO/SKIPである理由
 */
function is($got, $expected, $test_number, $description = '', $directive = '')
{
    if ($expected === $got) {
        echo 'ok ', $test_number;
    } else {
        echo 'not ok ', $test_number;
    }
    if ($description !== '') {
        echo ' - ', $description;
    }
    if ($directive !== '') {
        echo ' # ' . $directive;
    }
    echo PHP_EOL;
    if ($expected !== $got) {
        echo '# got:      ', $got, PHP_EOL;
        echo '# expected: ', $expected, PHP_EOL;
    }
}
fizzbuzz.php
<?php
function fizzbuzz($n) {
  return $n;
}
fizzbuzz.t
#!/usr/bin/env php
<?php

error_reporting(E_ALL);

require_once 'tap.php';
require_once 'fizzbuzz.php';

test(15);
is(fizzbuzz(1), 1, 1);
is(fizzbuzz(2), 2, 2);
is(fizzbuzz(3), 'fizz', 3, '三の倍数');
is(fizzbuzz(4), 4, 4);
is(fizzbuzz(5), 'buzz', 5, '五の倍数');
is(fizzbuzz(6), 'fizz', 6, '三の倍数');
is(fizzbuzz(7), 7, 7);
is(fizzbuzz(8), 8, 8);
is(fizzbuzz(9), 'fizz', 9, '三の倍数');
is(fizzbuzz(10), 'buzz', 10, '五の倍数');
is(fizzbuzz(11), 11, 11);
is(fizzbuzz(12), 'fizz', 12, '三の倍数');
is(fizzbuzz(13), 13, 13);
is(fizzbuzz(14), 14, 14);
is(fizzbuzz(15), 'fizzbuzz', 15, '三の倍数 かつ 五の倍数');

TAPに従って書かれたテストを実行するには、proveというコマンドを使います。Perlの実行環境があるなら、インストールされているはず(Mac OS Xには標準で入っていますし、大抵のLinix/Unixには入っているでしょう)。

「prove fizzbuzz.t」を実行すると、「fizzbuzz.t .. Failed 7/15 subtests」と表示されます。15回のテストのうち、7回失敗したということです。「php fizzbuzz.t」を実行してみると、以下のような表示がされます。

not ok 3 - 三の倍数
# got:      3
# expected: fizz

最初のfizzbuzz.phpでは、fizzbuzzのロジックを組み込んでいなかったので、テストにこけるのは当たり前。fizzbuzz.phpの中身を差し替えましょう。

fizzbuzz.php
<?php
function fizzbuzz($n) {
    if ($n % 15 === 0) {
        return 'fizzbuzz';
    }
    if ($n % 5 === 0) {
        return 'buzz';
    }
    if ($n % 3 === 0) {
        return 'fizz';
    }
    return $n;
}
今度は、fizzbuzzのロジックをちゃんと組んであるので、テストに通るはずです。再度「prove fizzbuzz.t」を実行すると…「fizzbuzz.t .. ok」と表示されました!

このように、TAPを利用したテストは、*Unit等を使用したテストに比べ、(1) テストが簡単に書ける (2) 導入が非常に簡単である という特徴があります。

本格的なプロジェクトであれば、PHPUnit等のテスティングフレームワークを導入したほうがいいと思いますが、小規模なスクリプトのテストならTAPで十分でしょう。

<参考>

「同じコード」の同じって何さ - TAPのススメ
:tap.phpは、こちらのtap.rbのロジックをPHPで書き直し + 一部改変したものです。
TAP Specification:TAPの仕様






永和システムマネジメントの中の人が、受託開発の手法について書いた本。永和といえば、アジャイル開発の方法論に強い会社である、という印象がある(実際、自社サイトのタイトルに「アジャイル開発」というワードを入れている)。

しかし、本書の著者である岡島氏は、必ずしもアジャイル開発の信奉者というわけではない。従来型のプロセス(ウォーターフォール開発)と、アジャイル開発は、必要に応じて使い分けるべし、としている。

航空管制システムのようにミッションクリティカルで大規模なプロジェクトには従来型のプロセスの採用が必要です。一方、アイディア勝負のネットわービスを競合に先駆けて素早く立ち上げたい場合には、アジャイルプロセスの採用が適しています。 - 『受託開発の極意』p.102

ウォーターフォールとアジャイルの両方を熟知している著者ならではの、「現実離れしない程度に理想的な開発手法の提案」が本書の持ち味。

参考文献も充実しており、本書を起点に学びを広げていくことができる。開発手法に興味のある人にはおすすめ。

JavaScriptで、初期化と同時にオブジェクトにメソッドを持たせるには、(継承を除くと)2つの方法があります。コンストラクタでメソッド定義する方法と、プロトタイプオブジェクトのメソッドとして定義する方法です。
// コンストラクタ
var Member = function(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.getName = function(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

// プロトタイプ
var Member = function(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
}
Member.prototype = {
    getName: function() {
        return this.lastName + ' ' + this.firstName;
    }
}

いずれの方法でも、MemberオブジェクトにgetName()メソッドが定義されます。違いは、メモリの使い方にあります。

コンストラクタは、インスタンスを生成する度に、それぞれのインスタンスのためにメモリを確保します。同じ処理を行うメソッドでも、インスタンスが異なればそれぞれのメソッドのぶん、メモリを消費します。

一方、プロトタイプオブジェクトは、元となるオブジェクトから暗黙的に参照されています。あるオブジェクトを元に複数のインスタンスを作ったとしても、メソッドはプロトタイプオブジェクトに定義されている1つだけです。

そのため、メソッド定義はコンストラクタで行うよりも、プロトタイプオブジェクトに持たせたほうが、メモリ効率が良くなります。


また、プロトタイプオブジェクトを変更すれば、同一のオブジェクトからインスタンス化された全てのオブジェクトに変更が行き渡る、という点も、プロトタイプの特徴です。


なお、「初期化と同時」という条件を外せば、以下のような追加も可能です。
var mem = new Member('John', 'Smith');
mem.getName = function () {
    return this.lastName + ' ' + this.firstName;
}

この場合、Memberオブジェクトをインスタンス化したmemオブジェクトにはgetName()メソッドが追加されますが、この追加が別のMemberオブジェクトに影響を及ぼすことはありません。

また、メソッドやプロパティが呼び出された際の探索の順番は、(1) オブジェクト自身のメソッド・プロパティ (2) オブジェクトが参照しているプロトタイプオブジェクトのメソッド・プロパティ となります。そのため、↓のように、オブジェクト自身とプロトタイプに、同じ名前で別の処理を行うメソッドを追加した場合、オブジェクト自身に持たせたメソッドの方が優先されます。
var mem = new Member('John', 'Smith');
mem.getName = function () {
    return this.lastName + ' ' + this.firstName; // last + first
}
Member.prototype = {
    getName: function () {
        return this.firstName + ' ' + this.lastName; // first + last
    }
}
console.log(mem.getName()); // Smith John


クロージャとは、「ローカル変数を参照している関数内関数」。
function closure(init) {
    var counter = init;

    return function() {
        return ++counter;
    }
}

var myClosure0 = closure(0);
var myClosure100 = closure(100);

console.log(myClosure0()); // 1
console.log(myClosure100()); // 101
console.log(myClosure0()); // 2
console.log(myClosure100()); // 102
var myClosure1 = closure(0) で、変数 myClosure0 に、function(){return ++counter} という匿名関数が登録される。その後は、myClosure0() を呼び出す度に、myClosure0 が内部で保持しているローカル変数 counter の値がインクリメントされて返される。

別の変数に格納されたクロージャは、別のスコープチェーンに属すので、myClosure0 と myClosure100 は、同じ名前のローカル変数 counter を内部に保持しているが、counter に入っている値は異なる。

※本文中のサンプルコードは、『JavaScript本格入門』p.165を参考にしている。




このページのトップヘ