finallyな書き方、デストラクタな書き方

Posted by Hiraku on 2011-11-17

前回の記事の補足。微妙にコード例がわかりにくい気がしたので、同じようなコードをfinallyで書いた場合とデストラクタで書いた場合の比較をしてみます。

題材

やっぱりファイル操作しか思いつかなかったので、ファイルを追記モードで開いて書きこむだけのlogger()関数を実装することを考えました。かなり適当な作りですので、実用性は微妙です。

logger(file, message, time=null)
string file: 書き込むファイル名。もし書き込めないファイルを指定した場合はdefault.logに書き込む。default.logは必ず書き込めるものとしてよい。
string message: 書き込む文字列。
string time: ログに出す時間を指定する。省略した場合は現在時刻が書き込まれる。

fileに書き込めないファイルパスを突っ込んだ場合、timeにパースできない文字列を指定した場合、あたりが怪しいでしょうか。

finally(JavaScript)

ひとまずnode.jsで書いてみました。ファイル操作と言えばFileSystemモジュール(fs)を使えばいいですね。簡単のために同期系関数を使っています。

#!/usr/bin/env node

var fs = require("fs");

function logger(file, message, time) {
    try {
        var fd = fs.openSync(file, "a");
    } catch (e) {
        var fd = fs.openSync('default.log', "a");
    }

    try {
        var date = time ? new Date(time) : new Date;
        if (!isFinite(date)) {
            throw new RuntimeError;
        }
        fs.writeSync(fd, date+" "+message+"\n");
    } catch (e) {
        console.log(e);
    } finally {
        fs.closeSync(fd);
    }
}
logger('test.log', 'JavaScript Logger');

Dateオブジェクトは例外を吐いてくれないので、ちょっと無理矢理になってます。。finallyでcloseを忘れずに行います。

デストラクタ(PHP)

PHPはfopen()などのファイル操作関数もありますが、SplFileObjectを使うとすっきり書けます。こっちの方がモダンでしょう。

#!/usr/bin/env php
<?php

function logger($file, $message, $time=null) {
    try {
        $fd = new SplFileObject($file, 'a');
    } catch (Exception $e) {
        $fd = new SplFileObject('default.log', 'a');
    }

    try {
        $t = new DateTime($time);
        $fd->fwrite($t->format('r')." $message\n");
    } catch (Exception $e) {
        trigger_error($e->getMessage());
    }
}

logger('test.log', 'PHP Logger');

$fdの開放は関数を抜けて変数が消える際に自動的に行われるので、明示的に書く必要はありません。

※ぶっちゃけ、LLはリソース解放も自動でやってくれたりするので、fclose()的なものを考えなくても動作することが多いです(書き方としていいか悪いかは別として)。

所感

  • PHPの方が短いように見えるが、すっきり書けるまでの制約が多い。
    • 処理がきちんとクラス化され、デストラクタが記述されていること。
    • 関数で囲ってあること。ループ中に直書きされたりするとまずい。
    • デストラクタの自動起動順序は制御できない。複数のリソースの解放順を制御したいならunset()で手動起動する必要がある(レアケースかも?)
  • finallyを毎回書かなければならないのは煩雑。
    • try〜finallyを丸ごと関数化してしまえば、まあいいか。

ループ中だとまずい、というのは、要するに以下のようなケースです。

$a = new Klass();
$a = new Klass();

同じ文を2回書いただけですが、construct, construct, destruct, destructの順に呼ばれます。よく考えれば当たり前で、代入演算子「=」は右側から評価されるため、2行目の処理はnew Klassが先に呼ばれ、元の$aの解放は後になります。私はたまにこれでハマることがあります。

construct=>destructの順に呼びたいなら手動でunset()することになるでしょう。

$a = new Klass();
unset($a);
$a = new Klass();

これが嫌なら極力関数化して、変数が自動破棄されるようにしておくことでしょうね。

とりあえず、「tryとcatchごとに同じ処理を書かないといけない…」みたいなのは、デストラクタと関数化を駆使すれば、たぶんちゃんと綺麗に書けると思います。書きやすいかどうかは別ですが。

keyword: PHP javascript

codingの最新記事