Posted by & filed under いろいろ.

このエントリーを含むはてなブックマークはてなブックマーク - JavaScriptとCSSを1ファイルに このエントリをつぶやくこのWebページのtweets この記事をクリップ!Livedoorクリップ - JavaScriptとCSSを1ファイルに Share on Tumblr Digg This

フロントエンドの最適化手法の1つにJavaScriptやCSSのファイルを1つに結合するというのがあるので、ついでにJavaScriptとCSSを1つファイルにまとめてみました。

非標準ですが大抵のJavaScriptエンジンの実装では<!–が1行コメントになることを利用してJavaScriptとCSSを切り替えてます。確認したとこIE8、Firefox33、Chrome37では意図通りに動作しました。IE11ではMIMEタイプが違うといわれてCSSが適用されませんでした。

思いつきなんで効果の程は知りません。


関連文書:

  • 関連文書は見つからんがな

Posted by & filed under 開発.

このエントリーを含むはてなブックマークはてなブックマーク - アリエルの開発を支える Trac このエントリをつぶやくこのWebページのtweets この記事をクリップ!Livedoorクリップ - アリエルの開発を支える Trac Share on Tumblr Digg This

先日 アリエルの開発を支える Trac プラグイン を書きましたが、今日は課題/バグ管理システムの Trac 本体について書きます。

Trac は Edgewall Software が開発した Web ベースの課題管理システムです。修正 BSD ライセンス、オープンソースとして提供されています。いまはどうなっているか分かりませんが、サイト上では Edgewall Software 自身も Trac のコンサルや商用サポートを行っているように書かれています。また、

edgewall.org is a place where a community of software developers collaborate on creating exciting open source software based on the Python programming language.

とあるので Trac をホスティングしているサイト自体は開発者向けに開かれた場所として提供されているようです。Trac Team の情報をみる限り、現在 Edgewall Software の開発者は積極的に関わっていないようです。

 

Trac 0.12.x から 1.0.x への移行

アリエルでは2013年12月頃に 1.0 系へ移行しました。もう半年以上も前のお話になるため、いまは解決済みの内容もあるかもしれませんが、実際に移行していて遭遇した問題などを紹介します。社内で Trac は業務における一次情報を提供するインフラになっていて、これが止まると業務への影響が大きいため、移行そのものに心理的に慎重になってしまっていました。

アリエルで移行したバージョンは、0.12.1ja から開発中の 1.0.2-trunk にしました。当初は 1.0.1 にしようと試みました。しかし、Genshi 0.7 との組み合わせで #11218#11261 のエラーが発生したため、当時の 1.0.2-trunk を使うようにしました。その時から 1.0.2-trunk が開発終盤だったのもありますが、移行を阻害するほどのトラブルはなく、結果的には 1.0.2-trunk を選択して良かったように思います。

このブログを書く前にリポジトリを更新して、いまは trac:r12928 で運用しています。

 

移行中に報告して直してもらったバグ

移行中に発生して Trac Team に直してもらったバグです。修正方法をみていると、Trac の開発チームは最も古いメンテナンスブランチ 0.12-stable にパッチを適用し、その後 1.0-stable, trunk にポーティングするというスタイルを取っているようです。1つのパッチから、一緒に3つのブランチを保守するのはなかなか大変なようにみえます。

DB に postgresql を使っていて、マクロがエラーになったときに postgresql がエラーメッセージとして日本語を含む str 文字列を返し、Genshi がそのエラー文字列を表示するときに Unicode 文字列を期待して UnicodeDecodeError が発生していました。うろ覚えですが、TicketQuery マクロで format を指定せずに col にチケットのカスタムフィールドを指定したときにマクロがエラーになったように思います。0.12 の頃は動いていたので 1.0.x で format 指定が必須になったのかもしれません。

NG: [[TicketQuery(...,col=summary|owner|custom-field)]]
OK: [[TicketQuery(...,format=table,col=summary|owner|custom-field)]]

この Genshi のエラーはなかなか致命的で、レポート一覧を開いたときに個々のレポートが評価されてエラーになっていました。レポート一覧にレポートが200以上あったため、どのレポートが原因かを突き止めるのが面倒でした。

これは Firefox のみに影響する不具合です。Trac はコメントの入力中の、キーボード入力が一定時間止まったときにプレビューを表示します。どうも Firefox は IME が有効なときに keydown や keypress のイベントを発生しないようです。Firefox で IME を切り替えて入力していると、入力中であってもプレビューが表示されて鬱陶しいといった不具合でした。

移行の全体を通して Genshi が関連するトラブルが多かったように思います。Genshi 自体の問題ではないのかもしれませんが、Genshi のバージョンと Trac のバージョンの組み合わせで発生する問題もありました。通知メールのテンプレートの改行コードに CRLF を使うと、送られてくるメールにバックスラッシュが混入します。Genshi-0.6.1 では再現しません。0.7 ブランチと trunk に修正パッチが取り込まれていますが、そのパッチを含む Genshi-0.7 がまだリリースされていないため、アリエルの環境では Genshi-0.6.1 を使っています。

 

移行中に遭遇した問題

ここではバグというわけではなく、アリエルでの Trac 利用においてのカスタマイズを紹介します。プラグイン同様、Trac 本体に適用しているパッチも https://github.com/arielnetworks/trac で公開します。それぞれの修正はブランチ単位で管理しています。

  • 通知メールの改行コードを CRLF に変換

