Home > Latest topics

Latest topics > いざ要素名や属性名などを変えたくなった時にソースの中をあちこち探し回ったり巧みな正規表現を考えるために頭をひねったりしなくても済むようにするテク

宣伝1。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能! シス管系女子って何!? - 「シス管系女子」特設サイト

宣伝2。Firefox Hacks Rebooted発売中。本書の1/3を使って、再起動不要なアドオンの作り方のテクニックや非同期処理の効率のいい書き方などを解説しています。既刊のFirefox 3 Hacks拡張機能開発チュートリアルと併せてどうぞ。

Firefox Hacks Rebooted ―Mozillaテクノロジ徹底活用テクニック
浅井 智也 池田 譲治 小山田 昌史 五味渕 大賀 下田 洋志 寺田 真 松澤 太郎
オライリージャパン

いざ要素名や属性名などを変えたくなった時にソースの中をあちこち探し回ったり巧みな正規表現を考えるために頭をひねったりしなくても済むようにするテク - Sep 05, 2007

拡張機能勉強会の時に焚き付けられたText Shadowのコード(textshadow.js)を教材にして拡張機能開発のノウハウを解説していくシリーズ。

Firefoxの拡張機能で、DOM要素ノードを動的に生成したり、編集したり、そうして生成したノードを後でまた収集したり、といった操作を行うような物を作る時は、必然的に、ソースの中に要素名や属性名が登場してくる。

var newNode = document.createElement('box');
newNode.setAttribute('class', 'my-custom-box');
parentBox.appendChild(newNode);

