【アスペクト指向JavaScriptフレームワーク】Cerny.jsの英語ドキュメント翻訳
Cerny.jsを翻訳した
Qiitaにあげたのだが、最近ガイドライン系で問題になっていて、消されてもおかしくない内容なのでこちらでもあげることにした
概要
導入
Cerny.jsは大規模な開発チームのJavaScriptプロジェクトの開発を容易にすることを目的としてJavaScriptライブラリ
以下の解決を提供する目標を達成しようとする
- メソッド呼び出しをインターセプションする
- 関数呼び出しの引数と戻り値の型チェック
- 依存関係の管理
- ロギング
- スキーマに対するオブジェクトの検証
- プログラミングの規約
- 辞書
- JSONをテキストやHTML形式で表示
- 簡単な設定
- 良いドキュメント
ライブラリーポリシー
Cerny.jsのプログラミングインターフェースは時間とともに一定に保たれている
すべてのpublic関数は文書化されており、文書化された関数のみがpublicであり、そのシグネチャとセマンティックスは維持されている
文書化されていない関数は変更の対象かもしれません
ドキュメントからインターフェースの概要を知ることができる
ドキュメントは、ライブラリのバージョンに固有である
以前にリリースしたドキュメントはアクセスしやすくなっている
もしwww機能を使用ないくなったら、ドキュメントで廃止予定とマークされる
ライセンス
Cerny.jsはBSDライセンスの下で配布される
著者について
Robert Cernyによって書かれている
- 質の高いJavaScriptに関する情報を提供してくれたDouglas Crockfordに感謝します。
- Cerny.jsの開発をしている間、Ant、JsUnit、Rhinoを使わせていただきました。
- Cerny.jsに規約プログラミングを組み込むことを提案してくれて、Norman Harebottle IIIありがとう
ダウンロード
Cerny.jsは3つの数字のバージョンでリリースされていて、x.y.zのように3つの区切られている
最初の数字のxはライブラリの大きな変更で増加し、yはAPIの拡張で増加し、バグ修正でzが増加する
リリースノートは今のところ配布されているもののみ利用できる
デモ
Cerny.jsライブラリのいくつかの機能の一連のデモで実際にやってみせていて、右のメニューからアクセスできる
すべてのデモはCerny.js 1.3.以下で動く
すべてのデモページで、デモとCerny.jsライブラリーの出力したログを受け取るコンソールを見ることができる
コンソールをクリックして、拡大することができる
もう一つのコンソールをクリックすると、縮んで余白のバーに戻る
ログ出力は、ライブラリに付属しているインターセプタのLogIndenterとTracerによって作成される
スキーマのデモ
このページは、Cerny.jsライブラリによって提供させるスキーマ検証のデモ
それは入力文書から構文解析されたオブジェクトを、スキーマガイドのFamily schemaに対して検証する
最初に表示された文書は有効である
Validateボタンをクリックして確認できる
検証レポートは空になるだろう、それはエラーメッセージはないということを含んでいる
入力されている文書をいじくりまわすことで、検証の効果を見ることができる
スキーマを勉強してFamilyの意味と、それにそむく方法を学ぶ
型チェックデモ
このページはCerny.jsの型チェック機能のデモ
署名はCERNY.signatureによって指定される
実行時の型チェックはインターセプトによって実行される
タイプエラーは赤色でコンソールに記録される
次のコードは、DateUtils.isPastDateという単純な日付の関数の一部を定義している
さらに下に、この関数呼び出しに関数を入力でき、タイプ違反の検出を観察できる
ドキュメント
これはCerny.jsライブラリのコア機能を提供するファイルである
コア機能とは、インターセプション、型チェック、ロギング、依存関係の宣言、構成と辞書など
Cerny.jsライブラリを使うためには、あなたのページにこのファイルを含める必要がある
この設定ファイル(cerny.conf.jsのコピー)を事前に含める必要がある
いくつかの使用協定
- もし現在の関数かメンバがスクリプトで上書きされた場合、メンバの新しい名前はアンダースコアに古い名前が続き構成されている。例えばinsertBeforeが_insertBefore
- もし単純な名前が不可能な場合、キーワード制限のため名前はアンダースコアで処理される。例えば_delete
- もし関数が任意の多くの引数を取る場合、これらのパラメータのドキュメントは「引数」と呼ばなけらればならない
- もし関数のドキュメントで、戻り値がしていされていない場合、関数はundefinedを返す
用途
なし
利用
すべて、ただしCERNY.console.*
機能
CloningException
複数例外のコンストラクタ
type(string): クローン化できない方
return(object): 例外
ContractViolation
契約違反のコンストラクタ
message(string): 自然言語による違反の性質
return(object): 違反
Dictionary
辞書は用語と定義のマッピング
定義は波括弧({})のついた用語である変数を含むことできる
単語を辞書で検索し、lookupを用いて定義を得ることができる
どんな文字列でもevaluateによって評価される
辞書は辞書のパスを示すを含む文字列の配列を保持することを含まれるプロパティを含むかもしれない
obj(object): 拡張されるobj
return(obj): obj
Logger
ロガーを作成する
いつもカテゴリーに、正確に一つのロガーが存在する
name(string): カテゴリーの名前
return(object): 名前によって識別されたカテゴリーのロガー
check
exprがtrueと評価されているかどうかを調べる
もしCERNY.ContractViolationがスローしない場合
事前条件、事後条件、不変条件でのみ使用される
expr(boolean): 評価する式
message(string): 契約違反に追加するメッセージ
clone
オブジェクトをクローンする
いくつかの制限があるケースのほとんどで動作する
- カスタムタイプは、クローンメソッドを提供する。このメソッドはパラメータなしで呼ばれ、結果が返される
- 循環参照は取扱できない: 無限再帰!
- 一部のクローンをサポートしている、もしオブジェクトツリーで何かが参照されていて、それがクローンできない場合は、コンソールにメッセージが表示され、参照プロパティはundefinedになる
subject(any): クローンする値
return(any): 値のクローン
dump
ロギングの目的で値をダンプする
変数の値を返す
変数の値の後にその型を中括弧で返す
変数が文字列の場合、値はシングルコードで囲まれる
value(any): ダンプする値
return(string): ログングに便利な文字列
empty
empty関数
evaluate
文字列を評価する
文字列の中のすべての変数を、辞書の定義によって置き換える
str(string): 評価する文字列
context(undefined, string): 評価が行われるコンテキスト、用語;再帰を避ける
return(string): 評価された文字列
getResource
リソースの取得
location(string): リソースの場所
return(string): リソースの内容
identity
引数を返す
arg(any): 引数
return(any): 引数を返す
intercept
インターセプションのためのオブジェクトの計記法
CERNY.Configuration.Interception.activeまたは渡されたインターセプタだけに依存して、同じ効果で複数回呼ぶことができる
この関数はIEのウィンドウオブジェクトでは機能しない
既存のオブジェクトメソッドをオーバーライドした場合
この関数でインターセプションが既にインストールされていて、この関数を再び使ってメソッドをインターセプトすると、古いメソッドがインストールされ、情報がログに記録される
これは残念ですが、インターセプションがいつも同じ効果を持つ必要がある
回避策:CERNY.methodを使用してメソッドを追加します(どちらにも最適ですが、オーバライドするときは絶対)
obj(object,function):インターセプトするメソッドのオブジェクト
arguments(undefined,string,RegExp,Array): 文字列かどの関数をインターセプトするかを指定する正規表現。もし指定されない場合、すべての関数がインターセプトされる。もしくはインターセプタの配列、存在しない場合はインターセプタが使用され、複数の配列が与えられている場合、最後のものが使用される
return(Array):インターセプトされた関数の名前の配列
isPresent
ランタイム中に式が存在するかどうかをチェックする
exp(string):チェックする式、文字列
return(boolean):文字列が何も参照しないか文字列がundefinedを参照している場合はfalse
joinFunctions
Join関数は1つの関数として機能する。新しい関数は最後の関数の戻り値を返す
arguments(function):結合する関数
return(function):新しい関数
load
スクリプトを読み込む
この関数はrequreで呼ばれる
さまざまな環境(ブラウザ、Rhino[RHI])で実装がことなる
デフォルトのランタイム環境はブラウザ
location(undefined,string):読み込むスクリプトの場所
loadData
いくつかのデータを読み込み、オブジェクト、配列、関数、プリミティブ値にすることができる
location(string):データの場所
return(any):評価されたデータ
lookup
辞書から用語を検索する
1.5より前:用語が見つからない場合、undefinedを返す
1.5で気をつける:用語が見つからない場合、エラーを投げる
term(文字列):検索する用語
return(undefined,string):辞書内の用語の値、または用語が不明の場合はundefined
method
関数をメソッドとしてオブジェクトにattachする
実際の関数の周りに自由にたくさんの関数を作成することができる
関心事の分離を目指している
インターセプターは最後の関数から始まる関数にラップされる
依存関係に関して配列を埋めるとき「自然順序」が可能になる
したがって、より基本的なインターセプタ(例えば LogIndenter)が最初にプッシュされる
obj(object):関数をattachするobj
name(string):関数がオブジェクトに知られている名前
func(function):attachする関数
interceptors(undefined,Array):使用するインターセプタ。デフォルトはCERNY.Configuration.Interception.active
namespace
CERNYに名前空間を作成する
この関数はYahoo! UI Library[YUI]からインスピレーションを受けている
name(string):作成する名前空間の名前
parentNameSpace(undefined,object):親の名前空間、デフォルトはCERNY
object
プロトタイプ継承[DCP]
このアルゴリズムはDouglas Crockfordによって開発された
obj(object):新しいオブジェクトのプロトタイプになるオブジェクト
return(object):新しいオブジェクト
post
関数呼び出しが成功したかどうか決めるために満たすべき条件を指定する
事後条件は、CERNY.checkへの一連の呼び出しである関数
thisは呼び出された関数と同じ意味を持つ
事前条件は次のパラメーターが渡される:
1. 戻り値
2. 呼び出されたオブジェクトの古いバージョン、その後
3. 呼び出しの引数
func(function):事後条件が指定されるための関数
post(function):事後条件
pre
funcへの呼び出しが成功するかどうかを判断するために満たす必要がある条件を指定する
これらの条件は事前条件と呼ばれる一つの関数で収集される
事前条件は、消費者への関数の意図を伝える機能の作者をサポートする
事前条件はCERNY.checkへの一連の呼び出しである関数である
事前条件は呼び出しの引数と呼ばれた関数と同じ意味を持つthisが渡される
func(function):事前条件が指定されるための関数
pre(function):事前条件
require
式の存在を確認する
式をカタログで検索して読み込む
カタログに宣言されている外部依存関係を解決する
script(string):式を必要とするスクリプト
arguments(undefine,string):さらに実行するために必要な式
return(Array):0以上のアイテムを持つ式が見つからない配列
signature
関数のシグネチャを指定する
指定される型はどれか1つ文字列("boolean", "string", "number", "object", "function", "undefined", "null", "any")か関数、実際の値はinstanceofでテストされる
将来、オブジェクトの型として指定することが可能で、プロトタイプチェーンはオブジェクトよって検査される
さらに未来、Cernyスキーマを使用することができる
func(function):シグネチャを指定する関数
returnType(string,function,Array):戻り値の型
arguments(undefined,string,function,Array):パラメータの型
Ant tasks
Cerny.jsはAntタスクのいくつかのスクリプト定義が付属する
それらのほとんどはWebアプリケーションの開発をサポートする
これらのタスクの一つを使うためには、それぞれのAntファイルをAntディレクトリに配置する必要がある
さらに、cerny.conf.jsを作り、Antが見つかる場所を教える必要がある
<project name="ourapp" default="build" basedir="."> ... <!-- Prepare using the Cerny.js ant tasks --> <property name="cerny.js.configuration" location="conf/ant/cerny.conf.js" /> <import file="vendor/tools/cerny.js/ant/merge.xml" /> <target name="build"> <!-- Use the merge-css task --> <merge-css src-file="css/style.css" dest-file="build/docroot/css/style.css" /> ... </target> ... </project>
前提条件
JavaScriptで使うにはAntをインストールし設定する必要がある
bsf.jarとjs.jarのクラスパスが必要
詳細についてはAnt Webページのライブラリ依存関係を読んで
Reference
jsdoc
JavaScriptライブラリのAPIドキュメントを含むJSONドキュメントを作成する
ドキュメントはスキーマに準拠している
ライブラリのドキュメントスタイルはCerny.jsで使われるものに準拠している必要がある
JSONドキュメントは人間が使うことを目的としてものではなく、あとで処理するための基礎となる
<jsdoc src-dir="js" dest-dir="build/docs" />
merge-catalog
1つの大きなカタログを作成するために、ソースファイル内のすべてのinclude文を処理する
これにより、開発時に複数のカタログを管理し、本番用のカタログを1つ作成することができる
<merge-catalog src-file="js/catalog.json" dest-file="build/docroot/js/catalog.json" />
merge-css
ソースファイルのすべての@import文を、インポートされたファイルの内容で置き換える
相対的URLを含むスタイルシートを置き換えて省略する
このタスクでは複数のCSSファイルを維持したまま、1つのファイルに展開することができ、HTTP要求の数が減る
<merge-css src-file="css/style.css" dest-file="build/docroot/css/style.css" />
merge-js
1つのページに必要なすべての機能を含むJavaScriptファイルを作成する
Cerny.jsによって提供された依存関係管理機能の使用方法に依存する
全てのrequireされたスクリプトをスクリプト内およびカタログ内で宣言された依存情報に基づいた正しい順番で連結する
<merge-js expression="OURAPP.pages.Search" dest-file="build/docroot/js/pages.Search.js" catalog-file="js/catalog.json" base-dir="."/>
Configuration
このドキュメントはCerny.jsライブラリの設定の導入を提供する
この設定は、ソースコードを変更なしにCerny.jsのいくつかのアスペクトの動作を簡単に適応させることができる
このように、例えばログレベルの調整、アクティブなインターセプタを指定、依存関係を管理ができることによって、私たちはHTMLページ内に手動でスクリプトをインポートすることを避けることができる
設定ファイルの作成
このライブラリーはデフォルトの設定ファイルが備え付けてあり、自分たちの設定をテンプレートとして使うことができる
それはcerny.conf.jsと呼ばれ、cerny.jsの同じディレクトリに存在する
このファイルをアプリケーションディレクトリにコピーし、cerny.jsスクリプトの前にページにincludeしよう
設定ファイルの前に、Cerny.jsライブラリからすべての出力を受け取るPopupWindowコンソールを準備する
<script type="text/javascript" src="vendor/cerny.js/console/console.js" ></script> <script type="text/javascript" src="vendor/cerny.js/console/PopupWindow.js" ></script> <script type="text/javascript" src="ourapp/cerny.conf.js" ></script> <script type="text/javascript" src="vendor/cerny.js/cerny.js" ></script>
設定ファイルの構造
この設定ファイルは3つのパーツで構成されている
CERNY.Configurationオブジェクト、CERNY.configure関数、CERNY.print関数を定義する
CERNY.Configuration = { Logger: { "indentStr": " ", "CERNY": "OFF", "CERNY.require": "FATAL", "CERNY.load": "ERROR", "NONE": "TRACE" }, // The catalog is used to resolve dependencies, which are stated in // a script by the means of CERNY.require. Catalog: { "cerny.js.path":"{configure-manually}", "CERNY.js.Array":"{cerny.js.path}/js/Array.js", "CERNY.js.Date":"{cerny.js.path}/js/Date.js", "CERNY.js.Number":"{cerny.js.path}/js/Number.js", "CERNY.js.String":"{cerny.js.path}/js/String.js", "CERNY.js.doc.Generator":"{cerny.js.path}/js/doc/Generator.js", "CERNY.js.doc.Schema":"{cerny.js.path}/js/doc/Schema.js", "CERNY.json.HtmlPrettyPrinter":"{cerny.js.path}/json/HtmlPrettyPrinter.js", "CERNY.json.Printer":"{cerny.js.path}/json/Printer.js", "CERNY.json.TextPrettyPrinter":"{cerny.js.path}/json/TextPrettyPrinter.js", "CERNY.schema":"{cerny.js.path}/schema/schema.js", "CERNY.text.DateFormat":"{cerny.js.path}/text/DateFormat.js", "CERNY.text.NumberFormat":"{cerny.js.path}/text/NumberFormat.js", "CERNY.util":"{cerny.js.path}/util/util.js", }, Interception: { active: [] } }; CERNY.configure = function() { var active = CERNY.Configuration.Interception.active; active.push(CERNY.Interceptors.LogIndenter); // active.push(CERNY.Interceptors.Profiler); active.push(CERNY.Interceptors.Tracer); // active.push(CERNY.Interceptors.TypeChecker); // active.push(CERNY.Interceptors.ContractChecker); }; // We are printing to the PopupWindow console CERNY.print = function(message) { // A generic console that works in all browsers CERNY.console.PopupWindow.print(message); // Firebug // console.log(message); // JsUnit with xbDebug // debug(message); // Rhino shell // print(message); };
依存管理
Cerny.jsはカタログを使って、定義スクリプトの場所にマップする
このカタログはcerny.conf.jsのCERNY.Configuration.Catalogに定義されている
Cerny.js 2以降、カタログを複数のファイルに分けることが可能
カタログファイルを使うためには、CERNY.COnfiguration.Catalogにincludeプロパティに含める必要がある
CERNY.Configuration = { ... Catalog: { "include": ["catalog.json"] } ... };
カタログは整形式JSONドキュメントでなけらばならない
YUIも利用するwebアプリケーションのカタログは次のようになる
{"include": ["{cerny.js.path}/catalog.json"], "cerny.js.path": "/ourapp/vendor/cerny.js/js", // Declaring dependencies of the YUI library "yui.path": "/ourapp/vendor/yui/build", "YAHOO": "{yui.path}/yahoo/yahoo.js", "YAHOO.util.Dom": "YAHOO,{yui.path}/dom/dom.js", "YAHOO.util.Event": "YAHOO,{yui.path}/event/event.js", "YAHOO.widget.Calendar": "YAHOO,YAHOO.util.Event,YAHOO.util.Dom,{yui.path}/calendar/calendar.js", ... }
依存関係宣言
上記のカタログでは、Cerny.jsを使ってサードパーティ製のライブラリーの依存関係を宣言することができる
独自のスクリプトでは、CERNY.require関数を呼び出すことを元にして依存関係を記述する
最初のパラメータは、現在のスクリプトで定義された必要な式の名前
その後、いくつかの必要な式は文字列として渡すことができる
CERNY.require("OURAPP.somePackage", // the requiring expression "CERNY.js.Array", "YAHOO.widget.Calendar"); CERNY.namespace("somePackage", OURAPP); // Code starts here
カタログ経由で依存関係を解決されない場合、例外が投げられ、詳細な情報はCERNY.requireカテゴリに記録される
これはカタログが正しく構成されていないことを示す
Cerny.js 1.3からCerny.loadの実装はXMLHttpRequestを使用してスクリプトを取得する
すべてのモダンなブラウザで動く
他の実行環境にも適用できる
CERNY.Configurationのリファレンス
Logger
カテゴリロガーのマッピングをログレベルまで保持する
特定のカテゴリーは一般のカテゴリーより優先される
CERNYがOFFに設定されている場合、TRACEを設定することによって、CERNY.schemaのための例外を作成することができる
そのエントリの順序は問わない
indentStr
ライブラリの使用者はロギングのインデントで使用される文字列を指定できる
この機能はLogIndenterインターセプタと連携して利用できるようになる
Configurationにオプションがない場合、2つのスペースが利用される
HTMLをロギングする時、identStrを" "に設定する必要がある
もう一つの方法としてCSSの空白を保持する機能を使用すること
ROOT
このカテゴリーはすべての他のカテゴリーのトップレベルとして使用される
configurationからのカテゴリの欠如は、OFFに設定すると解釈される
NONE
この特別なカテゴリーはメンバーロガーでロガーが保持していないオブジェクトにインターセプトがインストールされている場合に使われる
ログの出力が失われるのを避けるのと、インターセプトされたオブジェクトに重要なカテゴリーを持つロガーをインストールするには、このカテゴリーをTRACEにすることをおすすめする
Catalog
CERNY.require文が現在のJavaScriptコンテキストでまだ定義されていない式を参照する場合、カタログが参照される
カタログは欠落している式の定義を保持するファイルへの参照(URL)を提供するだろう
その後、このファイルを読み込もうとする試みはCERNY.loadによって行われる
このメカニズムにより、プログラマはページに必要なスクリプトを手動でインポートすることから解放される(依存関係管理を参照)
Interception
active
アクティブなインターセプタの配列を保持する
CERNY.configureは、configurationファイルを読むときにインターセプタが定義されておらず、参照できないため、関数CERNY.configureにのみ書き込むことができる
Contracts
バージョン1.4からCerny.jsはJavaScriptによる契約(design by contract)によるプログラミングをサポートする
この技法は作者と消費者の間の使用契約を明示することによってオブジェクトや関数の文書化を改善するのに役立つ
このガイドではCerny.jsの助けを使ってJavaScriptで契約プログラミングを始めることができる立場に立ってる
Preconditions
関数は、呼び出しが成功する状態を満たすかチェックする機能である事前条件を持つことが出来る
事前条件はCERNY.preを使って指定される
チェックには、CERNY.check関数を用いて実行する必要がある
チェックの最初の引数はTrueかFalseで評価される式でないといけない
二つ目の引数は自然言語での違反を述べる性質のメッセージ
このメッセージは人間のためのもの
式がfalseと評価する場合、チェックは失敗したと言いCERNY.ContractViolationは投げられる
実際の関数呼び出しの引数は事前条件の渡される
thisキーワードを使うことも可能
thisは関数が呼び出されるオブジェクトを参照する
次のコードは事前条件の仕様を説明している
例のために、二つの数字を割る関数を定義する
var NewMath = {}; (function() { // Shorten some names var check = CERNY.check; var pre = CERNY.pre; var method = CERNY.method; // The new division function divide(a,b) { return a / b; } // For contracts to be enforced, it is necessary // that a function is subject to interception. Therefore // CERNY.method must be used. method(NewMath, "divide", divide); // The precondition for a division pre(divide, function(a,b) { check(b !== 0, "b may not be 0"); }); })();
事後条件
事後条件はCERNY.postを使用して指定される
事後条件自体は、オブジェクトのクローン、戻り値、呼び出しの引数をこの順序で受け取る
クローンはメソッド呼び出しの前にオブジェクトの状態を保持し、通常oldと呼ばれる
(function() { // Defined a set with a remove method, which returns true on a // remove ... function remove(element) { ... } // The postcondition for a remove post(remove, function(old, result, element) { check(result === true && old.length === this.length + 1, "result may not be true when no element was removed"); check(result === false && old.length === this.length, "result may not be false, when an element was removed"); check(!this.contains(element), "element is still in the set"); }); })();
Invariants
オブジェクトには、そのオブジェクトに対してtrueでなければならない条件を指定するinvariantと呼ばれるメソッドがある
invariantは他のメソッド呼び出しの後に呼ばれ、オブジェクトの特定の状態を強調する
次のカレンダーイベントを導入し、終了日が開始日より前でないことを指定する例を見てみましょう
またこの例は、invariantの概念の説明に役立つだけで、他の面では多くの意味を持たないかもしれない
(function() { var check = CERNY.check; var method = CERNY.method; var signature = CERNY.signature; function Event(start, end) { this.start = start; this.end = end; } function setStart(start) { this.start = start; } signature(setStart, "undefined", Date); method(Event.prototype, "setStart", setStart); function setEnd(end) { this.end = end; } signature(setEnd, "undefined", Date); method(Event.prototype, "setEnd", setEnd); // This invariant guarantees a certain state in the object, // which then can be considered true. Event.prototype.invariant = function() { check(this.start.getTime() <= this.end.getTime(), "start must be before end"); } })();
Enforcing contracts
契約を強制するにはContractCheckerインターセプトをアクティブ化する必要がある
さらなる詳細については、インターセプションガイドを参照する
契約違反はFATALレベルで記録されるため、ROOTカテゴリーに応じて設定する
更にブラウザのコンソールに例外として表示される
契約チェックに参加する方法は、インターセプションの対象となる必要がある
したがって、CERNY.methodを使ってオブジェクトにアタッチする必要がある。または、CERNY.interceptをオブジェクトに対して呼び出す必要がある
契約は開発期間中のみ実施させるべき
正しく動く最終製品では、時間がかかり、タスクに貢献しないためContractCheckerを有効にするべきではない
一方開発レベルだけ必要なたくさんのチェックは省略されるため、契約プログラミングを使って開発された製品は高速に動く
Limitations
現在、invariantsをチェックする時に継承はサポートされていない
CERNY.cloneのアルゴリズムはとても素朴で改善が必要なため、古いもののサポートは弱い
これらはたやすくない
契約を削除する
生産的なソースコードから契約を削除するのは容易くない
Interception
このドキュメントは、Cerny.jsによって提供されているメソッド呼び出しをインターセプションするメカニズムの概要を与える
メソッド呼び出しインターセプションは、関数の実際の呼び出しの前と後に関数呼び出しを可能にする
このように、実行時間を簡単にプロファイルしたり、実際の引数や戻り値を記録したりすることができる
インラインログイングステートメントとは異なり、インターセプションはコードを邪魔することなく、オンデマンドでオンとオフを切り替えることができる
インターセプションは型チェックでさえ可能性を広げる
インターセプションの設定
アクティブなインターセプタのリストは配列のCERNY.Configuration.Interception.activeに保持される
この配列はcerny.conf.jsスクリプトのCERNY.configure関数を使ってこの配列を埋めなければならない
CERNY.configure = function() { var interceptors = CERNY.Configuration.Interception.active; interceptors.push(CERNY.Interceptors.LogIndenter); interceptors.push(CERNY.Interceptors.Tracer); // interceptors.push(CERNY.Interceptors.TypeChecker); // interceptors.push(CERNY.Interceptors.ContractChecker); interceptors.push(CERNY.Interceptors.Profiler); }
この場合、Cerny.jsに付属している3つのインターセプタがインターセプタ配列にプッシュされる
Cerny.jsに付属されているオブジェクトを使って、次の関数にラップされる
Cerny.jsによって提供されているオブジェクトに付属している次の関数にラップされる。これについてはすぐに学ぶ
より基本的なインターセプタ、例えばLogIndenterは最初にプッシュされ、あとのインターセプタはその恩恵を受けることができる
LogIndenterの場合、ログ出力のプロファイリング文とトレース文が正しいインデントの結果になる
既存のコードにインターセプションをインストール
例を挙げるなら 既存のJavaScriptコードがあり、パフォーマンスの問題が発生したとする
免疫を与えられた後、私たちプログラマーの一人は 認める・許す Snailオブジェクトの可能性があること
免疫を与えられた後、私たちプログラマーの一人は、問題を引き起こしているSnailオブジェクトの可能性があることを認める
これを知って、Snailが定義された後に次の行を追加し、SnailのログレベルをTRACEに上げる
CERNY.intercept(Snail);
ログ出力を見て、メソッドがSlowを呼び出す実際の時間を知ることができる
Wed Jan 17 2007 18:39:40 GMT+0100 (CET), TRACE: start | OURAPP.snail.move Wed Jan 17 2007 18:39:40 GMT+0100 (CET), TRACE: start | OURAPP.snail.slime Wed Jan 17 2007 18:39:40 GMT+0100 (CET), TRACE: stop: 600 ms | OURAPP.snail.slime Wed Jan 17 2007 18:39:40 GMT+0100 (CET), TRACE: stop: 800 ms | OURAPP.snail.move
Snailはmoveで800ミリ秒を費やし、そのうちslimeの呼び出しに600ミリ秒を費やしていると私たちは気づいた
今や私たちはアプリケーションの中ですべてSnailが動いているので、パフォーマンスの問題がどこから来るのか知っている
CERNY.interceptは、インターセプトする関数の名前を文字列または正規表現で指定するために使うオプション引数を取る
上記の例のように、オブジェクト以外のパラメータがない場合は、オブジェクト(hasownProperty)に直接アタッチされているすべてのメソッドがインターセプト用に用意されている
これはプロトタイプのメソッドを必要とせず、インターセプションを別々に準備する必要がある
これを行う方法の例と、省略可能な引数の使用方法を示している
CERNY.intercept(Snail.prototype, /^get.*/, /^set.*/, "giveBirth");
インターセプションのオブジェクトはメンバーロガーにロガーが格納されている場合、インターセプタはロガーにロギングする
オブジェクトにロガーがアタッチされていない場合、ロガーはカテゴリーNONEになる
そういうわけで、NONEカテゴリは設定ファイルで常にTRACEに設定され、ログステートメントが失われないようにすることを推奨する
カスタマイズされたインターセプタの作成
インターセプタはbeforeメソッドとafterメソッドを持つオブジェクトである
Cerny.jsに付属するProfilerを見ていきましょう
CERNY.Interceptors.Profiler = { before: function(call) { call.logger.trace("start"); call.start = new Date(); }, after: function(call) { call.logger.trace("stop: " + (new Date().getTime() - call.start.getTime()) + " ms"); } }
インターセプタの両方のメソッドは、関数呼び出し特有のオブジェクトが渡されるパラメータ呼び出しがある
実際の関数呼び出しで情報転送するために使用できる
上記の例では、現在時刻がcallオブジェクトのstartプロパティに格納されていることがわかる
更に、callオブジェクトには、使用可能なloggerが用意されている
ロギング
このドキュメントはCerny.jsに付属しているロギング機能を使用可能にする
ロガーを取得して、cerny.conf.js設定ファイルでログレベルを調整する方法を示し、私たちが興味があるメッセージのみを見ることができる
ロガー取得
ログステートメントを出力するため、ロガーオブジェクトを取得する必要がある
私たちのコードに次の行を含むことで、達成することができる
OURAPP.doSomething = function() { var logger = CERNY.Logger("OURAPP.doSomething"); // do something ... logger.debug("myvar: " + CERNY.dump(myvar")); ... }
設定
CERNY.loggerは文字列でロガーのカテゴリと呼ばれる1つのパラメータを取る
そのカテゴリはドットで区切られた階層のパスを示す名前のリスト
始まりに近い名前ほど、階層の中で高くなる
設定で高いレベルのロガーを無効にできるが、特定のロガーの出力はまだ取得できる
ロガー設定の方法の例はこのようになる
CERNY.Configuration = { Logger: { "OURAPP": "OFF", "OURAPP.doSomething": "DEBUG" }, ... }
ログレベル
ログレベルはLog4jやその派生物から慣れ親しんでいる標準レベル
それらはOFF, FATAL, ERROR, WARN, INFO, DEBUG, TRACEである
ロガーはこれらのレベルごとに小文字の対応する機能を持っている
これらのレベルの目的は、プロジェクトポリシーを対象とする
生産レベルと開発レベルの一般的な区別は共通している
その境界は通常、厳密には定義されていない
いくつかのチームではINFOが最初の開発レベルであり、ほかのチームはDEBUG
Schema
このドキュメントはCerny.jsライブラリのスキーマ概念の紹介に役立つ
CernyスキーマはJavaScriptオブジェクトを検証することができる
オブジェクトが検証に合格した場合、特定のステートメントがオブジェクトに当てはまることを確かめることができる
スキーマを使用すると、オブジェクトに関する特定の事実が与えられ、チェックする必要がある、よって簡単なコードを書くことができる
これにより、読みやすく、保守性が良いJavaScriptコードになる
一方、スキーマを使わない場合、現在取り組んでいるデータ構造についての前提データに散在しており、同じ前提データが複数回チェックされる可能性がある
簡単なスキーマ
CernyスキーマはJavaScriptオブジェクト自体である
簡単なスキーマの定義のために、いくつかの前提条件は必要ない
Firefox = { vendor: "Firefox" };
このコードはFirefoxスキーマを定義する
オブジェクトがそのスキーマに準拠するためには、メンバにvendorが必要であり、そのメンバの値はFirefoxでなければならない
言い換えれば、firefoxスキーマはvendorという名前の制約で束縛されていて、値はリテラル文字列Firefoxである
検証の実行
バリデーションの実行するために、自分たちのページにCERNY.schemaを読み込む
このスクリプトはCERNY.schema.validateとCERNY.schema.isValid関数を提供する
Firefoxスキーマに対して現在のブラウザの検証のためのコードは次のとおりである
var report = CERNY.schema.validate(window.navigator, Firefox); var isFirefox = CERNY.schema.isValid(report);
最初の行はFirefoxスキーマに対してwindow.navigatorオブジェクトの実際の検証を実行する
検証の結果は、report変数に格納される
二行目は結果を検証し、バリデーションエラーが起こらなかった場合trueを返し、他の場合falseを返す
この検証の結果をisFirefoxに格納する
いくつかの制約を使ったスキーマ
Firefoxスキーマは一つの制約で構成されている
複数の制約を使ったスキーマを見ていきましょう
次のコードはPersonと呼ばれるスキーマを定義する
Person = { firstName: nonEmptyString, middleName: optional(nonEmptyString), lastName: nonEmptyString, dateOfBirth: optional(pastIsoDate) };
Personスキーマは複数の制約を持つだけではなく、Firefoxスキーマからわかるように、リテラル文字列ではなく関数であるという制約も制約もされている
firstName制約の場合、その関数はnonEmptyStringと呼ばれる
オブジェクトの検証には、firstNameメンバはtrueか適切なエラーメッセージが返すnonEmptyStringにパラメータとして渡される
動いているPersonスキーマが見れる、次のコードを見ていきましょう
var csp = { firstName: "Charles", middleName: "Sanders", lastName: "Peirce", dateOfBirth: "1839-09-10", }; var report = CERNY.schema.validate(csp, Person);
nonEmptyString関数の他に、dateOfBirthの可能な値で過去にあるyyyy-mm-ddの形式の日付に制限するpastIsoDateと呼ばれる関数がある
JavaScriptコードで表すと
pastIsoDate = function(value) { var dateValue = Date._parse(value, CERNY.text.DateFormat.ISO); if (dateValue && dateValue.getTime() < new Date().getTime()) { return true; } return CERNY.schema.printValue(value) + " must be an ISO date string (yyyy-mm-dd) for a past date."; }
このコードはCerny.jsライブラリによって提供されているいくつかの関数を必要とするが、これらの依存関係の宣言は明快な指示のために省略される
関数の名前は中間のプログラマに彼らが何をするかヒントを与える
pastIsoDate関数は、csp.dateOfBirth("1839-09-10")の値の検証をしている間、束縛されているvalueという一つのパラメータを取る
valueが文字列で正しいフォーマットの過去の日付を参照する場合、trueを返し、他の場合はエラーメッセージを返す
上記の例では、pastIsoDateはtrueを返す
失敗の場合エラーメッセージを返すことは、理解しやすいレポートを生成することに役立つ
もう一つのオプションは、一般的なエラーメッセージを生成するfalseを返す
プログラマは関数のドキュメントやソースコードを調べて何が悪いのかを理解を得る力になる
検証レポート
ここまで、すべてのサンプルオブジェクトはスキーマの確認
次のオブジェクトを見てみましょう
var notAPerson = { firstName: "", middleName: "Sanders", lastName: "Peirce", dateOfBirth: "1839-9-10" }; var report = CERNY.schema.validate(notAPerson, Person);
notAPersonオブジェクトはPersonスキーマの制約をすべて満たさない
firstNameが空であるのは失敗で、dateOfBirthの値はISO日付表記法の文字列ではない
validate呼び出しによって返されたエラーレポートは次のようになる
{ firstName: "'' (string) must be a non empty string.", dateOfBirth: "'1839-9-10' (string) must be an ISO date string (yyyy-mm-dd) for a past date." }
私たちが簡単に見れることができるように、エラーメッセージを解釈し、それに従って行動する人間が必要
しかし、これはできるだけ簡単にしようとする
検証レポートはスキーマと同じ構造に従っている
制約の定義
Personスキーマを見直した場合、middleName制約はオプションだと気づくだろう
オプション関数はCerny.jsライブラリのschema.jsスクリプトに含まれている
それは、スキーマの操作を用意にするサポート機能である
現在のバージョンに既に含まれているいくつかのサポート関数があり、フィードバックが利用可能になった将来はさらに多くが追加される
複合スキーマ
上記の通り、制約はスキーマとして定義することもできる
どのように動くか何の意味があるのか、次の例を見ていく
arrayOf = CERNY.schema.arrayOf; Family = { mother: Person, father: Person, children: arrayOf(Person, 1) };
Familyスキーマはmother、father、childrenという名前の3つの制約から構成されていて、最初の2つはPersonと定義され、後者は少なるとも1つの要素を持つPerson型のアイテムの配列である
次のファミリーはFamilyスキーマに従う
familyOfCsp = { mother: { firstName: "Sarah", middleName: "Hunt", lastName: "Mills" }, father: { firstName: "Benjamin", lastName: "Peirce" }, children: [csp] }
スキーマでのthisの使い方
オブジェクト内でthisキーワードを使うと、オブジェクトの複数のメンバを参照する制約を公式化できる
終了日が開始日の後であるかどうかをチェックする次のEventスキーマについて考えよう
Event = { startDate: isoDate, endDate: function (value) { var endDate = Date._parse(value, CERNY.text.DateFormat.ISO); if (endDate === null) { return "Must be a string of format (YYYY-MM-DD)"; } var startDate = Date._parse(this.startDate, CERNY.text.DateFormat.ISO); // No need to check start for not null because of startDate // constraint if (endDate.before(startDate)) { return "Must be after start date"; } return true; } }
lifeOfCsp = { startDate: "1839-09-10", endDate: "1914-04-19" }
オブジェクトの境界を超える
最後のセクションでは、似たオブジェクトの他のメンバを参照する検証ステートメントをどのように学んだ
これは、兄弟のメンバを参照するステートメントでもうまくいく
人の生年月日が母親の前でないことを保証する次のスキーマを見てください
Child = { firstName: nonEmptyString, lastName: nonEmptyString, dateOfBirth: function(value) { // Date check of value omitted ... var dobMother = Date._parse(this.mother.dateOfBirth, CERNY.text.DateFormat.ISO); if (dob.before(dobMother)) { return "Must be after mother's date of birth"; } }, mother: Person }
オブジェクトツリーの違うブランチに存在するオブジェクトにあるメンバを参照したい場合、どのように進めばいい?
この目的のためにvalidate関数は、_parentと呼ばれる一時的なメンバを使って渡されたオブジェクトを補強する
thisプロパティの助けを借りて、オブジェクトツリーの任意の場所にアクセスすることが可能
familyの例をまた見てみましょう
前の例とは異なり、Personスキーマはmotherメンバを持たない
しかし家族の中では、子供は母親の前には生まれないというルールにPersonはまだ従うべき
_parentプロパティはこの目的に役立つ
Child = { // ... just like Person dateOfBirth: function(value) { // Date check of value omitted ... var dobMother = Date._parse(this._parent.mother.dateOfBirth, CERNY.text.DateFormat.ISO); // Compare it and print meaningful error message like in the // previous example } };
概要
JavaScriptのフレキシブルで動的性質により、コードとの相互作用が可能なデータ構造をどのように描いているかを簡潔に記述することができる
これらの記述は、Cerny.jsライブラリのCERNY.schemaパッケージを使って検証された
このスクリプトは、オブジェクトが私たちの記述に従っているかどうか、人間が読めるレポートを作成する、短くて簡単な検証関数を提供する
制約は名前と値で構成されている
値はリテラル、正規表現、関数、スキーマで出来ている
検証結果はJavaScriptオブジェクトであり、オブジェクトがスキーマに準拠している場合は空である
他の場合、違反していない制約と同じ位置にエラーメッセージが見つかるはず
スキーマパッケージをさらに改善するために、コードの重複を避けるため、スキーマを拡張と連鎖制約関数の2つの機能を追加する必要がある
型チェック
バージョン1.2からのCerny.jsは関数のシグネチャを指定するメカニズムを提供する
これにより、シグネチャがドキュメント化されることによってコードの品質が改善される
さらに、実行中に呼ばれた関数のシグネチャはインターセプションを経由してチェックされている可能性が広がる
このドキュメントは使い方の特性を示している
関数のシグネチャの指定
関数のシグネチャを指定する時、CERNY.signature関数を呼び出す必要がある
最初に引数は指定されたシグネチャの関数でなければならない
2つ目の引数は戻り値の型
3つ目から始まる引数は与えられたパラメータの型
次のサンプルを見てみましょう
function parseIsoDate(isoDateString) { // Do what's necessary to parse the date out of isoDateString return parsedDate; } CERNY.signature(parseIsoDate, Date, "string");
このサンプルは、String型の一つのパラメータを取り、Dateを返すparseisoDate関数を定義する
サンプルの目的には何も貢献しないから関数の実装は省く
パラメータの型、または戻り値は関数か文字列のどちらかである
関数の場合、値は型に対してinstanceofを使ってテストされる
型が文字列で指定されている場合、意味のある値のリストは:boolean, function, number, object, string, undefined, null, any
最後の値anyは、型チェックが行われないことを意味し、任意の値が許可される
物をより柔軟にするために、型の組み合わせは配列に入れて、複合系として使用することができる
次のサンプルの場合、検索関数はプリミティブ文字列かRegExp型のオブジェクトをパラメータとして受け取り、配列を返す
CERNY.signature(someSearch, Array, ["string", RegExp]);
関数がnullを返す可能性がある場合、シグネチャで明示的に宣言する必要がある
parseIsoDateの作者が例外を投げる代わりにnullを返すことを選ぶ場合(文字列をDate型にパースできない場合)、このようにシグネチャを宣言する必要がある
CERNY.signature(parseIsoDate, ["null", Date], "string");
関数呼び出しがシグネチャで宣言されたパラメータより多くの引数をもっていた場合、シグネチャを超えたすべての引数は最後のパラメータの型に対してチェックされる
型チェックの有効化
型チェックはインターセプションメカニズムに基づいている
型チェックを有効にするのは、Cerny.jsライブラリで設定する
特に、有効なインターセプタの配列をTypeCheckerインターセプタにpushしなければならない
ここに、cerny.conf.jsの断片がある
CERNY.configure = function() { var active = CERNY.Configuration.Interception.active; active.push(CERNY.Interceptors.LogIndenter); active.push(CERNY.Interceptors.TypeChecker); };
メソッドをインターセプト対象にするには、CERNY.method関数を使用してメソッドをオブジェクトにアタッチするか、すべてのメソッドが割り当てられた後にオブジェクトのCERNY.interceptを呼び出す必要がある
それだけで、型チェックが実行される
型エラーはFATALレベルのロガーに報告される
これらのメッセージを表示するには、それに応じてロギングを設定する必要がある
TypeCheckerが有効化されると、ログレベルが設定され、メソッドがインターセプトの対象になり、呼び出し時に関数のシグネチャはチェックされ、型エラーが報告される
次に出力例を示す
1173824346796, FATAL: arg 0: Type error: Tue Mar 13 2007 23:19:06 GMT+0100 (object) should be of type string | NONE
型チェックを使う時
型チェックは大きなチームのJavaScriptプロジェクトの開発を簡単にするメカニズムである
それは、作者と関数の利用者の間のコミュニケーションを改善する
シグネチャの助けを借りて、利用者は関数の使い方の追加情報を受け取る
型チェックの導入は関数を使用して作成されたエラーについて早期に利用者に知らせるだろう
型チェックは開発中にいつも有効にして、チームメンバーはそのメリットを得る
生産中はそれを無効にすることができる