EdMax というメーラーを使っている開発者がいて、このメーラーが LF を改行として認識してくれません。Trac の過去のチケット (#2191, #1139) をみると通知メールの改行コードに CRLF を使うように対応したように伺えたのですが、なぜか LF が含まれるユースケースがあるようです。再現方法を調べきれていないため、チケット登録はしていません (1.0-stable-replace-lf-to-crlf-notification) 。多くの開発者が使っている Thunderbird では再現しないのでどっちでも良い気もしますが、RFC 2046 によると CRLF を改行コードに使うのが正しいようです。

  • チケットの添付ファイルフィールドの開閉状態のデフォルト設定

チケットの添付ファイルのフィールドの折り畳みのデフォルトが変更されたようです。0.12 では折り畳まれず開いた状態でしたが、1.0 からは折り畳まれるようになったようです。開発者だけでなく、全従業員が Trac を使うのであまり慣れてない人が添付ファイルを見逃してしまうかもしれないという経緯から修正しました (1.0-stable-expand-attachments-in-ticket) 。trac.ini で設定できても良さそうにも思いますが、チケットを作るほどでもないかなと一時対応した状態です。

リポジトリ連携のコメントに更新したファイルを表示する機能拡張です。Trac のマイルストーン 1.1.3 で取り込まれる予定です (1.0-stable-add-config-option-show-changeset-files) 。もともとは TracChangeFileBiffPlugin を作ったとき、ついでにコメントから更新ファイルを一見して分かると便利だというところから出てきたものです。ぱっと見てどういったファイルを更新しているかが分かるのが良いです。

trac-dummy2

 

Trac プラグインの非互換な不具合

アリエルでは20~30個ほどのプラグインを使っています。プラグインがたくさんあると、移行のときの互換性が気になりますね。この機にもう使っていないプラグインもいくつか削除しました。

TracTags は準標準的なプラグインです。私が移行しているときは 0.7 の開発バージョンで、且つ DB のテーブルのスキーマを更新したため後戻りができず、このチケットのエラーに悩まされました。いまは 0.7 の正式バージョンもリリースされているので大丈夫です。

DefaultCcPlugin の管理画面で設定をしようとすると、コンポーネントが重複して表示される不具合です。表示だけの問題だったので運用に影響は与えませんが、すぐに直してくれました。

 

Trac Team へのお礼

移行を通して上述した Trac/TracPlugin の不具合に対応して頂いた Trac Team の Ryan Ollos 氏 と Jun Ōmae 氏 (@jun66j5) に感謝します。おかげで大きなトラブルなく移行できました。ありがとうございました。Trac 開発に協力したい方は What is a good patch? を読んでコントリビュートすると良いと思います。

 

Trac 開発とロードマップ

せっかくなので Trac の開発状況も少し整理してみましょう。

 

1.0.2 のマイルストーン

現時点の マイルストーン 1.0.2 によると、94%完了しています。が、スケジュールが17ヶ月遅延とあるように、もはやマイルストーンとして意味を成していません。社内で移行作業を行っていた2013年12月頃も95%前後だったように思います。当時は、ほぼ 1.0.2 の開発が終わってるからいいかと選択したのですが、あれから半年以上経ってもリリースされていない状況です。Trac Team が何もしていないわけではなく、その後もチケットが登録されては直すの繰り返しが続いているようです。

社内で運用していている分には特に困ることはなかったのでみえない所の品質が改善されているのかもしれません。メーリングリストのやり取り をみると、いまあるチケットが close される2週間後あたりを目処にリリースされるそうです。1.0.2 がリリースされたら、この移行記事を書こうと思っていましたが、いつリリースされるか分からないので先に書いてしまいました。

 

Tracのロードマップ

Road Map によると、3つのバージョンがあります。

  • 0.12系: 2010年6月にリリース、LTS (Long Term Support) として保守
  • 1.0系: 2012年7月にリリース、バージョン番号を刷新
  • 1.1.系: マイナーバージョンの奇数は開発版としてリリース

0.12系は多くのプラグインとの互換性を維持するためにそう簡単には破棄できないようにみえます。プラグインのメンテナンスが追いついていない現状をみると、プラグインが動かないために 1.0 に移行できないケースはあるかもしれません。準標準的なプラグインはほとんど 1.0 に対応しているようにみえますが、メンテナンスされてないプラグインをどうするかが移行の今後の課題のようにみえます。

また TracHacks の運用の問題もあります。プラグインのメンテナーになる方法は Adopting Hacks に記載されています。しかし、メーリングリストにリクエストを送って承認してもらい、Subversion のリポジトリのアクセス権限をもらうといった、いまどきの github で PR を送り合う文化と比べると、手順が面倒なので勢いでメンテナーになるといったことはなさそうに思います。こういったインフラも含めた体制の移行はかなり大変で、いまの Trac コミュニティにそこまでの熱意をもった人がいるのかどうかは微妙なところだと思います。

先日の記事で、当社でもメンテナンスされてない Trac プラグインにパッチを適用したリポジトリを公開しました。使っていて不具合があれば修正はしますが、メンテナーになるほどではないといった開発者のモチベーションを有効活用できない管理体制にみえます。

閑話休題。1.0 以降の開発では、stabledevelopment の2つのブランチに分けることで開発を加速させることを狙いとしています。0.12 から 1.0 のメジャーリリースに2年かかっていることを憂慮しての、それよりも早く同等のリリースを行いたいように伺えます。とはいえ、いま2014年7月で 1.2 がリリース時期すら未定なので意図したように実際には進捗していないようです。マルチプロジェクト対応が 1.1 リリース の目玉機能なので 1.2 の登場は待ち遠しいですね。

 

Apache Bloodhound の存在

Trac とは違う未来の可能性も考えてみます。Trac の課題の1つとして開発やプロジェクトマネジメントそのものが停滞しつつあることは述べました。

さらにオープンソースではあるものの、コミュニティサイトの運営も含め、Edgewall Software が開発/運営しているものというイメージが付くことを問題と捉える人もいます。そんな人たちが vendor-neutral な開発体制への移行を促す意図で発足したのが Apache Bloodhound のようです。Incubator プロジェクトとして発足したときのアナウンスが [PROPOSAL] Apache Bloodhound です。これによると、当初は WANdisco という会社の開発者たちが中心になって開発し始めたようです。

その後、Incubator プロジェクトを卒業して Top-Level プロジェクトに昇格しました。2013年8月にバージョン 0.7 がリリースされています。機能的には、Trac と比較して以下に焦点を当てています。

  • マルチプロジェクト対応
  • 全文検索機能
  • ユーザーフレンドリーなデザイン
  • インストールが簡単

Trac 自体の、開発プロジェクトとしての反省から BEP (Bloodhound Enhancement Proposal) という PEP に倣った機能拡張や仕様を提案する方法も取り入れたり、先に述べたマイルストーンの事実上の崩壊にならないよう リリースプロセス を明確にしています。さらに UI スクリーン/情報アーキテクチャ も整備されています。Trac 0.12 からの移行 もできるとありますが、内容をみると Trac 0.12 から 1.0 への移行とあまり変わらないようにみえます。

試しに apache/bloodhoundリポジトリ から trunk をチェックアウトしてインストールしてみました。インストール時に admin ユーザーを作成できるのは簡単で良いですね (いまどき当たり前ですが) 。UI も刷新していて Trac に飽きてきた感のある人には良さそうです。

インストール直後にプラグインを見てると、Bloodhound のツール群は Trac プラグインの1つとしてみえるようです。

bloodhound-admin-plugin1

目玉機能のマルチプロジェクトです。管理画面上では、プロジェクトではなく プロダクト として管理するようです。UI はよくできています。このプロダクト単位にチケット番号が1から割り当てられます。

bloodhound-multiproject1

当然、チケットの関連付け (BEP-0006) も作れます。いまのところ、プロダクトを跨ぐ関連付けはできないようです。

bloodhound-ticket-relation1

簡単にインストールして使ってみた限りでは、機能は申し分ないですが、プラグインである点にがっかりしました。

てっきり Apache Bloodhound が Trac を fork して作り直すんだと勘違いしていました。今後どうなるのか分かりませんが、現状では Trac プラグインの再発明と UI を置き換えたもののようにみえます。穿った見方をすれば、Trac の開発が停滞しているのは Edgewall Software の vendor-neutral じゃない性格によるものだと言いたい人たちが Apache Bloodhound という名前に変えたようにも受け取れます。Apache プロジェクトなら vendor-neutral にこだわる開発者も確保できるんじゃないかという問いかけとしてはおもしろいですが、いまのアーキテクチャをみて手伝おうとは思わないでしょう。そこそこの規模の5つのプラグインが、依存関係をもって提供されています。

Trac のエコシステム (プラグイン) を放棄するのは覚悟がいるからそうそうできないでしょう。Trac 開発がいま以上に停滞してしまったら、それなりに需要はあるかもしれませんが、Trac 開発がまた勢いを取り戻せば Apache Bloodhound と Trac のバージョン間での管理コストが増大していき、いずれ Apache Bloodhound のメンテナーがいなくなっていって、いまの TracHacks の状況と同じ歴史を繰り返すんじゃないかという気がします。

もうしばらく Apache Bloodhound のバージョン 1.0 が出るぐらいまで、その動向を伺ってみようと思います。


関連文書:

Posted by & filed under 開発.

このエントリーを含むはてなブックマークはてなブックマーク - アリエルの開発を支える Trac プラグイン このエントリをつぶやくこのWebページのtweets この記事をクリップ!Livedoorクリップ - アリエルの開発を支える Trac プラグイン Share on Tumblr Digg This

アリエルの開発は、いわゆる チケット駆動開発 に分類されるわけですが、課題/バグ管理システムに Trac を使っています。最近の Trac 自体の開発は活発とは言えないし、Apache Bloodhound も出てきたりしていて、その未来がどうなるのか懐疑的なところもありますが、懸命に保守してくれる開発者もいるので、まだまだ数年は安心して使えるように思います。

そんな Trac は minimalistic な基本方針により、足りない機能をプラグインで拡張するのが一般的です。このプラグインという過去の遺産が Trac を課題管理システムとして有力な候補の1つに押し上げているとも言えます。Trac のプラグインは TracHacks で探してそのリポジトリからインストールできます。プラグインを PyPI からインストールできるように trac.plugins の名前空間を使う 方法もありますが、PyPI で公開されているパッケージを俯瞰すると、おそらくは歴史的な経緯でこちらの方がマイナーな方法にみえます。

 

アリエル製 Trac プラグイン

さてタイトルの本題です。アリエルで開発した Trac プラグインを紹介します。詳細な説明はリンク先をみてもらうとして、その目的や背景と用途を簡単に紹介します。

これらのプラグインの見た目はこんな感じです。

trac-dummy1
trac-dummy2
trac-dummy3

 

TracTicketReferencePlugin

minimalistic な方針とのトレードオフですが、チケット同士の関連付けを行う機能すらありません。私の知っているバグ管理システム (Bugzilla, Mantis, Redmine) で、この機能が標準で提供されていないのは Trac だけです。いまどき全く困ったもんです … という経緯から作られたのがこのプラグインです。最低限のチケットの関連付けとコミットフックとの連携ができるようになっています。

InterTrac でのチケット関連付けの 要望 は社内外からあるのですが、Trac 自体の TracLinks と InterTrac との境界が曖昧で、それらをどう扱えば良いのかがちょっと調べた限りでは、よく分からなくてずっと放置状態です。がんばればできそうだけど、そのモチベーションもインセンティブもないような機能は、最近だとクラウドファンディングしたりするといいのかもしれないなと思ったりもします。

TracMultiSelectBoxPlugin

この機能も minimalistic な方針とのトレードオフです。複数選択リストが標準で提供されていません。この種のプラグインも古からのものから、リッチ UI なものから、探せばたくさんあります。軽量/シンプルな機能性と Trac 本体で提供される text の list フォーマットを使う ことで将来的な互換性も保持することを狙いとして作りました。いずれ Trac 自身に複数選択リストの機能が入るでしょうから、それまでの繋ぎとして使うプラグインです。

TracChangeFileBiffPlugin

限定的な用途ですが、コミットフックでリポジトリ連携するときに特定のファイルパターンを検知して、そのチケットにフラグを立てるプラグインです。上記のサンプル画像では、*.properties に対してプロパティファイルというフラグを立てます。TracMultiSelectBoxPlugin と併用すると便利です (もともと TracMultiSelectBoxPlugin はこのプラグインのために作ったものでした) 。社内では、プロパティファイルの他に、メッセージカタログに対する更新を監視して、チケットが fix された後にテスターがベリファイするときに日本語/英語のメッセージ確認を見逃さないようにするために使っています。

 

その他のプラグイン

アリエルでは20~30個ほどのプラグインを使っています。ほとんどは一般的によく使われるものだと思うので、最近導入したプラグインで良かったものをいくつか紹介します。

TracAdvancedSearchPlugin

Trac の標準検索機能はデータベースに対する検索となるため、データの増加に伴ってどんどん遅くなります。以前は Trac の標準検索機能で10秒前後かかっていた全文検索が、このプラグインにより1秒未満に短縮されました。全文検索のバックエンドには Apache Solr を使います (いまのところ対応しているのも Solr のみ) 。Solr の構築はちょっとややこしいですが、そのコスト以上の快適さを得られます。

いまの仕組みとしてチケットにコメントが追加されるごとに、そのチケット情報とコメントのテキストを全てインデクシングし直すといった効率の悪いことをやっています。それでもアリエルの環境 (インデクシングのリクエストを非同期モードにする、チケットのコメントは数百程度) では、特に運用トラブルが起きていないので大丈夫そうです。まだマージされていませんが、#29 の pull request を取り込むと全部検索のノイズを減らせます。

TracTicketFieldsLayoutPlugin

上記の1番目のサンプル画像で「基本項目」と「拡張項目」にグルーピングされているのに気付きました? Trac を運用していく中でどうしてもカスタムフィールドが増えてしまいますね。いろいろなフィールドがあってごちゃごちゃになりがちです。これらの項目は◯◯の運用に使うものといったグルーピングをすることで、見た目がすっきりするだけでなく、フィールドを探すのも容易になります。管理画面の UI も優れていて簡単に設定できます。

TracChangeFileBiffPlugin のデータの持ち方もこのプラグインの実装をお手本にさせて頂きました。

WikiExtrasPlugin

もうメンテナンスされていないようで残念な感じですが、Trac での表現力をあげるマクロやアイコン (上山根 祐輔氏 が クリエイティブ・コモンズ 表示 3.0 ライセンス で公開しているもの) を提供します。見た目はこんな感じです。

trac-wikiextras1
 
trac-wikiextras2

 
trac-wikiextras3

 

メンテナンスされていないプラグインに対するパッチ

過去の遺産であるプラグインが多いのは Trac の魅力の1つですが、便利なプラグインであってもメンテナンスされていないものもたくさんあります。アリエルで利用しているプラグインの中にも TracHacks へパッチを投稿したものの、残念ながらメンテナー不在で対応してくれなかったりすることもあります。社内でパッチを管理するのが面倒になってきたのと、github に置いておく方が社内外でも扱いやすく、他にも困っている人がいたら役に立つかもしれないし、どんどん公開していくことにしました。同じようにパッチ管理している人がいたら活用してください。

今後も何か問題があったら増えていくかもしれません。


関連文書:

Posted by & filed under 勉強会.

このエントリーを含むはてなブックマークはてなブックマーク - 社内 Python3 プログラミング勉強会 このエントリをつぶやくこのWebページのtweets この記事をクリップ!Livedoorクリップ - 社内 Python3 プログラミング勉強会 Share on Tumblr Digg This

先月、Python3 でコーディングする機会がありました。初めて Python3 専用のツールを作ってみたのですが、作ってるうちに楽しくなってきて誰かに伝えたいと思い立ってその勢いで社内勉強会を開催しました。

社内にもいくつか Python 製ツールはありますが、サポート対象に RHEL5.x があるため、未だに 2.4 で保守を続けています。そんなうっぷんを晴らすべく「ほら! 2.4 では使えなかった構文や標準ライブラリが使い放題だよ。」と感動を伝えたはずなのに、同僚の Rubyist から「何て後ろ向きな楽しさ …」と言われてしまいました。Ruby の新しいバージョンをどんどん使えば良い的な雰囲気はうらやましいです (それはそれでメンテの苦労もあるでしょうけれど) 。

コーディングの話題へ移る前の、パッケージングの話題を盛り過ぎたのか、時間が足らなくて実際のコードを見ながらのお話が半分ぐらいしかできませんでした。説明を省いたものや消化不良になったスライドの補足、あとサンプルコードを以下にまとめました。

みんなが Python3 を普通に使うようになったらきっと世の中は楽しくなると思います。


関連文書:

Posted by & filed under いろいろ.

このエントリーを含むはてなブックマークはてなブックマーク - インドからインターンシップ生がやってきた このエントリをつぶやくこのWebページのtweets この記事をクリップ!Livedoorクリップ - インドからインターンシップ生がやってきた Share on Tumblr Digg This

昨年に引き続き、今年もインドから2名のインターンシップ生がやってきました。彼らは大学3回生だそうです。採用チームの人事ブログで彼らの紹介があるので良かったらご覧ください。

インターンシップ生を受け入れるのは2年目なので、昨年よりは彼らが業務に携わりやすいように英語の資料やオリエンテーションの質が上がっていますが、それでもかなり不十分なのが実情です。普通の日本企業で働いていると、それだけ私たちは日本語を前提とした考え方、業務、ワークフローを構築していることに彼らが加わることによって気付かされたりもします。

日本語と英語の壁

例えば、せっかく Javadoc にメソッドの説明があってもそれが日本語で書かれているため、彼らが IDE を使っているとがっかりするだろうなと思うときもあります。それは、私も過去に中国語でコメントされたソースコードを見たときのがっかり感から来ています。あーあって気分です。

昔はバージョン管理システムで文字化けしたり、バグったりするからソースコードに日本語を書くな的なルールにも一定の利がありましたが、いまはそんなこと言うと、英語が読める人 vs 読めない人の対立になってしまうので難しいところです。ソースコードもコミットメッセージも全て英語で書いた方が良いと私は考えますが、他人へ強要するほどの根拠があるわけでもありません。余談ですが、(チケット見ないと内容を把握できないからと) チケット ID だけ書いてコミットする人もいます。あれは個人的にそのコミットの意図は何なんだ?とちょっと納得できなくて、もっと声を荒げるべきなのかもしれません。

別の例では、ある日、メンターの1人が休暇でインターンシップ生の1人から質問を受けました。彼は Trac に登録されたチケットのバグ修正を行っていたのですが、そのチケットが日本語で記述されていたため、内容を教えてほしいということでした。私が彼だったら読めないからとそんなチケットは無視するところですが、彼は自らもインターネットの自動翻訳サービスを活用して英訳しつつ、その内容を理解しようと努めていました。そういうところは若い人の真摯さ、おっさんになると偏屈になってしまうことを実感するときでもあったりします。

郷に入れば郷に従えという考え方がインドにあるかどうか知りませんが、ここは日本なのだから日本語を使うことそのものに問題はないけれど、せっかくのコミュニケーションの機会を失うことにも繋がるという点で残念だなと思ったりもしました。彼らのためにチケットの内容を全て英訳しようとか、コメントを必ず英語で書きましょうとか、費用対効果を考慮するとそんな業務指示は絶対に出ないのですが、外国人とのコミュニケーションに挑戦しようとか、楽しんでみようといった視点からやってみると敷居が下がって良いんじゃないかと思います。

学生時代のなにか

最近「後生畏るべし」ということばを知ったのですが、彼らをみていると正にそんな印象を受けます。確か2人とも日本に来るのは初めての経験で、インドから海外へ出るのも初めてだと聞いた気がします。学生からよく聞く質問で「学生時代に何をしたら良いですか?」といったものがあります。で、無難 (?) な答えに旅行に行きなさいといったものが一番多いように思います。私が学生の頃にも、何人かの社会人の方に学生のときに旅行に行った方が良いと勧められた記憶があります。

私はいまになって気付いたことですが、ここで言う旅行というのはなるべく海外へ行った方が良いということだったんだと思います。自分が学生の頃は海外へ行ったことがなくて、20台の後半になってお仕事で北京へ3ヶ月間行きました。海外へ出たのもそのときが初めてでした。そこで1ヶ月経ったらホームシックになって早く日本に帰りたいと思いながら、3ヶ月間、辛い日々を過ごしました。自分自身、英語がほとんど話せないし、当時の中国では一般の人には簡単な英単語もほとんど通じません。何というか時間が経つほど、すごい孤独感を実感するようになりました。そういったものはお仕事のモチベーションにも大きく影響を与えるので他国で働くというのはすごく大変なことなんだと、そのときになって私は初めて気付きました。

そういうことを経験してから彼らをみると、彼らの優秀さ、大変さ、そして後生畏るべしだなと思うわけです。学生時代に何をしたら良いかの質問にあえて答えるとしたら、何か目的がなくても良いからただ海外へ行って、そこで数ヶ月過ごしてみるのも良いかもしれません。観光しても良いと思うし、留学しても良いと思うし、ただ一定期間、外国にいることで得られるものがあるように思います。

まとめ

話が発散したのでまとめられなくなりました。インターンシップ生の彼らをみていて受ける刺激がどういうものかの一端を紹介してみました。おかげで久しぶりにこのブログにも投稿できました。


関連文書:

Posted by & filed under いろいろ.

このエントリーを含むはてなブックマークはてなブックマーク - Sphinxの全文検索を複合語に強くする このエントリをつぶやくこのWebページのtweets この記事をクリップ!Livedoorクリップ - Sphinxの全文検索を複合語に強くする Share on Tumblr Digg This

最近社内のドキュメントは殆どSphinxで書いてます。しかしどうも検索の精度が悪い、特に複合語がヒットしないと言われたので改善してみました。Sphinxのバージョンはv1.2.2です。

まずはドキュメントに全文検索機能を追加する

こんな方法で追加しました。

  1. sphinx-quickstartでドキュメント作成
  2. conf.pyに全文検索の設定を追加
    python
    html_search_language = 'ja'
    html_search_options = {'type':'default'} # build server using type:mecab
    
  3. make html

改善案1: searchtools.jsを賢くする

_build/html/_static/searchtools.jsパッチを当てます。

生成物にパッチを当てるのは気持ち悪いけど説明の簡便のため、というか私のSphinxやPythonへの理解が低いのでとりあえずこうします。もっとよい方法があれば教えてください。

パッチが何をしてるかというと、元々Sphinxがsearchindex.jsへ出力する索引は転置インデックスindex.termsがオブジェクトリテラルで作成されます。これだと単語が完全一致した時しかインデックスを取得できません。オブジェクトリテラルをソート済みの配列にすることで2分探索+前方一致検索が可能になります。

ソート済みの配列への変換は簡便のためSearch.setIndexでやってます。本来は変換したものをsearchindex.jsに出力したほうがよいでしょう。

こんな配列の索引が出来上がったとします。

1. ["データ", 1]
2. ["データベース", 2]
3. ["移行", 1]

2分探索はSearch._performTermsSearch内でやってます。このbinarySearchは索引内に検索語があるかどうかに関係なく検索語の辞書順での位置を返します。そこから後ろ方向に前方一致検索することで、例えば検索語が「データ」の場合「データ」だけでなく「データベース」もヒットさせることができます。

しかしこれだけだと検索語が複合語の場合、例えば「データ移行」がヒットしません。そこで2分探索の結果を前方向に、今度は検索語が索引内の単語で始まるかどうかをチェックします。そして「データ」がヒットしたら「データ」と「移行」でAND検索したものとしてその結果を返します。「データ」と「移行」が離れてる文書もヒットしますが、それが問題になるようであればdisplayNextItem内の$.ajax#completeで除外すればよいでしょう。

文書の内容や索引の作り方にもよりますが、元のよりは複合語に強くなったと思います。

改善案2: oktaviaを使う

oktaviaは渋川よしきさんが作成したFM-indexによる検索エンジンです。単語の境界に関係なくすべての文書に対し部分一致検索ができます。

非常に魅力的な検索エンジンなのですが、ドキュメントが現行バージョンの1.0に追いついてない、バグと思われる挙動がある、など導入するには少しハードルが高いという印象を受けました。渋川さんに教えていただいた内容を元に私が検証した結果を記しておきます。リビジョンは 8ed26702c90994b621dc426dd16f84debe728799 です。バージョン0.5は安定してるそうです(未検証)。

まずはoktaviaをインストールします。

git clone https://github.com/shibukawa/oktavia.git
cd oktavia
npm install

次に私の作ったパッチをマージします。

oktavia-mkindex-cliで索引を作成します。

grunt build

# -I オプションは「大文字小文字を無視した検索対応」パッチをマージした時のみ有効
./bin/oktavia-mkindex-cli -i ../_build/html/ -r ../_build/html/ -m html -u file -f .body -c 5 -t web -o ../_build/html/searchindex.oktavia.js -I

# search.htmlで使うファイルをコピー
cp ./bin/web/oktavia-jquery-ui.js ../_build/html/oktavia-jquery-ui.js
cp ./templates/sphinx/_static/searchstyle.css ../_build/html/searchstyle.css

oktaviaのファイルをhtmlに追加します。確認だけなら生成した_build/html/search.htmlを変更するのが楽ですが、本来は_templates/searchbox.htmlなどで追加するのがよいと思われます。

 <!-- headに追加 -->
 <script type="text/javascript" src="oktavia-jquery-ui.js"></script>
 <link rel="stylesheet" href="searchstyle.css" type="text/css" />
 <!-- bodyに追加 -->
 <div id="oktavia_search_form" data-index="searchindex.oktavia.js"></div>

search.htmlをブラウザで表示すれば検索可能です。検索結果がポップアップで表示されるなど元のSphinxのUIとは異なるので、元のと同じにしたい場合はoktavia-jquery-ui.jsを変更する必要があります。

searchtools.jsの改善だけでそこそこよくなったので今回はoktaviaの採用を見送りました。ただSphinxの検索エンジンをデフォルトでoktaviaにするという話もあるらしいと聞いています。もしそうなればこのような面倒な改造を行わなくとも、手軽に快適な全文検索ができるようになるでしょう。私はデフォルトでoktaviaが使える未来を望んでます。


関連文書:

  • 関連文書は見つからんがな

Posted by & filed under いろいろ.

このエントリーを含むはてなブックマークはてなブックマーク - NashornでもE4Xを使いたい このエントリをつぶやくこのWebページのtweets この記事をクリップ!Livedoorクリップ - NashornでもE4Xを使いたい Share on Tumblr Digg This

Nashornの字句解析のソースを見るとnashorn.lexer.xmlliteralsというのがあります。

XMLリテラル?…XML?…!?

E4X!E4Xじゃないか!

試してみました。
https://gist.github.com/tuchida/9796167

nashorn.lexer.xmlliteralsなし

$ jdk1.8.0/bin/javac E4X.java && jdk1.8.0/bin/java E4X
E4X: null
javax.script.ScriptException: <eval>:1:0 Expected an operand but found <
<xml>xml</xml>
^ in <eval> at line number 1 at column number 0
at jdk.nashorn.api.scripting.NashornScriptEngine.throwAsScriptException(NashornScriptEngine.java:564)
at jdk.nashorn.api.scripting.NashornScriptEngine.compileImpl(NashornScriptEngine.java:610)
at jdk.nashorn.api.scripting.NashornScriptEngine.compileImpl(NashornScriptEngine.java:597)
at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:524)
at jdk.nashorn.api.scripting.NashornScriptEngine.eval(NashornScriptEngine.java:194)
at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:264)
at E4X.main(E4X.java:11)
Caused by: jdk.nashorn.internal.runtime.ParserException: <eval>:1:0 Expected an operand but found <
<xml>xml</xml>
^
at jdk.nashorn.internal.parser.AbstractParser.error(AbstractParser.java:258)
at jdk.nashorn.internal.parser.AbstractParser.error(AbstractParser.java:243)
at jdk.nashorn.internal.parser.Parser.unaryExpression(Parser.java:2788)
at jdk.nashorn.internal.parser.Parser.expression(Parser.java:2888)
at jdk.nashorn.internal.parser.Parser.expressionStatement(Parser.java:1017)
at jdk.nashorn.internal.parser.Parser.statement(Parser.java:852)
at jdk.nashorn.internal.parser.Parser.sourceElements(Parser.java:693)
at jdk.nashorn.internal.parser.Parser.program(Parser.java:631)
at jdk.nashorn.internal.parser.Parser.parse(Parser.java:213)
at jdk.nashorn.internal.parser.Parser.parse(Parser.java:188)
at jdk.nashorn.internal.runtime.Context.compile(Context.java:936)
at jdk.nashorn.internal.runtime.Context.compileScript(Context.java:917)
at jdk.nashorn.internal.runtime.Context.compileScript(Context.java:406)
at jdk.nashorn.api.scripting.NashornScriptEngine.compileImpl(NashornScriptEngine.java:608)
... 5 more

