手続き型音楽の日常

関数型音楽に乗り換えたい

お兄ちゃん!そこは名前空間パターンだよ!

タイトルは釣りです(お約束)

名前空間パターンのススメ

みなさん、名前空間パターンを使っていますか。私はよく使いますよ。

一体なんの話だって?もちろん JavaScript の話ですよ。

最近はサーバで動かすプログラムも書けるほど、活躍の場を広げていますよね。もともとは Web ブラウザで動かすスクリプト言語でしたが。

というわけで、そんな JavaScript でのプログラミングで知らず知らずのうちに使われている 名前空間パターン に注目していきます。

名前空間パターンのイロハ

まず、 JavaScript の世界には 名前空間なんて無い ということをまず覚えておきます。これはとても重要なことです。

一般的にオブジェクト指向と呼ばれるプログラミング言語には、複雑な階層構造をサポートするために名前空間 (あるいはモジュール) という機構が備わっています。

これを用いると、 複数のファイルをまたいでプログラム部品をまとめる ことや、 フォルダ階層のようにして目的の部品を見つけやすくする ことができます。

たとえば、 "Alex.cs" と "Lexa.cs" というファイルがあったとします。

namespace Mitsurin.Service
{
    public class Alex
    {
        public dynamic Mic;
    }
}
namespace Mitsurin.Service
{
    public class Lexa
    {
        public dynamic Phone;
    }
}

あっ、手が勝手に C# のコードを書いてしまいました…許してください。

この場合、 namespace Mitsurin.Service という名前空間が宣言してあります。これは "Mitsurin の Service である ○○" という意味に見て取れるようになります。

Alex クラスと Lexa クラスは名前空間として宣言されているため、コンパイルすると、当然2つのクラスは同じ名前空間に属することになります。フルネームで表すと、つぎのようになります。

Mitsurin.Service.Alex クラス
Mitsurin.Service.Lexa クラス

例えば他に、 Mitsurin.Item 名前空間があったとします。この場合は、 "Mitsurin の Item である ○○" のような分類になります。

Mitsurin.Item.Echo クラス

C# では using 文で名前空間の記述を省略できるようになるのですが、今回の場合は

using Mitsurin.Service;
using Mitsurin.Item;

namespace hoge
{
    public class Program
    {
        static void Main(object[] args)
        {
            var x = new Alex(); // Mitsurin.Service.Alex
            var y = new Echo(); // Mitsurin.Item.Echo
            var z = new Mitsurin.Service.Lexa(); // フルネームで指定することも可能
        }
    }
}

のように書くことができます。

というわけで、これを JavaScript でやろうって話です。

やらないとどうなるの?

John くんは、駆け出しのプログラマです。ある日彼は、ネットでこんなプログラムを見つけました。

function getRandom(i) {
    return parseInt(Math.random() * i);
}

彼はこのプログラムを気に入りました。なんて発想だ!これまで3ヶ月も JavaScript とにらめっこしたのに、こんな発想は思いもよらなかった!引数で指定した数を上限に、ランダムな整数が関数1つで帰ってくるなんて!

そうだ、このプログラムを使って、面白いことをしよう。彼はすぐさま行動に移しました。10分後には、彼のディスプレイには素晴らしいスクリプトが表示されていました:

<script>
function getRandom(i) {
    return parseInt(Math.random() * i);
}
window.onload = function () {
    alert("Welcome to my homepage! Your lucky number is " + getRandom(100) + "!!!");
};
</script>

これは、ホームページを訪れた人みんなが楽しくなるだろう!彼はハイテンションで実際のホームページにアップロードしました。

ブラウザで彼のページを開くや否や、ダイアログが表示されました。 "Welcome to my homepage! Your lucky number is 52!!!"

大成功です! 彼は飛び上がらんばかりの喜びを感じました。

しかし、そのダイアログを閉じると表情は一変。これまで動いていた 背景に雪を降らせるライブラリ が動かず、ただ単色の背景が表示されてしまったのです。これは大問題。

なぜ動かなくなったのだろう?さっきまでは動いていたのに。調べていくと、どうやら彼は大きな過ちを犯していたようです。

彼のホームページに使われていた例のライブラリは、 random.js という外部ファイルを参照していました。これには次のような関数が書かれていたのです。

function getRandom() { return Math.random() * 100; }

