Home > Latest topics

Latest topics 近況報告

たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。

萌えるふぉくす子さんだば子本制作プロジェクトの動向はもえじら組ブログで。

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

Page 4/26: « 1 2 3 4 5 6 7 8 »

RestartlessのE4Xレス化 - Dec 26, 2012

再起動の要らないアドオンをAdd-on SDK無しで開発するときの簡易的なテンプレートのような物として作っているRestartlessは、ツールバーボタンの定義や設定ダイアログの定義にE4Xを使う前提で設計してた。静的なXULファイルを別に用意しなくても、JavaScriptで書かれたロジックの中にリテラルとしてXULの定義を埋め込める、ということで重宝してたんだけど、E4Xのデフォルト無効化で完全にハシゴを外された形になってしまった。

E4Xを多用してた方々各方面悲喜こもごもあるようで、自力でスクリプトをパースしてCDATAマーク区間だけでも認識できるようにするとか色々なアプローチが試みられているようなのだけれども、僕はというと、面倒なのでE4Xはスッパリ諦める事にした。元はといえば、従来のやり方に近いやり方で再起動の要らないアドオンをラクに開発したいというのがRestartless開発の動機だったのだから、何か代わりの手段があるのであれば、E4Xにこだわる理由はなかったし。

まずE4Xで定義されたXULのコード片を受け取る部分についてだけど、これらは内部的にはXMLのソースコードの文字列に変換した後、RangeのcreateContextualFragmentでDOMDocumentFragmentにしてたので、インプットはただの文字列でも構わなかった。なのでToolbarItem.jsconfig.jsは以下の点だけ修正して終わりとした。

  • E4X無効の環境でもシンタックスエラーにならないように、E4X固有の書き方をなくした(具体的には、識別子としていきなり「*」が出てこないようにした)。
  • インプットが文字列の時は、それをそのままDOMDocumentFragmentにした後、ホワイトスペースだけのテキストノードをその都度削除するようにした(XBLの定義が手抜きなせいで、こういう場合に初期化エラーになるので)。

ツールバーボタンの定義は、手間だけど文字列リテラルに書き直すことにした(簡単なスクリプトでインデントも含めてそれっぽく置換した)。ちょよんごさんのnode-hereを移植することも考えたんだけど、単に改行込みのコード片を文字列として取得できるだけだと、E4Xの時みたいな「属性値やテキストノードの値にJSの変数の値を入れる」ということが簡単にはできなくて、そこの面倒さの方が気になったので、今回は見送ることにした。

ただ、設定ダイアログくらいの規模となると、一々文字列リテラルにしてたら埒があかないのでそこだけはなんとかしたかった。これについては、Firefox 8くらいから後のバージョンではchrome.manifestでcontentやlocaleのChrome URLを動的に登録・解除できるようになってるので、昔ながらのやり方に戻って静的なXULファイルで書くようにした

ロケールもこの要領でpropertiesファイルからDTDに書き直せばいいんだけど、Fox Splitterみたいにロケールの数が多いとそんなのやってらんないし、Fox Splitterの場合は設定ダイアログとブラウザ上のUIとで同じラベルを使い回したりしていて、どれをpropertiesファイルに残してどれをDTDにするのかとかを考えるのももう無理っぽかったので、逆転の発想で、属性値やテキストノードの値に{{ JavaScriptの文 }}という書き方を許容するようにして、XULドキュメントが開かれたときに自動的に解決するユーティリティを加えて、propertiesファイルをDTD代わりに使うことにしてみた。DOMContentLoadedのタイミングだと遅すぎるようだったので、今の所は待ち無しで実行してるけど、XBLの初期化タイミングが狂うリスクがあるので、将来的には問題が起こるかもしれない。(……と、ここまで書いて気付いたけど、node-hereを移植した上でこれと同じ記法を許容するようにすればよかったかなぁ。もう後の祭りだけど。)

Restartlessベースで作ってた各アドオン(親のタブに戻るFox Splitter、あと開発中で放置してた「Suspend Tab」)も更新済みで、masterベースのXPIはこの間から提供してる自動ビルドが既に利用できる状態になってる。Nightlyで使ってた人がもしいたら、これで動くようになってると思う。

……node-here.jsの移植はやらないでおくって書いたけど、結局やっぱり必要になってしまったので移植した。スタックトレースから列番号を取れないというGecko(SpiderMonkey?)の制限のため、1行の中に2個以上のhere()は書けない。

setTabState()を使わないでタブのサスペンドと復元をやる実装のサンプル - Apr 25, 2012

ツリー型タブに「バックグラウンドにあるタブをアンロードする機能を付けてくれ」って要望が来てて、そんなもんDormancyやらBarTabやらSaveMemoryやらUnloadTabやらなんぼでもあるがな、と思ったんだけど調べてみたらツリー型タブと併用できなかったり(Dormancy, UnloadTab)併用できても今のFirefoxで動かなくなってて開発止まってたり(BarTab)併用できないわ開発止まってるわだったり(SaveMemory)と軒並み全滅だった。

そもそもこの手の奴がなんでツリー型タブと併用できないのかっていうと、

  • ツリー型タブは、ツリー構造の情報をタブのセッション情報の一部として保存している。
  • タブをアンロードする系のアドオンは、セッションストアAPIを使ってgetTabState()でタブの現在のセッション情報を吸い出した後、タブを閉じて、新しい空のタブを開いて、そのタブが選択された時にさっき吸い出したセッション情報をsetTabState()で適用する、という事をしている。

ということで、アンロードする系のアドオンが元のタブを閉じてしまうからツリー構造が破壊されてしまうわけです。BarTabは互換性のためにツリー型タブのAPIを呼んでツリー構造をわざわざ復元するようにしてくれてたから併用できてただけで、本質的にはやっぱり相性が悪い。

っていうかべつに元のタブを閉じる必要なんてなくね? nsISHistoryのPurgeHistory()でセッションヒストリを空にするだけやったらあかんの? あと状態を復元する時もタブの属性とかまで含めて全部上書きせんでもいくない? と思って、「アンロードする時はセッションヒストリを消すだけ」「復元する時はセッションヒストリを復元するだけ」という実装のサンプルを作ってみた。

めんどくさかったから、nsSessionStore.jsをそのまま読み込んで流用するという乱暴なことをやってる。

これに「何分以上放置したら勝手にアンロード」「タブが選択されたら復元」みたいな事を加えればDormancyとかBarTabとかの代替になるんだろうけど、これ以上管理対象のアドオンを増やすのもイヤだし、かといってDormancy等にそのまま取り込んでもらえるとも思えないし(やり方があまりにダーティすぎて、開発の継続性を低下させてしまうだろうし、そもそも現状で困ってるのなんてツリー型タブのユーザくらいだろうし……)、どうしたもんでしょうかね。

……とかいってたんだけど結局真面目に作り始めてしまった。30分以上放置してたら勝手にアンロードするとかその程度の事はとりあえずできるようになってはいる。

文字入力中に補助的な情報や機能をポップアップとして表示するとキャレットが消えてしまう件について分かったこと - Mar 10, 2012

先に要点だけ書いておくと、文字入力中にXULのmenupopupで補助的な情報や機能を表示したい時は、openPopupAtScreen()openPopup()の引数で明示的にコンテキストメニューであると指定してポップアップを開くようにすると、キャレットが消えなくなります(あくまでworkaround。根本的な解決策とは言えない)。それか、menupopupの代わりにtooltipやpanelを使うようにするというのも、キャレットを消えなくする方法として有効のようです。という話。以下は、そこに辿り着くまでの間に何を調べたかという事の記録です。

続きを表示する ...

FirefoxやThunderbirdを必ずクラッシュさせるコード - Mar 02, 2012

クラッシュレポーターの挙動を調べたい時に。以下のコードを改行無しでエラーコンソールあたりで実行すれば一発で落ちる。

Components.utils.import('resource://gre/modules/ctypes.jsm');
const gNspr4 = ctypes.open(ctypes.libraryName('nspr4'));
const PR_Free = gNspr4.declare(
        'PR_Free',
        ctypes.default_abi,
        ctypes.void_t,
        ctypes.voidptr_t
      );
var ptr = new ctypes.voidptr_t(0123);
PR_Free(ptr);

要は、js-ctypesを使って適当なアドレスのメモリを解放してメモリ破壊を引き起こしてみるということで。

破壊するアドレスによってはひょっとしたらどえらいことになるかもしれないので、試す時は自己責任でね!


なんか誤解してる人がいるかもなので追記しておく。

このコードはjs-ctypes(JavaScriptからC製のライブラリを呼び出す機能)を使っていて、NSPRというFirefoxやThunderbirdのかなり根っこの方のライブラリに含まれてるメモリ操作系の関数を呼び出して意図的にメモリ破壊を引き起こすという例です。クラッシュして欲しくないコードでクラッシュしてしまうという脆弱性系の話ではありません。

でもって、js-ctypesはChrome権限が無いと使えません。言い換えると、Chrome権限を取られてしまうような脆弱性を突かれない限りこのようなコードはWeb上にある普通のスクリプトからは実行できないし、Chrome権限を取られてる(アドオンの一部として実行されている)状況ではクラッシュどころかパスワードマネージャに入ってるパスワードのぶっこぬきでも何でもやり放題だから、むしろその事(特権を取られてるという事)自体の方が問題です。

仕事でFirefoxの導入案件をやる時に、クラッシュレポーターを無効化してくれと言われて無効化したつもりだけど、ほんとに無効化されてるかどうか確認したかった、でも最近のFirefoxはわりかし安定してるからそう簡単にはクラッシュしてくれない、ハードウェアアクセラレーション系の機能あたりを使ってクラッシュするのを気長に待つわけにもいかない、ということでこういうコードを使って手っ取り早くクラッシュさせてみた……というだけの話ですよ。

tabFx2Compatible.xul、tabFx2Compatible.css、tabFx2Compatible.xmlを使わないようにした - Feb 09, 2012

ツリー型タブ情報化タブの今日付でのリリース分から、tabFx2Compatibleという自作のライブラリ(?)を使わないようにした。当初はマルチプルタブハンドラでも使ってたけど、ツリー型タブ・情報化タブに先駆けて一足先に使わないようにしていた。今回残りの2つでも利用をやめたことで、TBEやり直し組のアドオンからはこのライブラリが全く姿を消したことになる。作ったのが2008年の2月からだから、丸4年くらいは使ってたのか。

このライブラリは元々、Firefox 3での仕様変更に追従するために仕方なく作った物だった。

Firefox 2ではタブの中に任意の要素(サムネイル描画用のcanvasだったりカウンタ表示用のlabelだったり)をXBLのコンテナ要素を使って動的に追加できていた。しかしFirefox 3になる時、具体的には2007年の12月頃に、高速化のためにそういう冗長性を排除する変更が行われてしまって、JavaScriptで動的にタブの内容を追加するということが原理上不可能になってしまった。

正確には、方法はあった。Firefox本体がタブに適用していたXBLによるバインディングを独自のバインディングで置き換えて、それを使ってタブの内容を変更するというやり方だ。しかし、XBLのバインディングは同時に1個だけしか適用できないという制限がある。複数のアドオンが同じ事をやろうとしたら、結局どれか1つのアドオンしかまともに動かないという事になってしまう。他の人の作る物との互換性という話に限らず、リッチでファットなAll-in-One型のアドオンであったTBEを捨てて各機能ごとに個別のアドオンに開発し直す道を選んでいた僕にとっては、自分の手がける物同士の互換性を維持するためにも、これは致命的な問題だった。

前述のライブラリは、特定のアドオンのために特化したバインディングを適用するのではなく、Firefox 2時代の「ある程度好きなようにタブの内容を増やせる余地がある」、冗長性を持った汎用的なバインディング定義を、Firefox 3向けに復活させるという物だった。

風向きが変わったのは2010年9月の事だった。Firefox 3.7のモックアップで示された視覚的なデザインを実現するために、タブのバインディングに再び冗長性が付与された。メジャーバージョンとしては、この変更はFirefox 4から反映されている。僕はこの仕様変更をうけてtabFx2Compatibleを改修し、Firefox 4以降のタブのバインディングの構造と、Firefox 2以前のタブのバインディングの構造の、両方を併せ持つ状態に変更した。

という経緯を見ると分かるかもだけど、このtabFx2Compatibleというライブラリは本質的に、Firefox 3.0から3.6までの間のバージョンに対応するためには欠かすことができなかった。ツリー型タブ等のアドオンの対応バージョン範囲の下限がこの範囲に重なっている間は、このライブラリは絶対に使う必要があったので、いくらTMPやVimperator等とバインディングの衝突の危険性があったとしても、構成ファイル群から外すことができなかった。

今回、Firefox 10のESR(延長サポート版)がリリースされたことにより正式にFirefox 3.6の死期が確定したので、サポート終了を待たずにFirefox 3.6のサポートを打ち切って、それと同時にtabFx2Compatibleも廃止することにした。レガシーなFirefox 2由来のDOMツリー構造を前提にして書かれたスクリプトやスタイルシート等を、全面的にFirefox 4以降の既定のDOMツリー構造に合わせて変更する作業は、それなりの手間を要したけれども、これでカビの生えた過去から決別できるのなら安い物だと思った。