nashorn.lexer.xmlliteralsあり

$ jdk1.8.0/bin/javac E4X.java && jdk1.8.0/bin/java -Dnashorn.lexer.xmlliterals=true E4X
E4X: true
java.lang.NullPointerException
at jdk.nashorn.internal.codegen.MethodEmitter.convert(MethodEmitter.java:1560)
at jdk.nashorn.internal.codegen.CodeGenerator.enterLiteralNode(CodeGenerator.java:1432)
at jdk.nashorn.internal.codegen.CodeGenerator.access$300(CodeGenerator.java:154)
at jdk.nashorn.internal.codegen.CodeGenerator$1.enterLiteralNode(CodeGenerator.java:473)
at jdk.nashorn.internal.ir.LiteralNode.accept(LiteralNode.java:210)
at jdk.nashorn.internal.codegen.CodeGenerator.load(CodeGenerator.java:424)
at jdk.nashorn.internal.codegen.CodeGenerator.load(CodeGenerator.java:403)
at jdk.nashorn.internal.codegen.CodeGenerator$8.evaluate(CodeGenerator.java:2471)
at jdk.nashorn.internal.codegen.CodeGenerator$Store.store(CodeGenerator.java:3206)
at jdk.nashorn.internal.codegen.CodeGenerator.enterASSIGN(CodeGenerator.java:2476)
at jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor.enterBinaryNode(NodeOperatorVisitor.java:117)
at jdk.nashorn.internal.ir.BinaryNode.accept(BinaryNode.java:168)
at jdk.nashorn.internal.codegen.CodeGenerator$1.enterDefault(CodeGenerator.java:479)
at jdk.nashorn.internal.ir.visitor.NodeVisitor.enterBinaryNode(NodeVisitor.java:178)
at jdk.nashorn.internal.ir.BinaryNode.accept(BinaryNode.java:168)
at jdk.nashorn.internal.codegen.CodeGenerator.load(CodeGenerator.java:424)
at jdk.nashorn.internal.codegen.CodeGenerator.load(CodeGenerator.java:359)
at jdk.nashorn.internal.codegen.CodeGenerator.enterDISCARD(CodeGenerator.java:2345)
at jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor.enterUnaryNode(NodeOperatorVisitor.java:57)
at jdk.nashorn.internal.ir.UnaryNode.accept(UnaryNode.java:124)
at jdk.nashorn.internal.codegen.CodeGenerator.enterExpressionStatement(CodeGenerator.java:857)
at jdk.nashorn.internal.ir.ExpressionStatement.accept(ExpressionStatement.java:66)
at jdk.nashorn.internal.ir.Node.accept(Node.java:291)
at jdk.nashorn.internal.ir.Block.accept(Block.java:143)
at jdk.nashorn.internal.ir.LexicalContextNode$Acceptor.accept(LexicalContextNode.java:57)
at jdk.nashorn.internal.ir.Block.accept(Block.java:361)
at jdk.nashorn.internal.ir.FunctionNode.accept(FunctionNode.java:297)
at jdk.nashorn.internal.ir.LexicalContextNode$Acceptor.accept(LexicalContextNode.java:57)
at jdk.nashorn.internal.ir.LexicalContextExpression.accept(LexicalContextExpression.java:46)
at jdk.nashorn.internal.ir.FunctionNode.accept(FunctionNode.java:49)
at jdk.nashorn.internal.codegen.CompilationPhase$8.transform(CompilationPhase.java:379)
at jdk.nashorn.internal.codegen.CompilationPhase.apply(CompilationPhase.java:513)
at jdk.nashorn.internal.codegen.Compiler.compile(Compiler.java:358)
at jdk.nashorn.internal.runtime.Context.compile(Context.java:960)
at jdk.nashorn.internal.runtime.Context.compileScript(Context.java:917)
at jdk.nashorn.internal.runtime.Context.compileScript(Context.java:406)
at jdk.nashorn.api.scripting.NashornScriptEngine.compileImpl(NashornScriptEngine.java:608)
at jdk.nashorn.api.scripting.NashornScriptEngine.compileImpl(NashornScriptEngine.java:597)
at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:524)
at jdk.nashorn.api.scripting.NashornScriptEngine.eval(NashornScriptEngine.java:194)
at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:264)
at E4X.main(E4X.java:11)