そう、実は getRandom という名前の関数が別にあったのです! そして彼はその関数を上書きしてしまいました。もともと引数を受け取らない関数を、引数を受け取る関数に書き換えてしまったのです!

なんってこった!こんな罠に引っかかるなんて。彼はイライラしながら、自分の関数を書き換えていきました。


とまあ、大げさに書いてはみたものの、実際に起こりうる話です。

大きなウェブアプリになってくると、同じような名前の関数がいくつも出来上がり、まれにファイルをまたいで名前が衝突してしまうことがあります。

こうしたことを起きにくくする、という観点のもと、関数を階層構造にまとめる仕組みが出来上がりました。

それが 名前空間パターン です。

How to?

(function () {

    // グローバル空間にオブジェクトを生成 (既にある場合はそのまま)
    window.myApp = window.myApp || {};

    // グローバル空間のオブジェクトにプロパティを追加
    window.myApp.john = window.myApp.john || {};
    window.myApp.tony = window.myApp.tony || {};

    // 外からは参照できない
    var list = [];

    // 関数の定義
    window.myApp.john.getRandomA = function () {
        var r = parseInt(Math.random() * 1000);
        return r;
    };

    window.myApp.john.getRandomB = function (i) {
        var r = parseInt(Math.random() * i);
        return r;
    };

    // いくつも名前空間を生やすことができる
    window.myApp.tony.push = function (obj) {
        list.push(obj);
    };

    // 変数 (プロパティ) も可
    window.myApp.tony.count = [];
})();

名前空間パターンは、 グローバル空間に公開するオブジェクトを最小限にする 事から始まります。 JavaScript でのグローバル空間は、ブラウザでは window オブジェクト、 Node.js では global オブジェクトになります。つまり、この直下に作るオブジェクトを最小限にします。

上記の例では、グローバル空間に公開されているオブジェクトは window.myApp のみになります。また、即時関数パターンで囲まれているため、中で var キーワードによる変数宣言があっても、スコープを分けることができます。

yuzutan-hnk.hatenablog.com

こうすることで予期せぬ名前の衝突をできるだけ少なくし、移植性を高めます。

詳しいやり方

やり方はいろいろあります。とにかく、公開するオブジェクトを少なくすればよいです。

(function () {
    window.myApp = window.myApp || {};
    window.myApp.func = function () { return void 0; };
})();
var myApp = {};
(function () {
    myApp.func = function () { return void 0; };
})();
var myApp = {
    func: function () { return void 0; },
};
window.myApp = (function () {
    var num = 100;
    return {
        func: function () { return num; },
    };
})();

他にもいろいろなやり方があります。文字列から名前空間を取得・生成する関数を作ってもいいかもしれません。私は個人的に、一番上が気に入っています。

Using

名前空間を定義するのはいいですが、これらの関数を使うためには名前空間の中まで入っていかなければいけません。

階層が多くなると、どうしても名前が長くなってしまいます。これでは、逆に使いづらいです。

var a = myApp.john.getRandomA(); // グローバルオブジェクト (window) は省略できる

こういう時のために、 C# などでは using などのステートメントで省略できるようになっています。

using System;
using System.IO;

namespace myApp
{
    public static class john
    {
        // Stream のフルネームは System.IO.Stream
        public Stream test()
        {
            return new TextStream();
        }
    }
}

JavaScript ではこうした概念は存在しないので、変数で代用します。

var tony = myApp.tony;
var random = myApp.john.getRandomA();

tony.push(1);
var x = random();

厳密には変数なので中身の入れ替えが自由自在なのですが、コーディング規約などでルールを徹底すれば、コードの保守性が上がるかもしれません。

最後に

素人が生意気にすみませんでしたJavaScript なんて書けないです奥が深すぎて抜けられません・・・。

本業で使用言語がだんだん C# から JavaScript に変化してきたんですが、なかなか難しく、手っ取り早くコードをきれいにしようと思って手を付けたのが名前空間パターンでした。

JavaScript で継承とかなにやらやろうと思ったらかなり多くのコードを書かなきゃいけなくて、素人が見ても なんじゃこりゃ・・・ なコードにしかならないなと思って、試行錯誤した結果です。許してください。

class 構文?そんな新しい子しりませんねぇ・・・ (遠い目)

yuzutan-hnk.hatenablog.com

現在 0000/00/00 00:00 を生きています。