前回の記事の補足。微妙にコード例がわかりにくい気がしたので、同じようなコードを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