nashorn.lexer.xmlliteralsありだとシンタックスエラーにならずコード生成部分でヌルポになりました。ソースを追ってみるとトークンの生成はするのですが(valueOfXMLのコメントが「Convert a regex token to a token object.」なのが微笑ましいですね)、コード生成時にXMLTokenを見てる箇所がありません。

はー、RhinoかわいいよRhino。


関連文書:

  • 関連文書は見つからんがな

Posted by & filed under 勉強会.

このエントリーを含むはてなブックマークはてなブックマーク - 社内Java8勉強会 ラムダ式とストリームAPI このエントリをつぶやくこのWebページのtweets この記事をクリップ!Livedoorクリップ - 社内Java8勉強会 ラムダ式とストリームAPI Share on Tumblr Digg This

先週Java8がリリースされましたが、さっそく社内での開発でも使うことになりそうです。

というわけで、Java8の目玉機能であるラムダ式とストリームAPIについて、社内勉強会を開催しました。

普段の社内勉強会よりも参加者数が多くて、みんなの関心の高さが伺えますね。

 

ラムダ式とストリームAPIは、他のプログラミング言語とだいたい似たようなものだろうと思っていたのですが、調べてみると意外とJava特有のことが多くて面白かったです。


関連文書:

  • 関連文書は見つからんがな