選択肢の1つとして、tabFx2Compatibleを今後も使い続ける(Firefoxのタブのバインディングの構造が変わったら、その度にtabFx2Compatibleを更新していく)というやり方もあった。今この瞬間にかける労力を最小化する事を選ぶのなら、そういう選択もあり得たと思う。でも、tabFx2Compatibleは元々Firefox 3から3.6までの暗黒期を乗り越えるためだけに用意された物だったのだから、用済みになったのなら、思い切って捨てた方が今後のためになると思った。

こういう切り替えをできる時にやっておかないと、またTBEみたいな事になってしまう恐れがある。TBEでは、タブの移動等といった基本的な機能すらFirefox本体に備わっていなかった頃から開発していたため、TBE自身で独自の「タブを移動する」などのAPIを実装していた。そういう古いオレオレAPIから、Firefox 1.5くらいから新しくFirefox本体に備わったTabMoveとかのDOMイベントベースでのやり方にスイッチする事の手間を惜しんで、「とりあえず今動いてるから」と独自のオレオレAPIを維持することに固執してしまったがために、TBEはFirefox 1.5とともに時代に取り残され死んでしまった。そんな愚はもう繰り返してはいけない。

それにつけても、あの辺(Firefoxのタブまわり)の開発をやってる人達の意向に振り回されっぱなしだった4年間だったなー……と思うと、なんか感慨深い。

nsISessionStoreのsetTabValue()/getTabValue()がSSTabRestoringイベントの前でも使えるようになっている件 - Dec 07, 2011

で、それを活用できるのはいつのタイミングなのよって話。

ツリー型タブはツリー構造の復元にSSTabRestoringイベントを使っているのだけれども、Firefoxのセッション復元機能はタブを1個ずつ復元していくために、100個とか大量にタブを開いているとツリー全体が復元されるまでにめちゃめちゃ時間がかかってしまうという問題がある。

これを何とかしたくて、setWindowValue()/getWindowValue()を使ってタブではなくウィンドウそのものの方にツリーの構造の情報を持たせるとかそういう事を実験してみてたんだけど、いまいち期待したような結果を得られなくて長らく放置してた。

が、最近また同じ要望が上がってて、「だから無理だって言ってんじゃん……」と思いながらその時作った実験的なコードをもう一度いじってみたら、予想に反して結構期待に近い感じで動くようになっていた。

そもそも何故過去に実験した時に期待したような結果を得られなかったかというと、SSTabRestoringイベントが発行される前のタブに対してsetTabValue()/getTabValue()をやった時の挙動が怪しかったからだった。少なくともFirefox 3.6では、SSTabRestoringイベントが発行される準備が整った時点まで待たないと、前回保存したはずの値をgetTabValue()で取れないという制限があった。それでは結局、普通にSSTabRestoringを待つしかないという事になってしまう。

それに対して今のFirefoxでは、タブが選択されるまでは内容をロードしないという機能が取り込まれていて(主にメモリの節約のためと思われる)、「セッションは完全には復元されていないが、setTabValue()/getTabValue()で情報を保存したり読み取ったりされる」という状態を考慮する必要がある。そのためいつの頃からか実装が変わって、SSTabRestoringを待たなくてもgetTabValue()で前回保存した情報を読めるようになってた。(という事に、今回初めて気がついた。)

SSTabRestoringを待たなくてもよくなった、という事は分かったんだけど、じゃあ問題はいつのタイミングでなら安心してgetTabValue()できるのかっていう事。DOMContentLoadedなのか、loadなのか、それともそこからさらにsetTimeout()で遅らせた方がいいのか? できれば早いタイミングでやりたいけど、あんまり早くやり過ぎると他の処理を壊してしまいそうで怖い。

何かヒントはないかとnsSessionStore.jsを調べたところ、sessionstore-windows-restoredまたはsessionstore-browser-state-restoredというtopicのObserver NotificationをnsIObserverとして受け取ればよさそうだということが分かった。これらはsetWindowState()された後にそのイベントループの最後で発行されるObserver Notificationで、どっちが発行されるかは場合によって変わるんだけれども、どちらも全く同じタイミングで発行されている(三項演算子でtopicだけ切り替えてるようだった)。なのでこのタイミングでgetTabValue()した情報に基づいてツリー構造を復元するようにしてみた所、無事成功してくれた。

ということで、SSTabRestoringでは物足りないという人はこれらのObserver Notificationを使うといいと思います。

Firefox Hacks Rebooted 4章正誤情報速報 - Nov 20, 2011

Firefox Hacks Rebootedの正誤表を早いとこ作らないといけないという事は認識しているのですが、他の事に忙殺されていて手を着けられていません。なので、自分の担当章で現在把握しているミス、出版後に状況が変化した点などについて書いておきます。(ここに書いた内用は、いずれ本書のサポートサイトにもまとめる予定です。このエントリは「速報版」ということで。速報と言うには遅すぎますが……)

