コンパイラに優しいコードガイドライン

Sencha Cmd内にある主要コンポーネントの1つが、コンパイラです。このガイドでは、コンパイラを介さずにほとんどのコードを取得し、今後のフレームワーク認識の最適化の準備をするコードの書き方を説明します。

前提条件

先に進む前に以下のガイドをご覧になることをお勧めします。

コンパイラではないもの

Sencha Cmdコンパイラは、以下のようなツールの代わりには**なりません** :

これらのツールは、JavaScriptの開発者が持つさまざまな問題を解決し、JavaScriptの世界では好評を得ていますが、クラス宣言用のExt.defineなどの機能を備えたSenchaフレームワークを理解しません。

フレームワークの認識

Sencha Cmdコンパイラの役割は、フレームワーク認識の最適化と診断を行うことです。コードがSencha Cmdコンパイラを通じて渡されると、より一般的なツールで使う準備ができます。

こうした種類の最適化は、ブラウザ、とりわけレガシーブラウザにおけるJava Script コードの「取り込み」時間に著しい改善が見られます。

ただし、これらの利点を持つコンパイラでは、コンパイラが「理解」できるがゆえに最適化できるというコーディング規約を調べることが重要です。このガイドで説明されている規約に従って、Sencha Cmdから現在および将来的に、ほとんどのコードを取得できるように、ご使用のコードがどの位置にあるのかを確認してください。

コードの構成

動的ローダーおよび以前のJSBuilderは、クラスがどのように構成されているかについて一定の想定を常に保持していますが、それらは、これらのガイドラインを順守しない場合でも、それほど重大な影響は受けません。これらのガイドラインは、Javaと非常によく似ています。