Posted by & filed under 開発.

このエントリーを含むはてなブックマークはてなブックマーク - Java8のラムダ式の説明に対するつっこみを受けて このエントリをつぶやくこのWebページのtweets この記事をクリップ!Livedoorクリップ - Java8のラムダ式の説明に対するつっこみを受けて Share on Tumblr Digg This

先日の記事に宮川さんからつっこみが入りました。

http://d.hatena.ne.jp/miyakawa_taku/20140315/1394902012

ありがたいことです。やはり情報というのはどんどん公開すべきです。

リンク先を読まない人のために書いておくと、自分が書いた「Java8のラムダ式は(関数型インターフェースの)オブジェクトを生成しない」は間違いで、仕様上は「Evaluation of a lambda expression produces an instance of a functional interface」です、というつっこみです。

この記述は知りませんでした。

「an instance of a functional interface」をオブジェクトと言い換えていいかは多少迷います。オブジェクトという用語の定義次第だからです。個人的には、参照のとれる実体をオブジェクトと呼んでいいと思うので、これをオブジェクトと言い換えるのにそんなに抵抗はありません。とは言え、produce an instanceとcreate an objectを同じ日本語にするのは少し抵抗があります。

なので、昨日の記事の発言は「OpenJDK Java8のラムダ式は(関数型インターフェースの)オブジェクトをnewしない」に訂正します。newは単に対応するバイトコードのことです。用語に解釈の余地がありません。