Bootstrapped Extensionsでのchrome.manifestについて(Hack#33 、217ページ)

本書での解説のうち、chrome.manifestの読み込みの方法の記述が事実と異なっています。

Components.manager.addBootstrappedManifestLocation()およびComponents.manager.removeBootstrappedManifestLocation()には、 chrome.manifestのファイルの位置ではなく、XPIファイルもしくはchrome.manifestがあるフォルダの位置を渡します。別のファイル名・別の位置のマニフェストファイルを読み込ませたい場合は、chrome.manifestの中からmanifestディレクティブで間接的に読み込ませる必要があります。

なお、Firefox 8および9ではBootstrapped Extensionsに含めたchrome.manifestは自動的には読み込まれませんが、Firefox 10からは自動的に読み込まれるようになります(Bug 675371)。

e10s(プロセス分離)について(Hack#34~38、219ページ~249ページ)

各地で既報の通り、デスクトップ版Firefoxのe10s有効化は当面行われない事になりました。これらの節で解説しているMessage Managerの仕組み自体はまだ残されるものと思われますが、個人的な予想として僕はMessage Managerの仕組み自体も将来的に削除されてしまう可能性があるとも考えています。ですので、何らかの事情でどうしても使わないといけないという事でない限りは、Message Managerの仕組みは使用しない事を、僕個人としてはお勧めします

かなりのページ数を割いたにも関わらずこういう事になってしまって、本当に残念です。既に本書の内容を参考にe10s対応のための準備を始めていた方もいらっしゃる模様で、Firefox 3 Hacksの時のFUEL解説と同じように、結果的に皆さんを振り回してしまう事になってしまい誠に申し訳ありません。

プログレスリスナの実装について(Hack#33 、237ページ~238ページ)

サンプルコード中でnsIWeakReferenceという記述がありますが、これはnsISupportsWeakReferenceの誤りです。

ワーカースクリプトでのdata: URIの利用について(Hack #42、271ページ)

data: URIは利用できないと書いていますが、Firefox 10からはdata: URIも利用できるようになります(Bug 699857)。

ワーカースクリプト内でのXPConnectの利用について(Hack #42、276ページ)

本節ではChromeWorkerの名前空間で、XPConnectへのアクセスのためにXPCOMというオブジェクトを利用できると書いていますが、Firefox 8正式版ではこの機能は廃止されました(Bug 649537)。ワーカーの名前空間からは、XPConnectは一切利用できません。

ただ、本節で述べている通り、元々ワーカーの名前空間からXPConnect経由で利用できる機能はほとんど無かったという事と、js-ctypesは引き続き利用できる(Nightlyで実際に確認しましたので、これは間違いないです)という事から、実質的にはほとんど影響の無い変更だったと言えます。

Firefoxアドオンの開発を通じて考えるようになったインタラクションデザイン - Jun 30, 2011

あかつかだいすけさんのご紹介で、6月27日に慶応大学湘南藤沢キャンパスでインタラクションデザインについての講義を1時間ほどやらせていただきました。その際の発表資料はいつもの通り「高橋メソッド in XUL Returns」で作ったのですが、Remote XULがデフォルトで禁止されるようになったFirefox 4以降だとこれを見るためには色々と面倒が多いので、代わりに講義の内容を思い出しながら資料を引用しながらエントリにまとめてみたいと思います。

事前に伺っていたオーダーは、これまでのアドオン開発の中でUIという点についてどういうデザインポリシーを持って開発してきたのかを、また、コンセプトを実装する際の経験から得られた知見を語るというものでした。今までにもこの場やTwitter等で断片的に語ってきている事ではありますが、改めて整理するいい機会になったと思います。

自己紹介

まず簡単に自分の事をお話ししておきましょう。

僕は大阪出身の28歳で、Firefoxのアドオンとの関わりは「Firefox」というプロダクトが世に出るより前からなので、もうそろそろ10年になろうとしています。アドオンをやり始めた頃は大学1年とか2年とかの頃だったと思いますので、講義を聴いていただいた皆さんくらいの年の頃からということになります。卒業制作(僕のいた学科では卒論ではなく卒業制作、しかも選択制だったのです……)もMozillaの拡張機能で「2次元的にWeb閲覧の履歴を表現する履歴表示UI・兼・Webブラウズ用UI」という物だったので、余計に感慨深い物があります。

そういう僕ではありますが、今はクリアコードという受託開発の会社でプログラマとして勤務しており、最近はRubyでWebサービスを作るといった案件に関わっています。仕事の上でインタラクションデザインの事を考える事は実際の所ほとんどなくて、そういう事を実践する機会趣味のアドオン開発の場面くらいしか無いというのが実情です。(きちんとインタラクションデザインの事について学んでいる人に比べると、おそらく物凄く初歩的な事しか理解していない、本来であれば人前でインタラクションデザインについて一席打っていいような立場ではないとは思っています。)

自分はこの何年かで数だけはやたら多くのアドオンを開発してきましたが、インタラクションデザインという観点から語りやすそうな物として、以下の4つを特に取り上げて語ってみたいと思います。

  • ツリー型タブ:タブバーをウィンドウの上ではなく左に移動して、タブを縦に並べて表示し、タブの親子お関係をインデント幅で表現する。
  • マルチプルタブハンドラ:複数のタブをドラッグで選択して、まとめて閉じたりリロードしたりと言った操作を可能にする。
  • Fox Splitter:Firefoxのウィンドウを複数の表示領域に分割し、複数のページを平行して閲覧できるようにする。
  • セカンドサーチ:FirefoxのWeb検索バーにおいて、文字を入力した後で実際に使う検索エンジンを選べるようにする。

自分がUIをデザインする時に気をつけている事

5W1Hで目的をはっきりさせる?

ニュース記事を書く時の5W1Hという原則をご存じでしょうか。

  • 誰が(Who)
  • いつ(When)
  • どこで(Where)
  • 何故、何のために(Why)
  • 何を(What)
  • どのようにしたのか(How)

これらをきちんと抑えた文章にする事で、一体何の記事なのか、どういう事が起こったのか、という事を読者に過不足無く伝えられるという原則が5W1Hです。

僕は、UIのデザインにおいてもこれらの5W1Hが重要だと思っています。

  • 誰が(Who)
  • どういう利用局面で(When、Where)
  • どういう目的で(Why)
  • 何を(What)
  • どのように操作するのか(How)

これらを満たす事のできるUIを考える、というのが出発点としては分かりやすいのではないか。闇雲に「何か新しいUIを」と考えるよりも、これらを意識した方がゴールを設定しやすいのではないか。という事です。

何事でも目的は大事です。特にソフトウェア開発では、無目的に「単に技術的に可能なので作ってみました」という風に生み出したプロダクトがあっても、自分で使わないし誰も使わないために、そのまま死んだプロジェクトになってしまう恐れがあります。

ただ、それとは逆の場合もあると僕は思っています。「単に技術的に可能なので作ってみました」という物を、試しに自分で使ってみたら案外使い勝手が良かった。常用するためにもっと改良したくなった。というケースが僕には実際にありました。ツリー型タブは、元を正せばそのようにして生まれたアドオンです。

ツリー型タブの誕生の経緯

かつて僕はタブブラウザ拡張(Tabbrowser Extensions、TBE)という、Mozilla SuiteやFirefoxの貧弱なタブブラウズ機能をなんとなくいい感じにパワーアップするというようなオールインワン型のアドオンを開発していました。

その頃既に僕は、10とか20とかの多数のタブを同時に開いてWebを閲覧するという使い方をしていました。そういう使い方だと、通常の横一列のタブバーだと、今見ているタブが画面外に吹っ飛んでしまったり、さっきまで見ていたタブがどこに行ってしまったのか分からなくなったり、という具合に色々困った事が起こります。それで僕は2つの機能をTBEに持たせていました。

  • タブバーを多段表示して、1度に多くのタブを見えるようにする。 (タブバーを多段表示した様子)
  • タブを色分け表示して、どのタブからどのタブを開いたのかという親子関係を色で示す。 (タブを色分け表示した様子)

「多段タブ」は、僕がWindows 95を使い始めた頃から時々見かけていたインターフェースでした。Windowsのコントロールパネルで多数の設定項目を持つ項目を開いた場合に、そのダイアログが多段タブになっている場合があったからです。タブが多ければ2段や3段に分けて表示すればいい、というのは日常的に使うWindows自体の一般的なUIを見ていれば自然と思い浮かぶ発想でしょう。

タブの色分け表示は、元々は何か他のブラウザがそういう機能を持っていたのを真似て実装したような記憶があります。親となるタブと子タブとを同じ色で表示しておけば、タブの親子関係が一目で分かるようになります。IE7でもリンクからタブを開くとそれと同じようにタブが色分け表示されるようになっていましたが、当時としては、これもまたそう珍しくない機能だったのでしょう。

そこにツリー表示機能を加えるきっかけになったのは、malaさんが自身のブログで紹介されていたiRiderというIEコンポーネントブラウザでした。

livedoor Readerの開発者として知られるmalaさんは昔からJavaScriptで変態的な、もとい、他の人が思い付いたり実践したりしていなかったような先進的な試みを色々とやっている方なのですが、そのmalaさんが2005年に2005年もパクられてこなかったもの私的まとめベスト3というエントリを書かれていた際に、iRiderというブラウザの名前を挙げておられました。今では配布元のドメインが失効していて百式のエントリにあるスクリーンショットくらいしか様子を窺える物が残っていないのですが、あのmalaさんが(「あの」って付けるくらいに、僕はmalaさんの着眼点はいつも鋭いと考えているのです)名前を挙げているくらいなのだからさぞかし凄い物に違いない!と思ったので、興味本位に加えて腕試しにという思いもあって早速TBEに「タブの親子関係を色分けだけでなくツリー表示というシルエットの変化で示す」という機能、あと、リンク先のエントリでも触れられている「撫でるインターフェース」も併せて実装しました(実はこれが現在のマルチプルタブハンドラの原型だったりもします)。

iRiderのツリー表示というのは、実を言えば、「タブのツリー表示」ではありませんでした。ウィンドウの左に表示されているのはあくまで2次元的に表現されたWebページの閲覧履歴であって、それらの履歴項目をドラッグしたりクリックしたりするとウィンドウの右側でページを見る事ができる、というのがiRiderの挙動です。ただ、当時既にTBEが持っていた「全てのリンクを新しいタブで開く機能」「リンクから開かれたタブを現在のタブの子タブとして扱う機能」にいくつかの機能を加えさえすれば、(メモリの消費量等の観点ではオーバーヘッドはあるものの)表面的にはiRiderと同じような挙動を示すようになるだろうという見通しは立っていました。

ちなみに、タブに相当する物をツリーで表示するブラウザというのは当時既にいくつか存在していたようですが、僕はそれらの事を意識していませんでした。というか、ツリー? 一体何がおいしいの? みたいな認識でした。そういうわけなので、この時はタブをツリー表示する拡張機能を作ったというよりも、iRiderの挙動をFirefoxで(無理矢理)再現するというのが、やった事の本質を正しく言い表しています。

そういう事でとりあえず技術的には可能だったから実装してみるだけしてみたのですが、タブを履歴の代わりに使うというのは当時の自分にはあまりに違和感が大きすぎましたし、iRiderの「履歴」と違ってMozillaのタブは開いていればその間ずっとメモリを消費する物であるという背景もあって、自分はこの時実装した機能のうち「タブをツリー表示する機能」だけを、せっかく実装したのだから……と、ずっと使い続けていました。

そしたら、これが案外便利だったんですね。

という事で自分自身がすっかりツリー機能に依存するようになってしまいました。その後メンテナンスしきれなくなったために僕はTBEの開発を中止しましたが、まずは「なでるインターフェース」の再現のために書いたコードを応用して、その機能だけを抽出して「タブを選択してから操作する」というコンセプトで再設計したマルチプルタブハンドラを公開し、さらに続いて、タブのツリー表示機能だけを抽出したアドオンをツリー型タブとして公開して、TBEの頃と同じような使い勝手を保ち続けて今に至っています。

自分が「良い」と思えるUIのデザインポリシーを逆算する

タブのツリー表示のどの点が便利だと感じたのか、自分自身でもそれと意識する事はなかったのですが、ツリー型タブをMozilla主催のアドオンコンテストに出品するにあたってアピール文を書こうとして、改めて「自分がツリー表示されたタブを使いやすいと感じた理由」を言語化してみる事にしました。

その時自分で分析して思いついた「使ってみて分かったツリー表示のいい所」は、以下のようなものでした。

  • 一覧性が高い。(多数のタブを開いていても、縦に重ねた表示形態であればまだ全体を見通せる。)
  • ページを閲覧してきた履歴がそのまま可視化される。(どのタブからどのタブを開いたのか、どのページからどのページへ遷移したのか、という事がツリーとして視覚的に、2次元的に表現される。)
  • タブが自分の存在を主張してくる。(ブックマークフォルダの奥にしまい込んだ時のように、存在を忘れてしまわないですむ。未消化のタスクがタブとして溜まっていくと、タブバーをだんだん圧迫してきて、「片付けなきゃ」という意識を僕自身に喚起させる。)

これらをもっと突き詰めて一言にまとめると、「ツリー表示であればうろ覚えでも使える」からこそ自分は利便性を感じたからなのではないか。それが自分の辿り着いた結論でした。

うろ覚えで使える、とはどういう事か。もう少し具体的に考えてみましょう。

  1. ボケーッとしてても使える。
    • どこに何があったんだっけ、という事を憶えてなくてもUIを見れば必要な情報がそこにある。
    • 且つ、それらがうるさく主張しすぎてもいないので、多すぎる情報を前にして迷う事がない。
  2. 考えなくても使える。さあこれからこのタスクをこなそう!といちいち気張らなくても使える。
  3. 初見でも使える。使い方を知らない状態で初めて使う時でも、戸惑わないで済む。
  4. 忘れても使える。使い方を忘れてしまって、初めて使う時の感覚に戻ってしまっても、初見と同じようにすぐ慣れる事ができる。
  5. 既存の物に似ている。それ自体の使い方を特別に覚えなくても、他の物からの類推で違和感なく使える。

1は、表示される情報が適切な量になっているという事だと言えると思います。2から5は、学習コストが低いという事です。どうやら僕は、これらの点を満たしている時に「良いUI」と感じているみたいです。

適切な情報量とは

この点はSFCでの講義では話せていなかった点ですが、このまとめでは書いておきます。

情報が適切に整理されている、という事は非常に重要です。例えばある家具屋さんの折り込みチラシを見てみましょう。 (チラシの画像。様々な商品の情報が所狭しと押し込まれている。) この種のチラシは「新春初売りセール開催中ですよ!」という事を顧客に告知して集客する事を第1の目的に、そして売り込みたい商品にどういう物があるのかをなるべく多く知らせる事を第2の目的にしていると考えられます。情報を見やすく整理するという事はあまり重視されていないので、余白を設けるよりも、少しでも多く情報を詰め込む工夫が為されています。顧客はこのチラシの中から目を皿のようにして安い商品を探す事になります。セールのチラシの場合は顧客には「そこにどんな情報があるのかをじっくり読み取る努力をするモチベーション」がありますから、多少の苦難は乗り越えられます。

しかしブラウザのUIは、使い手の側にそれほど強いモチベーションはありません。「さっきまで見ていたタブ」を見つけるためにいちいちこのようなゴチャゴチャした絵面の中を一生懸命探すのは、ただのストレスにしかならないでしょう。常用するUIがこんな感じではとても使えません。

視覚的な情報の量を調整するテクニックはいくつかあります。DTPのセオリーやWeb制作のセオリーなどを読んでもらうといいと思いますが、今自分でもぱっと思いつく物としては以下の4項目があります。

  1. 適切な余白を設ける。要素と要素を詰め詰めに配置しすぎない。(詰め詰めに配置すると、誤操作の元にもなり得る。)
  2. 同じ種類の要素は近くにまとめたり、同じ位置に揃えたりして、グループとして認識できるようにする。
  3. 色を整理する。バラバラの色を混在させないで、同じような物は同じような色でまとめておく。そうする事で、特別な場合にだけ色を変えるという事が「特別」として活きてくる。
  4. 色ではなくシルエットで判別させる。モノクロディスプレイだったり色盲だったりといった色の違いが分からない状況でも、シルエットの違いなら認識できる。また、一般的に色の違いよりも形の違いの方が人は認識しやすい(とどこかで聞いた気がする)。

ツリー型タブによる縦型タブバーの場合、2から4が特に顕著だと僕は考えています。

(ツリー型タブのスクリーンショット) スクリーンショットを見ると分かりますが、縦にUI要素が揃う事で、左にアイコン(とインデント)、真ん中にタイトル、右に「タブを閉じる」ボタンが集まっています。 (ツリー型タブと情報化タブの併用時のスクリーンショット) ここにサムネイルというUI要素が加わっても、縦に同じような位置に同じような物が並んでいるので、これはまとめて意識の外に置いておきやすいはずだ、と僕は思っています。実際、自分はこのようなタブバーの左の方を見ている時は右側を無視していますし、右側を見ている時は左側を無視している気がします。

逆に、横置きのタブバーでは、「アイコン、タイトル、クローズボックス、アイコン、タイトル、クローズボックス……」とそれぞれ異なるUI要素が一列に並んでいるので、「アイコンにだけ注目する」とか「クローズボックスにだけ注目する」という事がやりにくいと僕は思っています。

タブの親子関係は、タブの色分けではなく、インデント幅というシルエットの違いで表現されています。これなら、2段階以上のネストであっても状態を正しく把握できるでしょう。

視覚的な情報の整理の仕方はデザインの本を学ぶと色々と情報を得られると思います。自分は一時期Webデザイナーになりたいと考えていたので、DTP的な記事が多く載っていたWebデザイン雑誌も何度か目にしていたのですが、上記のような話はそういった所で仕入れた物のような気がします。

学習コストを低くするためには

ヒューマン・インターフェースのデザインのセオリーという物がいくつかありますが、それらはソフトウェアにおけるインターフェースのデザインにも適用できます。

見た目の工夫

何のための物か、何を表している物なのか、説明がなくても一目で分かるようなデザイン。形がそれ自身の役割を表す。これはUIのデザインでよく言われる事です。

ソフトウェアの場合は、まずそのOSにおける一般的なデザインの流儀に則ってUI要素をデザインしておけば、少なくとも「ここはクリックできる場所なのだな」といった事を伝えられます。その上で適切なアイコン画像を加えましょう。基本はやはり重要です。

見えているUI要素はそのまま使えるようにする、という事も大事です。「見えているのに使えない」「見た目通りには動かない」というのは、ユーザを惑わせるばかりの害悪です。例えば以下のような物は論外でしょう。

  • グレイアウトされていないのに、クリックしても何も反応しないボタン。
  • グレイアウトされているのに、クリックを受け付けるボタン。
  • いかにも押せそうな風に出っ張った視覚的なデザインであるにもかかわらず、ボタンになっていない。

基本的に、人は他の場面で憶えたやり方を別の場面でも使いたくなるものです。タブを掴んで移動できる、という体験をExcelでやった後であれば、Firefoxのタブも掴んで移動できると考えておかしくないでしょう。UIの基本的なルールを統一しておく事の意義はそこにあります。

ですので、奇をてらってワケの分からないUIにするよりも、まずは既存の類似のソフトウェアの挙動に合わせる事ユーザがこれまでに学習してきた事に反しないようにする事。これをまずは意識しておくのが大事です。AndroidアプリのUIガイドラインなど、各OSで何らかのデザインのガイドが提供されているのであれば、目を通しておいて損はないです。

ツリー型タブも、タブをドラッグ&ドロップした時の挙動はAdobe Illustratorのレイヤーの操作をモデルに整備しました。また、マルチプルタブハンドラも「Ctrl-クリックでタブの選択状態をトグルする」「Shift-クリックで複数のタブを一気に選択する」という、Windowsやなんかで一般的な操作で使えるようにしてます。こういうのも、既存の物に合わせている例ですね。

見た通りに使えるかどうか、という観点で「こりゃあかんわ、好きになれんわ」と自分が最近感じたUIは、以下の2つのアドオンです。

これらはいずれもFox Splitterと同ジャンルのアドオンで、Firefoxのブラウズ領域を複数に分割してそれぞれに別々のページを表示できるようにする物です。

実際インストールして試してみると分かると思いますが、これらはどちらも、ウィンドウの上の方にあるタブバーやナビゲーションツールバーという操作UIが、タイル表示されたWebページの間で共有される設計になっています。下のペインで「戻る」操作をしたければ下のペインにフォーカスしてから上のツールバーの「戻る」ボタンをクリックする、という具合に、操作の対象と操作のために入力UIがバラバラに切り離されてしまっています。

また、タブと対応するWebページとが離れてしまっているのも問題です。どのタブをクリックしたらどのコンテンツ領域がフォーカスされるのか、どのコンテンツ領域をクリックしたらどのタブがフォーカスされるのか、今どのコンテンツ領域にフォーカスが当たっているのか(フォーカスされているコンテンツ領域の周りには枠が表示されますが、背景色によっては枠がとけ込んでしまう)、といった事を、このUIだとぱっと見て判断しにくいです。慣れれば使えるのかもしれませんが、これに慣れたくはないなあ、というのが僕の正直な感想でした。

UIが常時見えている事、表示UIが入力UIでもある事

入力UIが状態の表示のためのUIを兼ねる、というのもUIのデザインでよく言われるセオリーです。

  • 音量のボリュームをいじるダイヤルは、回した角度が入力となり、また、ボリュームの角度がそのまま音量を示すインジケータになる。
  • ガスコンロのつまみなら、回した角度が入力となり、同時にその角度が火の強さを示すインジケータになる。

ソフトウェアの場合もやはり同じ事が言えます。例えばクリックする度に機能の有効・無効を切り替えるトグル式のツールバーボタン(XULでは<toolbarbutton/>要素)であれば、これはチェックボックス型(<toolbarbutton type="checkbox"/>)としておきましょう。チェックボックス型のツールバーボタンは、1回クリックすると押し下げられたままの見た目で固定され、もう1度クリックすると出っ張った状態に戻ります。これは、入力装置であると同時に、見た目で今の状態を表すインジケータとなります。

ツリー型タブの場合も、タブという元々入力と状態の表示を兼ねたUIであった物を、そのまま少し形を変えて縦に並べています。

使用頻度の高いUIについては、常時見えている事も大事です。クリックしないと出てこない物は、そのワンステップが煩わしくてそのうち使われなくなってしまいます。Firefox 4から標準搭載されているPanoramaもこの問題を抱えていますし、Google Chrome版のTree Style Tab(※作者は僕ではないです)も、Chromeの拡張機能向けAPIの仕様上の制限でそのようになってしまっています。パネル型でもいいので、これを表示したままの状態に固定できるようにならないと、常用するUIとしてはちょっとキツいと自分は思います。僕が自分でツリー型タブをChrome向けに移植しないのは、この点をクリアする方法がまだないように見えるからという理由もあります。

カーソルを合わせたら自動で出てきて、カーソルを外したら自動で消える、という物は、場合によっては有用です。

  • 意図しないタイミングで出てきてしまえばストレスになる。
  • 出てきて欲しいのになかなか出てこない(例えば、表示するために1ピクセルの幅の領域の上を正確にポイントしないといけないとか)のもストレスになる。

「自動で隠す」系のUIは、WindowsのタスクバーやGNOMEのGNOMEパネルのような、画面全体の一番端に貼り付くUI以外ではあまり実用的ではないと僕には思えます。(余談ですが、UI要素は大きければ大きいほどそれを操作しやすく、画面の端であればどこでも反応するというボタンやUI要素は「無限の大きさを持っているに等しい」とよく言われます。Windowsでウィンドウを最大化した時に画面の右上にマウスのポインタを移動させてみれば、大雑把に指し示しただけで「閉じる」ボタンがハイライトされる事が分かると思います。)

モーダルよりもモードレス

これは1つ前の話とも少しかぶるのですが、「入力するモード」と「それ以外の操作をするモード」という風な排他的なモード切り替えは、極力避けるべきだというのが僕の考えです。設計を見直して、そもそもモードの切り替えが必要ないようにする、くらいに突き詰めていいと思います。

モーダルなUIは、ユーザの持つメンタルモデルの中にモードの違いがキッチリ組み込まれていればいいのですが、そうでない場合(ユーザのメンタルモデルの構築に失敗している場合)には混乱の元になります。いつもやっている操作を違うモードでやっても何も反応しない。でもユーザは、モードが違うせいでそうなっているという事に気付かない。「違うモードなのでその操作はできませんよ」と警告を出しても、根本的な解決にはなりません。

この観点で僕がどうしても好きになれないソフトウェアの代表例が、viとかvimとかVimperatorとかそのあたりという事になります。vi系のエディタはコマンドモードと編集モードを行き来しながら操作するという設計になっていて、この種のストレスを自分はしょっちゅう感じます。

単機能・明確なコンセプト

これは「UI」というよりも、もっと前の段階のテーマ決めの話になるかも知れません。

「何でもできる」という謳い文句は、「何をすればいいか分からない」という問題と表裏一体です。何をするための物なのかとか、どう使うべき物なのかとか、それを使う事で自分の生活がどう変わるのかとか、そういった事が伝わってこないとユーザは戸惑ってしまいます。先程、視覚的なデザインによって目に飛び込んでくる情報を制限して情報量を抑えるという事を述べましたが、コンセプトの設定にも同じ事が言えます。

「これができる」「これをする物だ」という事がはっきり見えていればいるほど、メリットが見やすくなるのと同時に、どういうデメリットがあるのかも見やすくなります。基本的にアドオンのインストールには多かれ少なかれデメリットがつきまとっていますが、そのデメリットを乗り越えてでも欲しい機能があるのだという人と、そういうデメリットがあるのであれば絶対に使いたくないという人とをきちんとスクリーニングできれば、ユーザを無駄に不幸にさせずに済むというのが僕の考えです。

カスタマイズ性の高い設計にしていたとしても、というか、カスタマイズ性が高い場合にこそ、既定の設定は重要です。第一に想定しているユーザ、この人にまずは使ってもらおうと思っている相手が、違和感なく使えるような設定をデフォルトにしておきましょう。インストールした後必ず自分に合わせた設定の変更を行わなくてはならないのでは、使い始める時の心理的コストが大きくなってしまいますし、そもそも、使う前から「どういう設定が望ましいか」をユーザが判断するというのが土台無理な話です。

また、最近はいろんな機能を1つのUIに統合する事が流行っているような感じがあると思いますが、「何でもできるUI」として1つのUIに2つも3つも機能を詰め込んでしまうと、実際の利用時に却って不便になる事もあります。

Firefoxのロケーションバーと検索バーを統合して「普通の単語を入力したらGoogleで検索して、URLっぽかったらタブを開いて……」という風な「文脈に応じた自動判別」を行うようにしたとして、「google.comという単語を検索したい」という意図をそのUIは正しく受け取れるでしょうか? 文脈の機械的な自動判別には今のところ限界があるので、誤判定、いわゆる「誤爆」の発生はゼロにはできません。

じゃあ考えられる候補をすべて一気に列挙してしまおう、というのも乱暴すぎる話です。入力欄に何か語句が入力されたら、検索結果を3件、その下に過去の履歴で入力とマッチした物を3件表示する、とした場合、ユーザが最初から「過去に見ていたページをもう一度見たくて」その入力を行っていたのであれば、最初に出てくる検索結果3件はただのノイズになってしまいます。ユーザがその時したい事に対して適切な結果が帰ってこないのは、単純にストレスですよね。

ある機能の利用頻度が特に高いのであれば、その機能には専用のUIを割り当ててやった方がいいです。よく使われている物を無理矢理他の物と統合するというのは、信頼性の低い自動判別を無駄に1枚噛ませるだけになったり、メニューを選ぶための手間を無駄に1つ増やすだけになったりという事になると思うので、僕はなるべくそういう事は避けたいと考えています。

Appleは、コンセプトをはっきりさせるという事を非常に強く実践しています。例えばiPodは非常に割り切った設計で「音楽を聴く」事に特化していて、関係ない要素をガンガン省いていました。それによって、「これは音楽を聴く物なのだ」という事が嫌でもユーザに伝わります。Appleの場合はスティーブ・ジョブズが「これがいい」と言った物を会社を挙げて製品にしており、製品の第1顧客はいつもスティーブ・ジョブズだと言えます。また、キングジム社のポメラも、会議の場で商品の企画が出た時にはほとんどの人が反対したけれど、1人だけ「買いたい」と言った人がいたので商品化できてヒットに繋がったといいます。(余談ですが、リンク先の記事によるとその1人というのは慶応大学の印南教授だそうです。何とも奇遇ですね。)

あらゆる人の方向を向こうとして誰の方向も向けないよりも、誰の方向を向いているのかがはっきり分かるようにした方がいい。100人を満足させようとしてみんなに「ふーん、悪くないね。まあ機会があれば試してみるかもね。」って言われて実際にはそのままスルーされてしまうよりも、99人にそっぽを向かれてもたった1人のユーザに「これすごくいい!!」と飛びついてもらえる、その人を確実に満足させられる方がいい。というのが今の自分の考えです。これだけ物や情報が溢れている世の中では、そうやって製品の特色をはっきりさせないと、評価してもらう前の段階で埋没してしまうのではないでしょうか。

これらの点で僕が「駄目だなー」と思っているのが、かつてのTBETab Mix Plusといったオールインワン型の多機能アドオンです。メリットを一言で説明しにくいから紹介する側も「とりあえずこれ入れとけ」という風な言い方にならざるを得ず、紹介された側はどこが具体的にどう変わるのか・どのようなデメリットがあるのかをきちんと把握できず、それのせいでトラブルが起こったとしても「何をするためのアドオンを入れたからこうなった」という事が全く分からないから原因を切り分ける事もできず、という負の連鎖が起こってしまいます。

先に入力を受け付けておいて、どうするかは後で選べるようにする。思考を途切れさせない。

僕が持ってる携帯電話は、待ち受け状態で数字をぽちぽち入力すると、その数字を電話番号として電話をかける以外にも、電卓機能を起動したり、積算メモ(簡易家計簿)に記入したり、という風な事ができます。メニューから「電卓」を起動して数値を入力するのでも、積算メモ機能を起動してから金額を入力するのでもなく、数字をぽちぽち入力したら画面の中で「この数字を電卓に流し込みたい時はこっちのボタンを押す」「この数字を積算メモに記録したい時はこっちのボタンを押す」という誘導が行われるんですね。

僕はどうも、そういうUIが好きみたいです。セカンドサーチは、Web検索バーで文字を入力した時に「既定の検索エンジンで検索するの? Amazonで検索するの? それとも英和辞書?」という具合に利用できる検索エンジンをポップアップで列挙して、文字入力からそのままスムーズに検索エンジンの選択に移れるようにする……というアドオンですが、前述の携帯電話のUIと考え方は共通していると思います。(なお、僕がセカンドサーチを作ったのはこの携帯電話を使い始めるよりずっと前でした。)

思い返してみると、セカンドサーチを作った動機は、「検索エンジンを選んでから検索して、また元のGoogleに戻して、という操作が死ぬほどめんどくさい。やってらんない。」という不満からでした。

エンジンを切り替えるのが面倒だから、大抵既定の検索エンジンで済ませちゃう。たまに検索エンジンを切り替えて使うと、その後無意識のうちに既定のGoogleで検索したくなって、しかし検索エンジンを切り替えたという事実をすっかり忘れていて、うっかり別の検索エンジンで検索してしまう。そういうのが鬱陶しくて、僕は結局Googleしか使わなくなってしまいました。でも、せっかくOpenSearchとかで検索エンジンを追加登録できても、それらを使う機会が全く訪れないなんて、こんな勿体ない事があっていいはずがない。そう思って、「先に検索エンジンを選ぶのと、後で元に戻すのがめんどくさいんだ」と判断して、「後から検索エンジンを選べて、既定の検索エンジンに戻す必要がない」という操作ができるようにしてみたんですね。

こういう傍目から見てアホとしか思えない行動を取ってしまう人はけっこういるんです。Web検索バーに文字を入力する時には、入力しようと思っている語句に注意がいっているから、「どの検索エンジンが選択されてるか」という事に注意が向かないんですね。そういう風な思考ができあがっている時に、期待通りの結果が得られないと、「なんでやねん」とストレスを感じてしまうわけです。

そういうミスに対して「それはあんたの使い方が悪いんだ」と責める人もいるでしょうけど、道具は使う人のためにデザインされるべき物と考える限りにおいては、人がそういう行動を取ってしまうのなら、その行動に合わせて道具をデザインした方がいいと僕は思っています。

ユーザの行動や入力をトリガーにする

1つ前の話を別の観点からもう1回語ってみます。

金額の事だけで頭がいっぱいになっていて、とりあえず無意識に数字を入力し始めてしまう、という僕の行動をトリガーにして、僕の携帯電話はよさげな機能をサジェストしてくれるわけです。同じようにセカンドサーチも、頭の中が調べたい単語の事でいっぱいになっていてとりあえず無意識に単語だけ入力してしまうという僕の行動をトリガーにして、適切な検索エンジンを後から選べるようにサジェストしてくれます。このように、無意識の行動を起点にして各機能を呼び出せるようになっていると、「機能をどこから呼び出すのか」を改めて考えなくて済みます。

機能をどこから呼び出すのか、というのは結構重要な問題です。

メニューを何階層も辿った奥にある機能というのは使うのが億劫なので、だんだん使わなくなってしまいます。しかし、だからといって階層の浅い所にメニュー項目を増やしていったり、ツールバーのボタンをどんどん増やしていくと、機能が増えていった時に収拾が付かなくなってしまいます。

WindowsのコマンドプロンプトやLinuxやMac OS Xのターミナル(端末エミュレータ)のようにコマンドを入力させる形式なら表示領域を悔いませんが、今度は、ユーザが「どんなコマンドが使えるのか」という事を常に頭の中に入れておかないといけないようになってしまいます。コマンドだけじゃなく、今どこのディレクトリにいるのかとか、ログインしているユーザが誰なのかとか、いろんな「モード」を把握してないと使えません。僕はそんなの全部は覚えてられないので、CUIでコマンドライン入力というのは基本的に好きじゃないです。

表示領域も食わないし文字列のコマンド入力よりも直感的、という事でマウスジェスチャ(リンクをドラッグしたままマウスを上下左右に動かすと、その軌跡(ジェスチャ)がコマンドとして解釈される機能)はどうなんだ? っていう話になってきますけど、マウスジェスチャも「ジェスチャを覚えないといけない」という問題はやっぱりあります。上上下下左右……みたいなややこしいジェスチャを全部覚えてられるかというと、まあ無理な話です。実際、マウスジェスチャを使ってる人でも「憶えてるのは上下左右の各方向にマウスを動かす単純な動き程度でそれより複雑な物は全然使わない」と言ってました。

という話の中にも出てきていますが、ごく簡単なジェスチャだったらアリだと言えます。リンクを引っ掴んで適当にぽいっと投げたらそれをタブで開く、とか、その程度の大雑把さなら苦になりません。ただ、この場合は「ジェスチャを入力している」と言うよりも、「リンクというオブジェクトを掴んで何かしようとしている」という場面だと解釈した方が正しいでしょう。

僕が「ユーザの入力をトリガーにする」という言葉で表現したかったのはそういう事です。何かキー入力を始めたとか、何かオブジェクトがドラッグされたとか、そういう行為そのものをトリガーにするっていう事であって、「どういう文字が入力されたのか」とか「どういう風にマウスを動かしたのか」とかの細かい所を情報として使うという話ではないんです。もちろん、入力された内容にそぐわない機能を除外した上でサジェストする(例えば数字だけが入力されていたら英和辞書は候補に出さないとかの)工夫はあってもいいと思いますが。

「冷蔵庫に卵とトマトがある事を確認して、それからクックパッドで卵とトマトを使ったレシピを探そう」という風な理路整然とした行動計画を、現状から目指すゴールまでを最初から完全にきちんとイメージできる人ばかりではないんです。というか僕自身がそういう事ができない方なんですね。おなかすいてとりあえず冷蔵庫を開けてみたとか、そういう無駄な事をやってしまう方です。

そういう最初のアクションを無意識レベルで起こした結果、機能がサジェストされたり視覚的なフィードバックがあったりして、そこからさらに「そういえば自分はこういうことをしたいんだった」と次の行動が喚起される。(もっと言うと、その時の既定の挙動が一番利用頻度の高い物になっている。)という風な誘導がウザくないレベルで働くようなUIが、僕にとっての「良いUI」なんでしょうね。

ブラウザのUIはゲームのUIではない

ちょっと話はズレますが、ストリートファイターとかの対戦格闘ゲームを考えてみましょう。この種のゲームは、技を出すための複雑なコマンドを覚えたり、覚えたコマンドを適切なタイミングで正確に入力できるようになったり、という努力の末の「上達」が「勝利(報酬)」に結び付くというゲームのデザインになっています。勝てるようになれたら嬉しくて、負ければ悔しくて、なのでみんな必死になってコマンドを覚えるし、正確に入力できるようになろうとする、そういうモチベーションがある。

翻ってブラウザのUIという物を考えてみると、誰が好きこのんで複雑なコマンドを覚えたくなるの? ブラウザを上手く使えるようになる努力をして、上達して、それのどこが嬉しいの? って話なんですよ。普通のユーザにとって、ブラウザとかUIとかってそんな努力を費やす対象ではないです。勉強しないとまともに使えない斬新な文房具とか包丁とか、使いたくなりますか? なりませんよね。普通の人にとってのブラウザって、そういうレベルの物でしかない。

UIをデザインするんだ!って思って頑張る時に勘違いしてしまいやすいのはそこなんですよね。こんだけ頑張って斬新なUIを考えました、他に類を見ないUIになりました、ただし使うのにはそれなりに慣れが必要です、っていって作った物をユーザに見せても相手にされないですよ。そんなの作り手の自己満足でしかない。自分にとって物作りそれ自体が楽しくなってきた頃に犯しがちなミスです。

実用品のUIと、娯楽だったり趣味の物だったりのUIとは、別の物として考えて欲しいです。日常的な利用にゲーム性を加えてみましたとか、普段の利用がちょっと楽しくなりますとか、たまにありますけど、それで本来の用途に支障をきたしたりストレスが増したりするようになってしまっては本末転倒です。

自分の部屋とかキッチンとか

自分が生活する部屋や、日常的に使うキッチンの事を考えてみて下さい。

  • よく使うものは奥にしまわない。流しの下を開けたらすぐそこに包丁があるとか、毎日使う茶碗は引き出しの手前に入れておくとか、そういう風に自然となってる。逆に、奥の方にしまい込んだ鍋とかは、出すのが面倒でほとんど使わないという事も結構ある。
  • どこに何があるか、自分にとって分かりやすいような配置に落ち着いてるはず。
  • どこに何を置いたか、だいたいは把握できている。こういうことをしよう、と思った時にどこに手を伸ばせばいいかは無意識に判断できる。

ソフトウェアのUIも基本は同じだと思います。

よく使うものを手前に置くとかの話はここまでで述べた話に通じています。ここで新しく述べたいのは、「どこに何を置いたかだいたい把握してるはず」という部分です。

ロケーションバーと検索バーの統合という風に、最近は(主に表示領域を広く取るために)複数のUIを1つにまとめる風潮があるみたいなんですが、そうすると使う時に余計なステップが増えてしまいかねないというデメリットがあります。UIをどんどんまとめていくという事に固執しないで、時にはUIを分けるという判断も必要だと僕は思っています。「使い方を頑張って覚えなきゃいけない様なUIは駄目だ」という風な事を前述しましたが、並んでる物を使い分ける程度の事は大抵の人にはできるはずです。あんまりユーザをなめんなよ、ということですね。URLを入力したい時にはロケーションバーをクリックして、検索したい時は検索バーをクリックして、という事をユーザが無意識にできるのであれば、そこはUIの設計に組み込んで活かしてもいいはずです。

あと、キッチンに例えるついでに言うと、キッチンを清潔に保った方がいいのと同じでソースコードも清潔にした方がいいです。詳しくは実装の話で述べますが、生ゴミを放っておくと腐って虫が湧くように、汚いソースコードも放置すればバグの温床になりますので。

人は自分の行動パターンを変えようとしないという事

一般的に言って、フツーの人は「どうしても必要だ!」という強烈な動機がない限りは新しい物の使い方を積極的には覚えようとしませんし、いつも取っている行動のパターンも変えたがりません。というか、無意識のうちに同じ行動を取ってしまいます。そこから意識して外れようとすると、これはストレスになります。何度も述べていますが、「素晴らしいUIを作ったから、ユーザは使い方を覚えるために苦労してくれるはずだ」というのは作り手の傲慢なんですね。そんな物は、そのうち使われなくなってしまうのがオチです

ただ、あんまり人間の順応性をナメすぎてもいけません。最初は違和感があった「いつもと違う行動パターン」でも、そのうち慣れちゃうというのは結構あります。そこを考慮から外してしまうと、常用するのがウザいUI(例えば、いつまで経っても毎回馬鹿丁寧に「どうしますか?」と聞いてくるような)になってしまいます。

  • 今まで慣れ親しんできたものと同じ要領で使えるようにする。
  • 同じ要領では使えないものでも、なるべく早く慣れる事ができるようにする。
  • 慣れてしまった人に余計なストレスを与えないようにする。

これが大事なんだ、「どこまで丁寧にやってどこからはユーザの慣れに期待するか」のバランス感覚が必要だ、と僕は思っています。

この観点から、僕はKDEでのファイルのドラッグ&ドロップの操作は「馬鹿丁寧すぎ」の方に入るのかなーと思っています。Linuxのデスクトップ環境の1つであるKDEでは、ファイルを別の位置にマウスの左ボタンでドラッグ&ドロップすると、必ずメニューが出てきて「コピー」「移動」みたいなその時可能な操作を選べるようになっています。これ、最初はいいなって僕自身思ってたんですが、使ってるとだんだんうざく感じるようになってしまいました。「上の階層のフォルダにドロップしたんだから、コピーじゃなくて移動したいに決まっとるがな!」「新しくフォルダ作ってそこにドロップしたんだから、移動したいに決まっとるがな!」と、自分の場合は少なくとも、ファイルのドラッグ&ドロップは「移動」のためにやる場合が圧倒的に多かったんですね。それなのに毎回「コピーですか? 移動ですか?」って訊かれる。これがそんなにストレスになるとは、実際使い続けてみるまで僕には分かりませんでした。

なので、セカンドサーチは何も検索エンジンを選ばなければ既定の検索エンジンで検索しますし、マルチプルタブハンドラも、「グッ」と押し込む感じで敢えて長くタブの上でボタンを押し続けない限りはタブの選択操作が始まらないようになっています(このタブ選択操作の挙動はユーザからの提案で実装したものですが、採用して大正解だったと思ってます)。

ユーザの「言う事」に振り回されない・惑わされない

「ユーザからの提案を取り入れて正解だった」と書いた直後に述べるのも何ですが、ユーザの提案を何でもかんでも呑んでしまうのは避けた方がいいと僕は思ってます。

ユーザは基本的に自分の不満を素直に言ってくるもので、その提案を取り込んだ結果他のユーザにどういう影響があるかとか、実装がどう変わるかとか、そんな事までは考えてないと思っていいと思います。そういう提案をどんどん取り込んでしまうと、最初に定めたはずの「これは何をするためのソフトウェアだ」というゴール設定がブレてしまう事にもなりかねません。なので、僕はユーザからの提案に対しては、最初に決めたコンセプトに沿っているか・既存の物を壊してしまわないかという事をなるべく考えて、折り合いが付かなければ「それは取り込めない」と回答するようにしています。

タブブラウズの機能を何でもかんでも拡張するTBEというアドオンを作っていた時は、僕はわりと何でも提案を取り込んでしまってましたが、その結果どうなったかというと、ソフトウェアが肥大化するわUIが複雑化するわコードがごちゃごちゃになるわで散々でした。これは、当時の僕が名誉欲だとか人に感謝されたい欲だとかに突き動かされていたからという理由だけでなく、ソフトウェアのコンセプトが曖昧だったので、何を入れて何を外せばいいのかという判断がそもそもできなかったせいでもあるんじゃないかと、今では思ってます。

コンセプトがはっきりしていると、寄せられた意見を取捨選択しやすいです。意見を退ける時も、「それはコンセプトに合わないから」とはっきり言える。好き嫌いだとか意地悪だとかで言ってるんじゃないという、ある意味では言い訳を自分に対してできるんですね。八方美人タイプだと人の頼みを断る事自体がストレスになってしまいますが、こうすれば罪悪感を感じなくて済みます。

ということでNOと言う事の大事さを散々説いたのですが、かといって頑なになりすぎるのも良くないです。ユーザ1人だけが言ってきているのならまだしも、同じ要望・不満が別々のユーザ10人から飛んでくるようだと、これはユーザの使い方ではなくてUIの方が(ひょっとしたらコンセプトも)悪いという可能性を疑った方がいいでしょう。「なんでみんな理解してくれないんだ、こんなに素晴らしい物なのに!!」という思い上がりに囚われてしまってはいけません。

あと、意見を取り入れる時も、取り入れ方は工夫する必要があると思います。例えば「ここにこういうボタンが欲しい」という要望が来た時に、本当にそのまま言われた通りにボタンを追加する前に、なんでそこにボタンが欲しくなったんだろうかという事を考えるという事です。そこで考えた結果、やっぱりボタンを追加した方がいいという結論に辿り着くかも知れませんが、ボタンを追加しなくても別の方法で解決できるという事が分かるかも知れませんし、コンセプトの見直しを図るきっかけになるかも知れません。

余談ですが、Microsoftは製品を開発する時に、ユーザの言葉を聞くんじゃなくて、ユーザがその製品をどう使おうとしたかという様子を観察して製品にフィードバックするという事をやっている、という話を以前にどこかで読みました。ユーザ自身が自分の要望や不満を100%正しく言語化できるわけではない、ユーザ自身が気付いていない問題や視点があるのかも知れない、恥ずかしくて嘘をついているかも知れない、だから行動を見る……という話だったと思います。

Microsoftと同じ事をやるにはお金も手間もかかるのでそのままマネするのは難しいですが、そういう考え方は持っておいていいと思います。

セオリー

僕がここで述べた事は、多分UIのデザインのセオリーと言われるような事ばかりです。名前を挙げた自作のアドオンも、最初からそういう理屈を考えて作ったというよりも、試行錯誤してるうちに辿り着いた結果を後から分析してみたら案外セオリーに則っていた、という感じです。もっとちゃんと体系立ててUIのデザインの理論を勉強してから臨んでいたら、もっと少ない労力で答えに辿り着けたのかも知れないなあと思っています。

セオリーにはセオリーと呼ばれるだけの理由がちゃんとあると思いますので、我流で突っ走る前に一応は基本を抑えておくといいんじゃないでしょうか。

実装上の工夫やテクニック、コンセプトを具現化する時の注意点

プログラムとしての作りやすさに引きずられない

人にやさしい・使いやすいUIを実現するためには、プログラムとしては複雑な物になってしまいがちです。逆に、プログラムとして簡単な物にしようと思うと、エンドユーザにとっての使いやすさが犠牲になってしまいがちです。

「Now Loading…」系の物なんかは、その代表例ですよね。プログラムを作る側としては、必要なデータが全部揃うまで待ってから最後に一気に処理した方が、コードとしてはシンプルに書けます。しかし、これではエンドユーザは待たされている間何もできません。今では考えられない事でしょうが、昔のブラウザもまさにそういう設計でした。ページの内容を全部インターネット経由でダウンロードしてくるまで、ページの描画をしてくれなかったのです。

今のブラウザはそんなことは無いですよね。読み込みが少しずつ進行するに従って、ダイナミックに表示を更新して、現在までに読み込みが完了した部分から順次表示してくれます。多少作るのが面倒であっても、こういう風な設計を心がける事はとても大事だと思います。

ただ、そのようにユーザにとっての使いやすさを追求するために、プログラムとしてのコードの綺麗さを犠牲にしすぎるのも良くないです。コードの見通しが悪くなってしまうと、いざ挙動がおかしい所を見つけたとしても、どこをどのように直せば良いのか見当が付かなかったり、ある所を変更したせいで別の所に意図しない影響が発生してしまったりするため、それ以上コードに変更を加えられない(ちょっとでも変更したらぶっ壊れてしまう)、みたいな事になってしまいます。

ユーザにとっての使いやすさを大事にする事と、プログラムとしての見通しの良さを高く保ってメンテナンスしやすくしておく事は、頑張り次第で両立できます。そのためのノウハウを、ここで少しだけご紹介しようと思います。

Firefoxのアドオン開発における、避けるべきパターン

Firefoxのアドオン開発では、XMLとJavaScriptの組み合わせで「要素の振る舞い」を定義しておくXBLという技術によって「独自のタグ」のようなものを定義できます。しかし、メンテナンス性を高く保つという観点からは、XBLの利用はあまりお勧めできません。使い所をわきまえればXBLはそれなりに便利な技術ではあるのですが、「Firefoxのアドオン」の開発においては、便利さよりもデメリットの方が大きいと僕は思っています。

実際、僕は上述したアドオン「Fox Splitter」の旧バージョンをXBLを多用して開発してきましたが、XBLのせいで全体として見通しが悪くなってしまっていたり、開発時の構成に強く依存したコードがJavaScriptとXBLに散らばっていたせいで調べなければいけないコードの範囲が無駄に広がってしまったりしていたせいで、ドツボにハマってもはや自力ではメンテナンスできなくなってしまい、1年半もの間完全に放置することになってしまいました。

前述した話ともかぶりますが、不必要な多機能化も避けた方がいいです。ある機能Aと別の機能Bとで共通の処理があるから、AとBと共通の処理を1つのアドオンにまとめておこう、という風な考えに至るのはごく自然な発想だと思いますが、しかしAとBがそれぞれ全然関係の無い機能なのであれば、共通の処理を2回書く事になったとしても、AとBは別々のアドオンとして分けて開発する事を僕は強くお勧めします。

今はAとBで共通の処理を使っているとしても、Aの機能とBの機能のそれぞれのコンセプトを突き詰めていくと、共通の処理にできる部分はほとんどなくなってしまうかもしれません。にもかかわらず、「AとBとそれらの共通部分」という構成のコードがあると、この構成を維持する事に発想が囚われてしまって、AもBもコンセプト的に中途半端なままになってしまうのではないでしょうか。別々のアドオンに分けて開発していれば、そういう風に今ある実装に引きずられないで、コンセプトを突き詰めた開発をしやすいと僕は思っています。

他に思いつく事としては、ありきたりですが、プログラミングにおいて一般的なアンチパターン(これはマネしちゃいけない、という反面教師的なパターン)を避ける事がやはり大事だと思います。

例えば、1つのクラスに2つも3つも関係のない機能をまとめてしまうと、これは先ほど述べた「アドオンの多機能化」と同じような問題に繋がりかねません。それを避けるには、個々のクラスの実装が受け持つ機能の範囲を定める形で、実装を適切な粒度に分けておく事が大事です。

また、3項演算子や、論理演算子の優先順位を逆手に取ったプログラミングなどのトリッキーな書き方も、あまり多用するべきではないです。トリッキーなコードは芸術的で見ていて美しいものですが、あまりに芸術的すぎるコードは、非常に限定された条件下でしか正しく動かず、手を少し加えただけで動かなくなってしまう事もあったりして、その後の機能拡張やメンテナンスに対して脆弱になってしまいます。芸術品を作るのではなくて、実用品を作ろうとしているのだ、という事を常に念頭に置いておく事を僕はお勧めしたいです。

1年後の自分が読んでも意味が分かるようなコードにする事を心がける

1つのプログラムを1人で集中して開発している時は、プログラムの全体像が頭に入っていますから、多少読みにくいコードでも開発する事ができます。

しかし、誰かに手伝ってもらうとしたらどうでしょうか。あるいは、1年間そのプログラムの事を全く考えずにいて、1年後に急に開発を再開する事になったとしたら(1年後の自分なんて、はっきり言って他人も同然です)。読みにくいコードになっていると、「この関数は何のための物なんだろう……」とか「なんでこんな設計になっているんだろう……」といった感じで、全体像を把握できなくて何も手出しできなくなってしまいます。また、そういう状態で不用意に既存のコードを書き換えると、思わぬ副作用が発生して、今まで動いていたものが全く動かなくなってしまうかも知れません。これでは開発になりません。

前述した「1つのクラスに2つも3つも機能が入ってしまっているコード」や「あまりに芸術的すぎるコード」も、そういう問題を引き起こしがちです。そういった問題を避けるための、「1年後の自分が読んでも分かるようなコードにするテクニック」をいくつか紹介しましょう。

変数名やメソッド名、クラス名を分かりやすくする

変数やメソッド、クラスの名前は、それが何を表しているのか・何をするための物なのかをきちんと表す物にしましょう。例えば以下のようなコードは最悪ですね。

var bool = true; // 変数名が「真偽値である事」だけしか表していない

function f() { ... } // 関数に適切な名前が付いていない

これは、以下のような書き方をした方がいいです。(これはあくまで例です。表す内容がこれとは違う場面であれば、その都度適切な名前を付けましょう。)

var enabled = true; // 変数名から「何かの機能が有効か無効かを示すものだ」という事が分かる

function openTabAndInitialize() { ... } // 関数名が処理の内容を表している

ただ、名前に動詞や形容詞、前置詞などが2つも3つも入ってくるようだと、いくら処理の内容をきちんと言い表していても駄目です。関数や変数に簡潔明瞭な名前を付けられないのは、そもそも設計が悪いからと考えられます。きちんと設計してあれば、関数や変数には大抵の場合、ちゃんと簡潔明瞭な名前を付けられるのです。ですから、この例のopenTabAndInitialize()だと以下のように設計し直す事になるでしょう。

function openTab() { // 2つの小さな処理単位からなる、1つの大きな処理単位
  var tab = openNewTab();
  initializeTab(tab);
}

function openNewTab() { ... } // 新しいタブを開く、という1つの小さな処理単位

function initializeTab(aTab) { ... } // タブを初期化する、という1つの小さな処理単位

Rubyというプログラミング言語の開発者のMatz氏の言う「名前重要」というスローガンは、この事を指しています。変数や関数に名前を付ける時はちゃんと考えてから付けないと駄目で、名前付けに困る時は設計を見直す事も視野に入れるべき、という事ですね。

データに意味を持たせない。

データを表す時に、配列の1個目の要素が名前で、2個目の要素がデータの数を表して、3個目の要素がアイコンのサイズを表して……という風な作りにしてしまう事があります。

var data = ["foo", 11, 32];

こういうデータの設計はやってはいけません。「何番目のデータがこういう意味である」という風な、データの順番などがそのまま意味を表すような設計だと、順番をいちいち細かく覚えてられませんから、うっかりミスで間違えてしまいやすいです。また、こういうデータの設計だと、データ構造も変えにくいです(最後にどんどん新しい情報を付け足すというような、やっつけ仕事の解決策しか取れない)。

こういう物は、連想配列とかハッシュとか言われる形式でデータを表現する方がよいです。

var session = {
      title     : "foo",
      tabsCount : 11,
      size      : 32
    };

連想配列やハッシュであれば、ハッシュのキーがその値の意味を表すメタ情報になります

引数の多い関数を作らない

1つ前の話と同じ事なのですが、以下のように関数の引数が3つも4つもあるような関数は作るべきではないです。関数を呼び出す時に、何番目にどの情報を渡せば良いのかを間違えやすくなってしまいます。

function load(uri, title, size,
              referrer, ...)

こういうものは、ハッシュを1つだけ引数に取り、そのハッシュの値が個々のパラメータを表すという設計にしておきましょう。

load({
  title    : "foo",
  uri      : "http://...",
  size     : 32,
  referrer : null
})

このようになっていれば、引数の順番を意識しなくて済みます。

自動テストを作っておく

自動テストとは、ある関数や機能について「どういう呼び出し方をした時に、どういう結果が返ってくるか」という事を確認するためのプログラムです。具体的には、以下のような「テスト関数」が沢山連なったものが「自動テスト」というプログラムになります。

function test_getMessage() {
  // 実装を実際に呼び出してみる
  var result = getMessage();
  // 返り値は期待値と一致しているか?
  assert.equal('expected message', result);
}

ある機能Aがあって、そのAに依存するBという機能があって、Bに依存するCという機能がある時に、Cが正しく動かなかったら、さてどこに問題があると考えれば良いでしょうか。Aが壊れているのかも知れないし、Bが壊れているかも知れないし、Cが壊れているのかも知れない、このままだと何が原因なのかは分かりません。

しかし、機能AとBは自動テストでちゃんと期待通りの動作をする事を保証されていて、今まさにCを開発している場面なのだとしたら、これはCに問題がある可能性が高いと言えます。このように、自動テストがある事で複雑なプログラムであっても一歩一歩着実に開発を進められるのです。

また、自動テストは、その関数や機能が期待通りに動くかどうかを検証するものであると同時に、その機能はどのようにして使う物なのか、どういう結果が返ってくる物なのか、といった事の例にもなります。

遅延処理、非同期処理

プログラムは、上から順に1行ずつ読んで処理の流れを把握できるようになっている物ばかりではありません。ユーザの入力を待ってから次の処理を行うとか、読み込みが完了するまでの間別の処理を走らせておいて読み込みが完了したら画面を切り替えるとか、このような「人にやさしい挙動」を実現しようと思うと、上から順に1行ずつ読んだのでは処理の流れを追う事ができないような、分かりにくいコードになってしまう事を避けられません。

Firefoxのアドオン開発の場合は言語はJavaScriptを使う事になりますが、JavaScriptでは非同期処理や遅延処理が関係する場面だとコールバック関数(ある処理を呼び出す際に、その処理が終わった後で実行されるべき処理を関数として定義して渡すという物)を使わざるを得ません。そのような処理を2つも3つも連携させようとすると、コールバック関数を何十にも重ねる事になってしまいがちです。

例えば「新しいウィンドウを開いて、そのウィンドウの読み込みが完了したらコールバック関数を実行する」という例は以下のように書けます。

function openAndWait(aCallback) {
  var win = window.open(...);
  win.addEventListener('load', function() {
    win.removeEventListener('load', arguments.callee, false);
    aCallback(win);
  }, false);
}

これを使って「ウィンドウを1つずつ500ミリ秒おきに順番に3つ開いていく」という処理を書こうとすると、以下のようにコールバック関数をどんどんネストしていかないといけません。

openAndWait(function(aWindow) {
  aWindow.move(...);
  window.setTimeout(function() {
    openAndWait(function(aWindow) {
      aWindow.move(...);
      window.setTimeout(function() {
        openAndWait(function(aWindow) {
          aWindow.move(...);
          ...
        });
      }, 500);
    });
  }, 500);
});

2階層や3階層ならまだしも、これが10階層や20階層も重なると、インデント幅が増えすぎてワケが分からなくなってしまいます。また、開き括弧と閉じ括弧の間に沢山のコードが入るため、コード全体の見通しも悪くなります。

このような問題に対処するための機能が、jQueryにはdeferredという名前で搭載されていますし、同様の機能だけを提供するJSDeferredという軽量なライブラリもあります。ここではJSDeferredの使い方を少しだけ解説したいと思います。

JSDeferred

先の openAndWait() という関数は、ウィンドウの読み込みが終わった後に次の処理としてコールバック関数を実行する物でした。これをJSDeferredを使って書き換えてみた物が、以下の例です。

function openAndWait() {
  // (1) Deferredクラスのインスタンス(Deferredオブジェクト)を作る。
  var deferred = new Deferred();

  var win = window.open(...);
  win.addEventListener('load', function() {
    win.removeEventListener('load', arguments.callee, false);
    // (3) 読み込みが終わったら、返したDeferredオブジェクトの
    //     call()メソッドを呼ぶ。
    deferred.call(win);
  }, false);

  // (2) Deferredオブジェクトを返り値として返す。
  return deferred;
}

このように書き換えた関数は、以下のようにして「次の処理」を記述できるようになります。

openAndWait()
  .next(function(aWindow) {
    aWindow.moveTo(...);
  });

それどころか、さらにその先にもまだまだ「次の処理」を続けて記述できます。

openAndWait()
  .next(function(aWindow) {
    // 読み込みが終わったら実行
    aWindow.moveTo(...);
  })
  .wait(0.5) // 500ミリ秒待つ
  .next(function() {
    // 次のウィンドウを開く
    return openAndWait();
  })
  .next(function(aWindow) {
    // 読み込みが終わったら実行
    aWindow.moveTo();
  })
  .wait(0.5) // 500ミリ秒待つ
  ..

JSDeferredを使うと、何個も処理を重ねる場合でもコード上のネストがあまり深くなりません。また、コールバック関数を重ねる場合と異なり、コードを読む時に上と下を行ったり来たりする必要もありません。コールバック関数を1個目の引数に受け取るのか2個目の引数に受け取るのか、といったインターフェースの違いも意識しなくて済みます(全ての非同期処理を統一的なインターフェースで利用できるようになる、とも言えます)。

詳しい事はJSDeferred開発者のcho45氏自身による解説を読んで下さい。JSDeferredを使いこなせるようになると、表現の幅がぐっと広がります。

先に名前を挙げたアドオンFox Splitterも、JSDeferredに強く依存しています。Fox Splitterは新しいウィンドウを開いて位置や大きさを制御するという事をやっていますが、ウィンドウが開かれるのを待って次の処理に進んだり、ウィンドウの位置が確定するのを待って他のウィンドウの位置を調整したり、といった感じで「前の処理の完了を待ってから次の処理を行う」場面が非常に多いです。僕はJSDeferredのおかげで、そういった処理の前後関係を意識しないで複数の機能と機能を連携できるようになったために、Fox Splitterを無事完成させることができました。

前述した「ユーザの入力を一旦受け取って、入力が完了した後に次に進む」というような事も、JSDeferredを使えば比較的簡単に処理を記述できるでしょう。人にやさしいUIを作る上で、JSDeferredのようなライブラリは非常に役立ちます。

とりあえず作ってみるという事

UIのアイディアを思いついたら、あれこれ計画を練って検討するのもいいですが、さっさと実際にプロトタイプを作ってみることを僕はお勧めしたいです。

コンセプトを形にする事は重要です。形にして実際使ってみて初めて分かる事もあります。プランを練っている時には「素晴らしい」と思っていても、実際使ってみると「アレ?」って事はよくあります。手早くプロトタイプを形にして、コンセプトが正しかったかどうかを初期段階で確認して軌道修正をするのが、後で痛い目を見ないで済むようにするコツです。問題点の発覚が遅れれば遅れるほど、取り返しが付きにくいです。

ブラウザのアドオンは、そういう意味では格好の実験場だと僕は思っています。

これは、大学の卒業制作でMozillaの拡張機能として「こんなUIがあってもいいんじゃないか?」という物を作った時に考えた理屈なのですが、ブラウザのアドオンとしてプロトタイプを作る事にはいくつかのメリットがあります。

  • 既にあるソフトウェア資産を流用できる。いちから自分でブラウザを作るのは大変だが、アドオンとして作るのなら、ネットワークの通信だとかそういった部分を考えなくても済む。
  • ユーザに使ってもらう時の障壁を低くできる。今まで使ってきたWebブラウザの延長線上として、全く新しい別の物を使い始めるのに比べれば少ない学習コストで使ってもらえる。

Firefoxのアドオンの場合、ソースコードがオープンソースなライセンスで公開されている場合が多いというのもいい所です。分からない所があれば、やりたい事に近い事をやっているソフトウェアのソースコードをそのまま参考資料として参照できますし、ライセンスが定める条件に従ってさえいれば、そのままソースコードを流用する事すらできます。

ちなみに、「オープンソースの物を流用したら、その成果もいきなり全世界に公表しないといけないんでしょ? それじゃ秘密裏に実験的な事ができないじゃないか」という風に思う人がいるかもしれませんが、それは誤解です。例えばGPLなどのライセンスでは、ソースコードを流用して作ったソフトウェアのユーザに対してソースコードを開示する義務はあっても、ソフトウェアを渡されてもいなければ存在も知らないような人相手にまでソースコードを開示する義務はありません。安心して下さい。

デバイスの進歩について

質疑応答で少し話しましたが、ソフトウェアのUIとハードウェアの進歩は切っても切れない関係にあると思います。

「複雑なマウスジェスチャは覚えられないから使い物にならない、シンプルな動き以外はまともに使えない」という風な事を前述しましたが、MacやiPhone、iPadで可能な2本指・3本指でのジェスチャ(マルチタッチ)は、その制限を乗り越える技術です。指2本を開くような動き(ピンチアウト)で画面の拡大、指2本を閉じるような動き(ピンチイン)で画面の縮小、という風に、簡単でありながら表現力豊かなジェスチャ操作が可能です。これはハードウェア側のサポート無しには成り立たないUIです。

Panoramaはモーダルなせいでいまいち使いづらいですが、これはPCの画面が狭いという現在のデバイスの制約による所が大きいと僕は思っています。もしPCの画面が物凄く広ければ、あるいは、画面の外に空中に映像を投影するような技術があれば、Panoramaはモーダルにしないで常時表示しておけるかもしれません。

今あるハードウェアの限界の中で使いやすいUIを考える事も大切ですが、できない事はやっぱりできないので、そこは潔く諦めてハードウェアの進歩を待つといった判断も必要では無いかと僕は思います。あるいは、ハードウェアの制限に合わせる形でもっと別のUIを考えてみるとか。いずれにしても、今あるハードウェアの上では使いにくいと言わざるを得ない物を「理論的には使いやすいはずなんだ」みたいな感じでごり押しするような事はしないで、柔軟に考えるようにして下さい。

ターゲットユーザを誰にするのかという割り切り

僕の場合は、今は自分自身が第1のターゲットユーザだと考えています。自分自身が常用するものだからこそ、クオリティを維持して快適な状態を保ちたいと思える。というのが今の僕の考えです。

自分以外の人をターゲットユーザに想定するのもありだとは思いますが、僕の場合は既に20を超えるアドオンを開発していて、それぞれにバグ報告が来て修正のためにてんてこ舞いになっているというのが実情で、自分で使う物についてすら十分なメンテナンスをできていない状態にあります。自分自身を第1のターゲットユーザとして考えるというのは、コンセプトをブレさせないための方法論でもありますが、ユーザから寄せられる大量の要望から距離を置いて自分の身(精神)を守るための手段でもあるんですね。

誰をターゲットにする場合であっても、基本的な所はセオリー通りにやる事が大事なのだと僕は思っています。その上で、例えばお年寄りのための物にするなら文字を大きくするとか、使う言葉を平易にするとか、応用を組み合わせていく事になるのだ……と、僕は思っています。

道具を自分に合わせないと我慢できない人と、道具に自分を合わせるのが苦にならない人がいる(11時20分追記)

カスタマイズ性の高いブラウザだからみんなカスタマイズしたくなるかというと、そうとも言えません。統計として、Firefoxのユーザの全員がアドオンを使っている訳ではなく、また、アドオンを使っている人でもインストールしているアドオンの数は片手で数えられる程度である場合がほとんどだというデータが、過去のMozillaの調査で出ていました。カスタマイズ性の高い設計になっている事と、実際にカスタマイズして使われる事とは、あくまで別の事なんですね。(この調査結果は、Firefoxの初期状態が普通の人にそれなりに使いやすい形になっている事の顕れと言えるとも思います。)

僕自身は、ペンタブレット(ペン型の入力装置を使って、ペンで字や絵を描くのと同じ感覚で入力を行えるデバイス)を使う時に、指に当たって痛いなーと思ったらペンの軸に何か巻いてみたりプロテクターのような物を作って付けてみたりと、DIYで「カスタマイズ」して使っています。漫画家の人の中には、ペンの軸が長すぎるからとノコギリでぶった切って使っている人もいるそうです。

でも、そういう風にしない人もいるんですよね。使いやすいと思えるありものの製品を別に探したり、使いにくいから使うのをやめてしまったり。そもそも、自分が使いやすいと思うようにカスタマイズするという発想が無いという人もいるのだと思います。

僕自身、あまり拘りを持っていない分野ではそういう行動を取っている気がします。僕は料理をする事が好きで好きでたまらないという人ではないので、調理器具は買ってきた物をそのまま使っていますし、少々使いにくいと思っても面倒だからそのまま放置しています。カスタマイズしてでも使い続けるかどうかは、「なんでもカスタマイズしたがる性格」の有無以外にも、使いにくさを乗り越えてでもそれを使い続けるに足る動機があるのかどうか、どれだけそれを切実に必要としているのか、という事も影響しているのかもしれません。

そう考えると、カスタマイズ性を高くしておきさえすれば万事オッケーとは言えないのだと思います。僕自身、車や自転車にそれほど強い関心があるわけではないので、「タイヤをこれに替えるともっと快適だよ」と言われても「ふーん、そうなんだ。まあ面倒だから僕はそのまま使い続けますよ。」って思ってしまいますし、そのまま使い続けるのが辛いくらいに合わなければ、カスタマイズを検討する前に他の車や自転車に乗り換えてしまうのではないかと思います。同じように、Webを見る事に対してそれほど切実な動機を持っていない人に「カスタマイズすればもっと使いやすくなるよ」と言っても、「面倒だし別にいいよ」って多分言われてしまうでしょうし、その人が「なんかこれ使いにくい」って言ってる時に「カスタマイズすれば解消できるよ」と言っても、「自分で手を入れなきゃ使いやすくならないの? そうなんだったらもう最初から使わないでいいや……もっと別の物を探すよ」と言われてしまうと思います。

ほとんどの「ユーザ」はそういう物だ、と僕は考えるようにしています。だからこそ、デフォルト設定を使いやすくした方がいいとか、初見で使えるようにした方がいいとか、そういう事を言ってるんですね。

ブラウザのアドオン、である理由

今プログラマ的にアツい分野というとHTML5とかWebサービスとかクラウドとかっていう話になると思うんですが、僕がWeb開発じゃなくブラウザのアドオンの開発をやり続けてる理由は、インターフェースとして、より人に近い部分で動く物を作れるからなんだと思います。

ユーザの何気ない無意識での入力をトリガーにして適切な次の処理をサジェストするというのは、普通のWebページやWebアプリの枠を超えた部分の話です。サジェストされる選択肢の1つとしてWebサービスに誘導するのはもちろんアリだと思いますが、起点になるのは特定のWebサービスの画面ではなくて、ブラウザのUIだったりスマートフォンのホーム画面だったりという、もっとユーザに近い部分になるはずです。

そこの所への関心が強いから、僕はWebアプリよりもアドオンを作るのが好きなんでしょうね。

nsIFocusManagerを使ってウィンドウを最前面に持ってくる方法 - Jun 24, 2011

Firefox上でいくつかのサブウィンドウを開いていて、それらのウィンドウすべてがワンセットで他のウィンドウより前に出てきていて欲しい場面、というのがある。

例えば、GIMPはツールボックス等が複数のウィンドウにばらけている。これがもし、画像を編集するウィンドウにフォーカスしたらツールボックスのウィンドウがその背後に隠れてしまうという風になっていると、まるで作業にならないだろう(古いバージョンのGIMPをWindowsで使った時にはそんな風になってて頭を抱えた記憶がある)。

また、そういうワンセットで表示されていて欲しいウィンドウ達が、同時に起動している他のアプリケーションのウィンドウの前と後ろにそれぞればらけてしまうというのも、使う時に地味にうざい。

Firefoxの上で、拡張機能が開くウィンドウでGIMPのウィンドウ群のような振る舞いをさせるにはどうすればよいのか。このエントリでは2つの方法を紹介する。

まず1つ目。nsIXULWindowインターフェースのzLevelプロパティを使うと、Firefoxのプロセスが開くウィンドウの重ね合わせの順序をある程度制御する事ができる。一番単純なやり方は、ウィンドウを一時的に最前面表示に切り替えて、その後すぐに元に戻す、という方法だろう。

var XULWindow = window
                .top
                .QueryInterface(Ci.nsIInterfaceRequestor)
                .getInterface(Ci.nsIWebNavigation)
                .QueryInterface(Ci.nsIDocShellTreeItem)
                .treeOwner
                .QueryInterface(Ci.nsIInterfaceRequestor)
                .getInterface(Ci.nsIXULWindow);

var originalZIndex = XULWindow.zLevel;
XULWindow.zLevel = Ci.nsIXULWindow.highestZ;
window.setTimeout(function() {
    XULWindow.zLevel = originalZIndex;
}, 0);

ただ、仕様上の制限のため、この方法はWindowsでしか使えない。少なくともUbuntu 10.04のGnomeでは機能しなかった。

代わりに考えられるもう1つのやり方が、フォーカスを使うやり方だ。単純に考えても、window.focus()でウィンドウにフォーカスを与えると、強制的にそのウィンドウを最前面に持ってくる事ができる。

しかしこの方法にも問題がある。普通にwindow.focus()すると、例えばそのウィンドウの中の特定のテキストボックスにフォーカスしていたとしても、そのフォーカスが失われてしまう事になる。

この問題を回避するには、Gecko 1.9.2/Firefox 3.6から導入されたフォーカスマネージャを使わないといけない。具体的には以下のようにする。

var FocusManager = Cc['@mozilla.org/focus-manager;1']
                   .getService(Ci.nsIFocusManager);

// 現在フォーカスされている要素を取得する。
// 第1引数:検索する最上位のDOMWindow
// 第2引数:再帰的な検索を行うかどうかのフラグ(trueを渡す)
// 第3引数:その要素が含まれているフレームのDOMWindowを
//          受け取るためのスロットとなるオブジェクト
var focusedWindow = {};
var focusedElement = FocusManager.getFocusedElementForWindow(window, true, focusedWindow);

// 現在のフォーカスが何によって与えられたかの情報を取得する。
var reason = FocusManager.getLastFocusMethod(focusedWindow.value);

// フレームにフォーカスする。
focusedWindow.value.focus();

// フォーカスされていた要素がある時は、その要素にもフォーカスする。
if (focusedElement) {
    // フォーカスを与える時に、スクロール状態等に変更を加えないように指定する
    let flags = Ci.nsIFocusManager.FLAG_RAISE |
                Ci.nsIFocusManager.FLAG_NOSCROLL |
                Ci.nsIFocusManager.FLAG_NOSWITCHFRAME |
                reason;
    FocusManager.setFocus(focusedElement, flags);
}

こうすれば、Linuxでも要素のフォーカス状態を失わせずにウィンドウを最前面に持って来れる。ウィンドウのフォーカスを動的に切り替えるため、その都度各ウィンドウが画面上でぺかぺか点滅してしまう(一瞬だけフォーカスされて、その直後にフォーカスが失われるため)、というデメリットはあるが。

XPIファイルのパッケージングの仕方を工夫してもあんまり報われないという事が分かった(僕の場合は) - May 30, 2011

先に結論だけ。

  • アドオンの規模にもよるだろうけど、僕の場合、XPIパッケージの作り方の工夫は大したパフォーマンスの向上には繋がりませんでした。
  • アドオンをインストールした時にFirefoxの起動がどれくらい遅くなるかのパフォーマンス計測の仕組みをMozillaが用意してくれているので、みんなそれを使うといいです。

Firefoxの起動を遅くするアドオンランキングなんてものが公開されて、そこにツリー型タブも堂々ランクインしてて、「多機能オールインワン型アドオンを散々批判してるくせに単機能とか言ってるお前のアドオンの方がよっぽど重てーじゃねえかプゲラ」的なコメントも見受けられる昨今(僕はメンテナンス性とか共存のしやすさとかの観点から多機能型を否定しているつもりなので、ある単機能のアドオンが必ずある多機能のアドオンより軽いはずだとかそんな事は言えないと思っているのですが)、皆様いかがお過ごしでしょうか。

自分はわりと富豪的なプログラムを書く方なので、欲しい機能を実現させるためにいろんなイベントを監視したり、メンテナンスしやすくするためにファイルを細かく分けたり、とかそういう事をやりがちなのですけれども、そういうのって一般的にFirefoxの起動とか動作とかを遅くさせるからやめなさいよというメッセージを、Mozillaは最近アドオン開発者に向けてよく発しています。上記のランキングもその一環ですね(あんまり遅いとこうして晒し挙げるぜ、という)。

で、その晒し挙げランキングのページからパフォーマンス改善の色々なテクニックがリンクされていて、これ読んで出直して来いよ、出直してこないならペナルティだぜ、という話なんですが、そのページの末尾でTalosというパフォーマンス測定ツールを使ってアドオンがFirefoxの起動速度に与える影響を計測する手順も紹介されてました。上記の晒し上げランキングもこれで計測した結果に基づいてるみたいです。

晒し上げランキングの中でツリー型タブがWindows XPで99% Slow(Firefoxの起動にかかる時間が2倍になる)と書かれてて、いくら僕でもちょっと信じがたかったので、実際に自分でもTalosを使ってパフォーマンス計測を試みてみました。僕が試験用に用意したWindows XP環境では、ツリー型タブ0.11.2011050602とNightlyとの組み合わせで35%前後という数値が出ていますが、これはランキングに出ている数値と大きく隔たりがあります。どういうことなんでしょうね。

まあそれはどうでもいいんですよこの際。とにかくこれが契機となって、自分の環境でもアドオンがFirefoxの起動速度に与える影響を機械的に測定できるようになったので、今後のパフォーマンス改善にはこれを1つの指標として使っていこうと思ってるわけです。


さて、そういう背景があってツリー型タブのリポジトリ上の最新版では、主要なコードをJavaScriptコードモジュール化してみたり、関連ライブラリの読み込みをXPCOMUtils.defineLazyGetter()でやるようにしてみたり(この機能はFirefox 3.6以降でしか使えないので、自動的にツリー型タブの次のリリースはFirefox 3.5対応を切り捨てる事になります)、読み込ませるスタイルシートの数を大幅に減らすようにしたり、ついでに属性セレクタの使用を減らしてみたり、という風な、アーキテクチャに変更が無い範囲で前述の「パフォーマンス改善のためのテクニック」を色々と取り込んでいるのですけれども、まだ実施していない項目の1つに「XPIパッケージの作り方を工夫する」という物がありました。

XPIパッケージというのは、アドオンを構成するファイル一式をZIP形式で圧縮した、配布用のパッケージです。Firefox 3.6までは、アドオンをインストールするとこのXPIパッケージが自動的に展開されて、ユーザのPCのストレージ上に個々のファイルが配置されるようになってました。ただ、ファイルの数が多いとFirefoxの起動に時間がかかるようになってしまうためとか諸々の理由から、XULファイルやCSSファイルやJavaScriptファイルなどはJARファイル(これも実態はZIP形式の書庫ファイルです)にまとめておいて、見かけ上のファイルアクセスを減らす事が推奨されていました。

JARファイルにせよXPIファイルにせよ、圧縮率を高くすればするほど中身を取り出すのには余計な時間がかかる事になります。なので、ユーザの環境にインストールされた時に1回だけ展開されるXPIファイルは圧縮率を最高にして保存して、インストールされた後も毎回中身を参照されるJARファイルは無圧縮で保存する、という事によって配布ファイルのサイズを小さくすると同時に実際の使用時のパフォーマンスも高く保つ……というのが従来のXPIファイルの作り方の定石でした。

ところがFirefox 4以降、パフォーマンス改善の一環として、Firefoxの構成ファイルは可能な限り1つのJARファイルにまとめられるようになりました。これは「omnijar」と呼ばれている新機能で、今まではバラバラのファイルとして配置されていたJavaScript製のXPCOMコンポーネントやJavaScriptコードモジュールまでも、他のXULファイルやCSSファイルのようにJARファイルの中に格納してしまって、更なるパフォーマンスの向上を実現しようというわけです。

Firefox 4本体がそうなったのに併せて、アドオンにもomnijarの仕組みが使われるようになりました。今までならXPIファイルをインストールしたらその中身を常に展開するようになっていたのが、Firefox 4でアドオンをインストールした場合は原則としてXPIファイルのままで保存されて、中身にはomnijarの仕組みでアクセスするようになったんですね。

ここで3つの問題が起こります。

  1. XPIの中にあるバイナリ形式のコンポーネントをそのまま使う事はできない。
  2. XPIファイルはインストール時に1回だけ展開される事を想定して最高の圧縮率にしている。毎回の起動時にその都度展開されると、その分余計な時間がかかる事になる。
  3. XPIファイルの中にJARファイルが格納されている、という入れ子構造のままで毎回使われる事になる。これもその分余計な時間がかかる事になる。

1については仕様上の制限という事で、こういう場合だけはFirefox 3.6の時と同様の形でXPIを展開した状態にしてインストールさせる必要があります。install.rdfにem:unpack="true"という指定を書き加えると、アドオンのインストール時にXPIファイルが展開されるようになります。

2と3はパフォーマンス低下の問題です。前述の「パフォーマンス改善のためのテクニック」では2と3を理由として、無圧縮JARに固めてから最高圧縮率のXPIを作るのではなく、中身をJARに固めないで全体を圧縮率の低いXPIに固める(アーカイブを入れ子にしない)、という事が推奨されています。

しかしこれは見ての通り、Firefox 3.6以前のバージョン向けの定石と全く相反しています。Firefox 4のためにFirefox 3.6でのパフォーマンスを犠牲にするか、Firefox 3.6のためにFirefox 4でのパフォーマンスを犠牲にするか、どちらかを選ばないといけないという事になります。


でも、ほんとにそうなの? Firefox本体くらいの規模ならいざ知らず、それより遙かに小規模なアドオンでXPIファイルの作り方がそんなに起動速度に大きな影響を与えるの?

という事が気になったので、ツリー型タブのリポジトリ上のHEADでパッケージングの仕方だけを変えてTalosでパフォーマンスを計測してみました。結果は以下の通りでした。

パッケージングの仕方 1回目の結果2回目の結果3回目の結果平均余計にかかった起動時間起動時間の増加率
(アドオン無し) 2029.53msec2091.58msec2058.53msec2059.88msec0-
無圧縮JAR+圧縮率最高XPI 2354.05msec2341.63msec2279.21msec2324.96msec265.08msec12.87%
無圧縮JAR+圧縮率最高XPI+em:unpack="true" 2327.0msec2314.84msec2337.63msec2326.49msec266.61msec12.94%
無圧縮XPI 2287.58msec2380.79msec2331.26msec2333.21msec273.33msec13.27%

数値が物凄く大きいのは、試験環境がシングルコアなCeleronの上で動いてるVirtualPCの上で動いてるWindows XP SP3だったからです。無圧縮XPIは上記の「Firefo 4向けの推奨されるやり方」ではないのですが、CPUが低速なら展開に時間がかからない方が有利なんじゃないかと思ってそうしてみました。

表を見ての通り、XPIの作り方で劇的に起動速度が変わるという事はありませんでした。少なくともツリー型タブ程度の規模では、XPIファイルの作り方の違いを変えてもFirefoxの起動速度は大して早くならない、ぶっちゃけ好きなようにやればいいという事が言えると思います。

ちなみに、繰り返しテストする前に試験実行した時にはそもそもFirefox自体の起動時間が600ミリ秒ほど短い1400ミリ秒ほどであるという結果が出ていました。その時の計測結果も以下に記しておきます。

パッケージングの仕方 テスト実行時の結果余計にかかった起動時間起動時間の増加率
(アドオン無し) 1411.26msec0-
無圧縮JAR+圧縮率最高XPI 1730.58msec319.32msec22.63%
無圧縮JAR+圧縮率最高XPI+em:unpack="true" 1718.05msec306.79msec21.74%
無圧縮XPI 1779.53msec368.27msec26.10%

これを見ると、ツリー型タブがある時に絶対的に何ミリ秒余計な時間がかかるのか?という点では、先の表と併せて見ても、だいたい常に300ミリ秒くらい起動に余計に時間がかかってるみたいだという事が分かります。Firefox自体の起動が300ミリ秒くらいで済む環境でテストすると、ツリー型タブのせいで起動時間が2倍かかるようになると考えられます。もしこれが事実なら、上記の晒し上げランキングの元データは結構高速なマシンで計測した物という事になるのかもしれません。


結論としては、XPIファイルの作り方を見直すなんてのは最後の最後でいいと思います。それよりもアーキテクチャの変更とかの方が多分ずっと高速化には効きます。実際、ツリー型タブのここまでの改善の中では、レイジーゲッターとかJSコードモジュール化とかよりも、読み込んだまま使ってなかった大量のスタイルシートを必要最小限だけ読み込むようにして@importを減らしたりセレクタを単純化したりした時の方が、大きな数値上の差が出てたと思います。

あと、このエントリ内での計測結果がおかしいと思う人は自分でPythonとTalosとNightlyを用意してパフォーマンス計測をやってみるといいです。こういう物が既にある以上、今後は、数値による裏付けが無い「遅い」「重い」といった類の話は無視しちゃっていいと思いますよ(動作時の速度についての言及は除く。上記の文書で解説されてる手順で計測できるのはあくまで起動にかかる時間だけで、メニューを展開したりタブを切り替えたりといった操作のパフォーマンスまでは測定してないので)。

Page 4/26: « 1 2 3 4 5 6 7 8 »

Powered by blosxom 2.0 + starter kit
Home

カテゴリ一覧

過去の記事

1999.2~2005.8

最近のコメント

最近のつぶやき