要約すると、これらのガイドラインは、以下のようになります:

  • 各JavaScriptソースファイルは、グローバルスコープで、1つのExt.define文を含む必要があります。
  • ソースファイル名は、Ext.define("MyApp.foo.bar.Thing", ...を含むソースファイル名を“Thing.js”にするなど、定義されたタイプの名前の最後のセグメントに一致します。
  • すべてのソースファイルは、定義済みタイプの名前空間に基づいたフォルダ構造に格納されます。たとえば、Ext.define("MyApp.foo.bar.Thing", ...の場合、ソースファイルのパスは、“/foo/bar”で終わります。

内部的には、ソースファイルとクラスファイルは、コンパイラには、基本的に同義に見えています。必要がなければ、クラスを削除するためにファイルを分割する必要はありません。完全なファイルだけが選択され、出力に含まれます。つまり、ソースファイルにあるどのクラスが必要になるにしても、ファイル中にあるクラスは、すべて出力に含まれることになります。

コンパイラにクラスレベルのコードを選択する自由を与えるには、各ファイル内に入れるクラスを1つだけにすることが不可欠です。

クラスの宣言

Sencha クラスシステムには、より高次レベルであるオブジェクト指向のプログラミングを可能にする、Ext.define 関数があります。コンパイラは、Ext.defineが、本当に「宣言的な」プログラミング形式であると見て、それに従って、「クラス宣言」を処理します。

Ext.defineが宣言として理解されることが明らかである場合、クラス本体の内容は、コード内で動的に構築することはできません。この慣習はまれですが、JavaScriptでは有効です。ただし、以下のコード形式から分かるとおり、これは、パースするコードを理解するためのコンパイラの機能とは正反対なものです。動的なクラス宣言は、コンパイラの他の機能により、処理をよりうまく行うために、頻繁に使用されます。これらの機能の詳細に関しては、Senchaコンパイラリファレンスを参照してください。

コンパイラは、以下のような、この宣言部分の言語の「キーワード」を理解します。

  • requires
  • uses
  • extend
  • mixins
  • statics
  • alias
  • singleton
  • override
  • alternateClassName
  • xtype

コンパイラがクラス宣言を認識するには、宣言は、以下の形式の1つに従う必要があります。

標準形式

ほとんどのクラスでは、以下のようなシンプルな宣言を使用します:

Ext.define('Foo.bar.Thing', {
    // keywords go here ... such as:

    extend: '...',

    // ...
});

2つめの引数はクラス本体で、クラス「宣言」としてコンパイラは処理します。

注意:_全ての形式において、グローバルスコープでExt.define を呼び出します。_

ラップ関数の形式

ある使用ケースでは、クラスメソッド用のクロージャスコープを作成するために、関数内にクラス宣言を包む場合があります。さまざまな形式すべてにおいて、オブジェクト文字列としてクラス本体を返すreturn文で終わる関数がコンパイラには不可欠です。その他の技術は、コンパイラには認識されません。

関数の形式

以下で説明するこの技術の古い形式を合理化するために、Ext.defineは、関数を2番目の引数として考えた場合、クラス本体を生成するための関数を呼び出す必要がある、と理解します。また、それは、クロージャスコープを介した静的メンバへのアクセスを促す単一の引数として、クラスへ参照を渡します。フレームワークへは、これは、内部的に、クロージャスコープに対する最も一般的な理由でした。

Ext.define('Foo.bar.Thing', function (Thing) {

    return {
        // keywords go here ... such as:

        extend: '...',

        // ...
    };
});

注意:この形式は、Ext JS 4.1.2 以降および Sencha Touch 2.1 以降のみでサポートされています。

呼び出される関数の形式

過去のリリースでは、「関数の形式」は、サポートされていませんでした。そのため、関数は、ただ即時に起動されるだけでした。

Ext.define('Foo.bar.Thing', function () {

    return {
        // keywords go here ... such as:

        extend: '...',

        // ...
    };
}());

括弧付きで呼び出す関数の形式

この形式および次の形式は、SHint (またはJSLint)のようなツールの要件を満たすためによく使われます。

Ext.define('Foo.bar.Thing', (function () {

    return {
        // keywords go here ... such as:

        extend: '...',

        // ...
    };
})());

括弧つきで呼び出される関数の形式

JSHint/JSLintを使えるようにするために即時に呼び出される「関数の形式」に関する別のバリエーション

Ext.define('Foo.bar.Thing', (function () {

    return {
        // keywords go here ... such as:

        extend: '...',

        // ...
    };
}()));

キーワード

多くの形式内にあるクラス宣言は、究極的に「キーワード」を含みます。各キーワードには、独自のセマンティックがありますが、共通する「形」を持つものが多数あります。

文字列を使用したキーワード

extendおよびoverrideキーワードは、文字列リテラルを受け取るのみです。

また、これらのキーワードは、どんな宣言にも使用できるという点でのみ、相互に排他的です。

文字列または文字列[]を使用したキーワード

以下のキーワードは、すべて同じ形式で使用します:

  • requires
  • uses
  • alias
  • alternateClassName
  • xtype

以下は、これらのキーワードをサポートしている形式です。

文字列のみ:

requires: 'Foo.thing.Bar',
//...

文字列の配列:

requires: [ 'Foo.thing.Bar', 'Foo.other.Thing' ],
//...

mixinsの形式

オブジェクトリテラルを使用する場合に、mixinに与えた名前は、引用符を付ける場合と付けない場合があります:

mixins: {
    name: 'Foo.bar.Mixin',
    'other': 'Foo.other.Mixin'
},
//...

Mixinsを、文字列[]として指定することもできます:

mixins: [
    'Foo.bar.Mixin',
    'Foo.other.Mixin'
],
//...

このアプローチは mixin クラスの mixinId に依存しますが、受信側のクラスは mixin の順序をコントロールできます。mixin に重複するメソッドやプロパティがあり、受信側のクラスが重複するプロパティまたはメソッドを提供する mixin をコントロールする場合に重要です。

staticsキーワード

キーワードは、各インスタンスとは対照的に、クラス上にプロパティまたはメソッドを配置します。これは、オブジェクトリテラルでなければなりません。

statics: {
    // members go here
},
// ...

singletonキーワード

このキーワードは、昔からbooleanの“true”値と共にのみ使用されてきました。

singleton: true,

次の (冗長な) 使用方法もサポートされています:

singleton: false,

オーバーライド

Ext JS 4.1.0およびSencha Touch 2.0では、Ext.defineに、オーバーライドを管理する機能があります。オーバーライドは、昔からバグまわりで機能したり、拡張機能を追加したりするコードにパッチをあてるために使用されてきました。この使用方法は、Ext.overrideメソッドの実行に必要とされるタイミングのために、動的ローダーの導入もあって、複雑になっていました。また、多くのオーバーライドを持つ大型アプリケーションでは、コードベース内のオーバーライドのすべてがすべてのページやビルドで必要になるとは限りませんでした (たとえば、対象のクラスが必要でない場合など)。

一度、クラスシステムとローダーがオーバーライドを理解すると、このすべてが変更されました。この傾向は、Sencha Cmdでも続いています。コンパイラは、オーバーライドを理解すると、それらの依存関係が影響して、ロードシーケンスが発行されます。

将来的には、コンパイラは、オーバーライドで置換されるメソッドのデッドコードの削除に、より積極的になるでしょう。以下の説明にあるように、管理されたオーバーライドの使用が、Sencha Cmdで利用できるようになると、このコードの最適化が可能となります。

標準的なオーバーライドの形式

以下は、オーバーライドの標準形です。名前空間の選択は、任意ですが、以下の事項に注意してください。

Ext.define('MyApp.patches.grid.Panel', {
    override: 'Ext.grid.Panel',

    ...
});

ユースケース オーバーライドを管理するExt.defineを管理する機能を使うと、新しいイディオムが開き、それらを積極的に利用できます。たとえば、Sencha Architectやフレームワーク内部のコードジェネレータでは、Ext.Elementがより管理可能で凝縮したピースになるように、大きなクラスから分割されます。

パッチとしてのオーバーライド

パッチとしてのオーバーライドは、履歴として使用される場合があり、そのため、それは今では最も一般的な慣習になっています。

警告:コードにパッチをあてるときには、慎重にしてください。オーバーライドそのものの使用はサポートされていますが、フレームワークメソッドをオーバーライドする最終結果は、サポートされていません。すべてのオーバーライドは、新しいフレームワーク版にアップグレードされるたびに慎重に見直しを行う必要があります。

つまり、フレームワークメソッドをオーバーライドする必要がある場合もあります。これが、バグをフィックスするのに最も一般的な事例です。この事例では、標準のオーバーライド形式が、理想的です。実際に、Sencha Supportは、この形式のパッチをお客様にたびたび支給します。ただし、支給されたら、パッチが不要になった場合の管理や削除は、以前述べたレビュープロセスにとっては重要です。

推奨される命名方法:
  • 対象の最上位レベルの名前空間に関する名前空間内のパッチの整理たとえば、“MyApp.patches”は、“Ext”名前空間を対象にしています。おそらく別のレベルまたは名前空間に関わるサードパーティのコードは、その最上位レベルの名前空間に対応するように選ぶ必要があります。そこから、一致する名前や下位の名前空間を使ったオーバーライドに名前を付けます。これまでの例:

    Ext -> MyApp.patches).grid.Panel

一部のクラスとしてのオーバーライド

(Sencha Architectとして) コード生成の処理をする場合、2つのパートから成るクラスが一般的です:1つは、コンピュータで生成されるもので、もう1つは、人間が編集するものです。言語によっては、「一部のクラス」または2つの部分から成るクラスという概念を正式にサポートしています。

オーバーライドを使用すると、これをすっきりと管理できます:

./foo/bar/Thing.js の場合:

Ext.define('Foo.bar.Thing', {
    // NOTE: This class is generated - DO NOT EDIT...

    requires: [
        'Foo.bar.custom.Thing'
    ],

    method: function () {
        // some generated method
    },

    ...
});

./foo/bar/custom/Thing.js の場合:

Ext.define('Foo.bar.custom.Thing', {
    override: 'Foo.bar.Thing',

    method: function () {
        this.callParent(); // calls generated method
        ...
    },

    ...
});

推奨される命名方法:

  • 名前空間によって、生成されたコードと手作業で編集されたコードの構成の比較
  • 名前空間によらない場合、接尾辞が Foo.bar.ThingOverride または Foo.bar.ThingGenerated などクラスの一部がリストと照合される場合の共通の基本名にします。

アスペクトとしてのオーバーライド

オブジェクト指向の設計でのベースクラスの共通の問題は、「肥大したベースクラス」です。これは、ある動作が、すべてのクラスを通じて適用されることが原因で発生します。ただし、これらの動作 (または機能) が必要になると、動作はがある肥大したベースクラスの一部として実行される場合に、すぐには削除できません。

オーバーライドを使用すると、こうした機能は、自身の階層に集められ、必要なときに、 requiresを使用して、これらの機能を選択できます。

./foo/feature/Component.js の場合:

Ext.define('Foo.feature.Component', {
    override: 'Ext.Component',

    ...
});

./foo/feature/grid/Panel.js の場合:

Ext.define('Foo.feature.grid.Panel', {
    override: 'Ext.grid.Panel',

    requires: [
        'Foo.feature.Component' // since overrides do not "extend" each other
    ],

    ...
});

この機能は、必要とされたために、現在では使用できます:

...
requires: [
    'Foo.feature.grid.Panel'
]

または、適切な「bootstrap」ファイル ( Workspaces in Sencha Cmd 参照)で使用します。

...
requires: [
    'Foo.feature.*'
]

推奨される命名方法:

  • 名前空間によって、生成されたコードと手作業で編集されたコードの構成の比較これにより、すべての機能でワイルドカードが使用できます。

オーバーライド内でのrequiresusesの使用

これらのキーワードは、オーバーライド内でサポートされています。requiresの使用により、コンパイラのオーバーライドのコードを再整列させる機能が制限されます。

callParentおよびcallSuperの使用

これら新規の事例のすべてをサポートするために、Ext JS 4.0 and Sencha Touch 2.0内の callParentは、「次のメソッドを呼び出す」機能へと拡張されました。「次のメソッド」とは、オーバーライドされたメソッド、または継承されたメソッドかもしれません。次のメソッドがある限り、callParentは、そのメソッドを呼び出します。

これを表示するもう1つの方法は、callParentに、Ext.defineの形式とすべて同じように動作させて、それらをクラスやオーバーライドにすることです。

領域によっては、これが役に立ちますが、(パッチまたはバグフィックスとしての) オリジナルメソッドをバイパスすることが困難になります。Ext JS 4.1以降およびSencha Touch 2.1以降では、オーバーライドされたメソッドをバイパスできるcallSuperという名前のメソッドがあります。

今後のリリースでは、コンパイラは、オーバーライドされたメソッドのデッドコードを削除するためのセマンティックの差異を利用することにしています。

オーバーライドの互換性

バージョン4.2.2で起動すると、オーバーライドはフレームワークバージョンや他のパッケージのバージョンをベースにしたcompatibilityを宣言できます。これは、ターゲットのクラスバージョンと互換性のない場合において、無視しても安全なパッチを選択的に適用する場合に便利です。

一番シンプルな使い道は、フレームワークバージョンの適合性テストです。

Ext.define('App.overrides.grid.Panel', {
    override: 'Ext.grid.Panel',

    compatibility: '4.2.2', // only if framework version is 4.2.2

    //...
});

配列はORとして扱われるため、スペックがマッチした場合、そのオーバーライドは互換性を持ちます。

Ext.define('App.overrides.some.Thing', {
    override: 'Foo.some.Thing',

    compatibility: [
        '4.2.2',
        '[email protected]'
    ],

    //...
});

すべての仕様がマッチするよう要求すると、オブジェクトが与えられます。

Ext.define('App.overrides.some.Thing', {
    override: 'Foo.some.Thing',

    compatibility: {
        and: [
            '4.2.2',
            '[email protected]'
        ]
    },

    //...
});

Because the object form is just a recursive check, these can be nested:

Ext.define('App.overrides.some.Thing', {
    override: 'Foo.some.Thing',

    compatibility: {
        and: [
            '4.2.2',  // exactly version 4.2.2 of the framework *AND*
            {
                // either (or both) of these package specs:
                or: [
                    '[email protected]',
                    '[email protected]+'
                ]
            }
        ]
    },

    //...
});

バージョン構文の詳細については、Ext.VersioncheckVersion メソッドを参照してください。

結論

Sencha Cmdが進化を続けているため、これらのガイドラインからの逸脱を指摘する助けとなる新しい診断メッセージの導入を今後も継続していきます。

ガイドラインをどこから読み始めるのが適切か、については、この情報から、まずはご自身の内部コードスタイルガイドラインおよび慣習について役に立つ方法を調べてみることから始めるのが適当でしょう。

次のステップ

Last updated