ちなみに、「Java8のラムダ式は(関数型インターフェースの)オブジェクトを生成しない」と書いた理由のひとつは、new呼び出しのないバイトコードを見ての判断ですが、もうひとつの理由としてラムダ式の中に書いたthis参照の動きがあります。と言うのも、ラムダ式の中のthis参照はエンクロージングオブジェクトを参照するからです。この動作が、(InvokeDynamicを使うか否かの)実装依存だとまずいはずです。

仕様から探すと、「Unlike code appearing in anonymous class declarations, the meaning of names and the this and super keywords appearing in a lambda body are the same as in the surrounding context (except that lambda parameters introduce new names).」が該当するようです。


関連文書:

  • 関連文書は見つからんがな

Posted by & filed under いろいろ.

このエントリーを含むはてなブックマークはてなブックマーク - そろそろはてなブログのjsについて一言いっておくか このエントリをつぶやくこのWebページのtweets この記事をクリップ!Livedoorクリップ - そろそろはてなブログのjsについて一言いっておくか Share on Tumblr Digg This

※本記事は最後まで読まないと誤解されかねない内容を含むため、Web広告に敵愾心を抱いてる方、プライバシやくざの方などは閲覧をご遠慮ください

http://hatenablog.com/js

Hatena.Diary.setupTrackとかHatena.Diary.trackEventNowとかあるのにnavigator.doNotTrackを見てないのが気になりました。

別にこのことを批判したいわけではありません。ぱっと見た感じ表示しているURLやreferrerなどの一般的なサーバーのアクセスログに記録される情報だったり、表示にかかった時間やスクリプトエラーなどのセンシティブでない情報を記録してるだけに見えます。Do Not Trackがこの手のものまで禁止してるかも知りません。

ただ今回の件ではてなブックマークボタンのトラッキングの件にふれてる人が見当たらなかったので一言いっておきたくなりました。minifyやコメント削除をするとこういったことのチェックが難しくなります。まあトラッキングだけなら通信見ればわかるし、minifyされててもClosure CompilerのADVANCEDモードで鍛えられたおかげである程度は読めるけど。


関連文書:

  • 関連文書は見つからんがな