var nodes = document.evaluate('/descendant::*[@class="my-custom-box"]',
  document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
for (var i = 0, maxi = nodes.snapshotLength; i < maxi; i++)
{
  this.processBox(nodes.snapshotItem(i));
}

こういう操作が一カ所だけにしか登場しないんなら別にいいけど、複数箇所で、似たような操作が何度もある場合は、要素名であるとか属性値・属性名であるとかノード検索の条件であるとかを、コードの冒頭で定数(定数プロパティ)として定義しておくことをお薦めしたい。

var myService = {
  CUSTOM_BOX_NODE_NAME  : 'box',
  CUSTOM_BOX_CLASS_NAME : 'my-custom-box',
  CUSTOM_BOX_EXPRESSION : '/descendant::*[@class="my-custom-box"]',
(略)

すると、さっきのような箇所はこうなる。

var newNode = document.createElement(this.CUSTOM_BOX_NODE_NAME);
newNode.setAttribute('class', this.CUSTOM_BOX_CLASS_NAME);
parentBox.appendChild(newNode);

var nodes = document.evaluate(this.CUSTOM_BOX_EXPRESSION, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
for (var i = 0, maxi = nodes.snapshotLength; i < maxi; i++)
{
  this.processBox(nodes.snapshotItem(i));
}

textshadow.jsの冒頭箇所を見てみると、動的に生成するIDのプレフィクスとか、途中で生成する要素のクラス名であるとか、XPath式の中に埋め込む検索条件だとかを、片っ端から定数としてまとめて定義していることが分かるはずだ。

こうしておくと、いざクラス名が他の拡張機能とかぶっていたと判明した時なんかでも、ソースの頭の方をちょこっと書き換えるだけで済む。class属性の値として文字列リテラルが書かれている箇所を片っ端から探すようなことはしなくていい。

え? 一括置換を使えばすぐだろうって? まあ、確かにたいていの場合はそうなんだけど、でもそれじゃ解決できない時もある。

例えばこんな例。

(略)
myAttributeCheck : function(aNode, aNewValue)
{
  var value = aNode.getAttribute('myAttribute');
  if (value != aNewValue) {
    this.report('old value is '+value);
    aNode.setAttribute('myAttribute', aNewValue);
  }
},
(略)

ここで「myAttribute」という属性名が他の拡張機能とかぶってた!ということに気がついた時、このJavaScriptファイルの中を「myAttribute」→「myExtensionCustomAttribute」と一括置換したとする。すると、この箇所はこうなるだろう。

(略)
myExtensionCustomAttributeCheck : function(aNode, aNewValue)
{
  var value = aNode.getAttribute('myExtensionCustomAttribute');
  if (value != aNewValue) {
    this.report('old value is '+value);
    aNode.setAttribute('myExtensionCustomAttribute', aNewValue);
  }
},
(略)

属性名と同じ文字列があったメソッド名の定義の部分まで変わってしまったことに気がついただろうか。このメソッドがこのファイルの中でしか呼ばれていない物なんだったら別にいいけど、他のファイルで元の「myAttributeCheck」というメソッド名を参照してる箇所があったら、エラーになってしまうじゃないですか。

え? GREPしてから一括置換すればいいだろって? なるほどその通り。でも、その文字列がメソッド名にも使われてるということを忘れて、属性値の定義部分にしか登場しえないと思い込んで、メソッド名に使えない文字を置換文字列に使ってしまったら? 例えば、「myAttribute」を「my-extension-custom-attribute」に一括置換してしまったら?

(略)
my-extension-custom-attributeCheck : function(aNode, aNewValue)
{
  var value = aNode.getAttribute('my-extension-custom-attribute');
  if (value != aNewValue) {
    this.report('old value is '+value);
    aNode.setAttribute('my-extension-custom-attribute', aNewValue);
  }
},
(略)

これはもう問答無用でエラーになってしまう。

こういう例を見ても分かるとおり、一括置換というのは注意深くやらないと思わぬ所で問題を引き起こしてしまう可能性がある。無論、正規表現を駆使すれば「本当に変更したい箇所」だけをピンポイントで狙い撃ちすることもできるだろうけど、そんな事に頭を使うのって激しく無駄じゃないすか?

でも、こういう属性名の定義を定数プロパティにまとめておけば、そんな気苦労からはおさらばできる。

var myService = {
  CUSTOM_BOX_NODE_NAME  : 'box',
  CUSTOM_BOX_CLASS_NAME : 'my-custom-box',
  CUSTOM_BOX_EXPRESSION : '/descendant::*[@class="my-custom-box"]',
(略)

一括置換しなくても、さっきも書いたとおり、冒頭のここをちょこっと書き換えるだけで作業は終わりだ。

また、仮に一括置換をどうしても使いたくなってしまった時……「CUSTOM_BOX_CLASS_NAME」という定数プロパティ名自体が気に入らなくなってしまった時でも、安心して一括置換できる。なぜなら、ここでは「定数プロパティ名は全部大文字で書く」というコーディングルールを採用しているから、仮にこの定数プロパティ名を一括置換したところで、他の箇所の変数やメソッド名には全然影響しない。

ということで、まとめ。属性名や属性値など、後で変更する可能性があるものはソース内のあちこちに文字列リテラルで書いてしまわずに、一カ所にまとめて定数プロパティとして定義する。また、「定数プロパティ名は必ず大文字で書く」という風なコーディングルールを徹底しておく。安易に属性名を決めてしまったり、どんなメソッドを定義していたか忘れてしまったり、ということの多い僕のようなウッカリさんには、上記のようなミスを未然に防ぐためにも是非採用してもらいたいところです。

分類:Mozilla > XUL, , , , , , , 時刻:02:40 | Comments/Trackbacks (1) | Edit

Comments/Trackbacks

no title

Firefoxの拡張に限らず「マジックナンバを直書きしてはならない」という話と合わせて、すべてのプログラミングで留意したいと思います。

Commented by 木俣 at 2007/09/05 (Wed) 03:14:25

TrackBack ping me at


の末尾に2014年1月19日時点の日本の首相のファミリーネーム(ローマ字で回答)を繋げて下さい。例えば「noda」なら、「2007-09-05_constant.trackbacknoda」です。これは機械的なトラックバックスパムを防止するための措置です。

Post a comment

writeback message: Ready to post a comment.

2014年1月19日時点の日本の首相のファミリーネーム(ひらがなで回答)

Powered by blosxom 2.0 + starter kit
Home

カテゴリ一覧

過去の記事

1999.2~2005.8

最近のつぶやき

オススメ

Mozilla Firefox ブラウザ無料ダウンロード