watermint.org

Takayuki Okazaki's blog

Share on Facebook
Share on GREE

寺田さんにつづき、Java Advent Calendar 2011の12月20日分です。明日はzinbeさんです。

さて今回はJava自体の話ではなくて、現在の勤務先でやっているようなソーシャルサービスの開発とJavaの関係について少し考察します。まず自分自身のスキルを簡単に紹介しておくと、Sun Microsystems時代には7年程度Javaに関連する仕事をしていました。その内容は金融業、流通業、製造業など様々な業種のサービスを支えるシステム構築や、トラブル解決のお手伝いという内容でした。その後、ACCESSに移ってすこしJavaから離れてObjective-Cを書いていました。現職ではObjective-Cも時々触りますがPHPがメインです。Objective-Cは実質1年、PHPも同じく1年ぐらいというところでしょうか。本格的にJavaを使うことから離れておよそ3年たちましたが、未だに一番詳しいのはたぶんJavaだと思います。
R0023024.JPG
幸か不幸か、2008年にSunを退社してからしばらくはOracleによるSun買収などもありJava自体のテクノロジー的な進歩が停滞していました。このため、世の中に流通しているJava技術はまだ僕の知っている頃のものとそんなに差分がないと思っています。今年に入ってOracle持ち前の推進力がようやくJavaに対して効果を見せ始め、大きな変化のタイミングに入ってきたなと感じています。

Javaは2000年頃から続くエンタープライズ時代からのしがらみもあり、その殻からしばらく抜け出せず、「クラウド」が当たり前になって、もはや必須になった状況にはうまく対応できずにいました。最近になってようやくJBossがクラウドのソリューションを発表したり、Java EE 7にはクラウドプラットホームを前提とした仕様が入るようになってきました。こういう状況を見ていると、数年以内にJavaで育ったソーシャルサービスが世の中に増えてくることはおそらく間違いないだろうと思っています。それは、SIをしていた会社がB2Cサービスに転身したり、Javaの資産を使ったWebサービスがいよいよ現実的にマススケールに対応できるようになるだろうからです。

一方、現在のソーシャルサービス開発の現場を見渡してみるとJavaはまだまだこの分野に踏み込めていないことがわかります。たとえば、GREEやFacebookはPHP、mixiやmobageはPerl、CookpadはRubyなどいわゆるスクリプト言語が開発現場での主流です。各社の求人も多くはこれらに関連するスキルを持ったエンジニアに対するものが中心であることからもよくわかります。
R0023026.JPG
これにはいくつか要因があると思いますが、今回は2つ紹介しておこうと思います。

ひとつめは、既存資産の問題です。ソーシャルサービスは各社とも個人レベルで小さく始めていたサービスが人気を呼び、拡大していった。という歴史を持っています。このため当然ながら、システムを支える多くの部分は開発当初に選んだ環境、2004〜2006年頃に価格的・技術的にリーズナブルであった選択肢をベースとしています。それが結果的にPHPであったり、PerlやRubyであるわけです。仮に、2011年の今、ソーシャルサービスを開発するのに最も適した言語がJavaであると判断したとしたとしても、わざわざいま開発環境を変え、PHPで書いてあるコードをJavaに移し替えることは労力や期間などを考えれば現実的ではないですし、費用対効果の面からも新しい環境に乗り換えることは十分説明がつかないでしょう。
R0023007.JPG
二つ目は、リリースサイクルの問題です。現在もJavaは多くのエンタープライズシステムで使われています。Javaはエンタープライズシステムのリリースサイクルと相性がよいと思っています。エンタープライズシステムは統計などをみるとおおざっぱには、3〜18ヶ月程度かけて開発され、12〜48ヶ月程度で新しいシステムにバージョンアップしたり、リプレースされているとおもいます。安定性を重視するシステムでは、バグ修正や、バージョンアップは数週間に1度程度であることがほとんどでしょう。一方、ソーシャルサービスはユーザの動向を見ながら、ほぼリアルタイムにシステムを更新することが求められます。Java EEアーキテクチャのように、コンテナ側で多くの状態管理をしてくれることはスケーラビリティーやパフォーマンスの観点から重要なのですが、サービスを止めずにシステムを部分的に置き換えたり、追加や削除するにはあまり向いていません。OSGiやJSR 277、JSR 294のような仕様は部分的にこれらの問題を解決できるかもしれませんが、運用フローまでのノウハウが蓄積されるまでにはまだ2〜3年単位の時間がかかるのではないかとおもいます。

以上、結論としてはややJavaに対してネガティブなものになりましたが、この現状をきちんと認識した上でテクノロジーとしてのJava、それを支えるコミュニティーが問題解決に取り組んでいければ、ソーシャルサービスの中でのプレゼンスをどんどん高めていけるのではないかと思いました。

Share on Facebook
Share on GREE

なんて思ってたら、


!!!
お客様の中に名探偵の方はいらっしゃいませんか?><

ということで、iOS Advent Calendarのネタです。今回は、iOSデバッグ界隈の中ではマイナーと言われるか、一部のファンに熱狂的な支持!といわれるか絶妙なバランスを保ったDTraceでのデバッグを取り上げます。

DTraceはもともと、Sun Microsystemsによって開発されSolaris 10に搭載されたデバッグフレームワークです。現在は、FreeBSDやMac OS Xなどでも利用することが出来ます。詳しい説明は今回は省きますがTraceと名前がついていることから想像できる通り、プログラムの実行をトレースしてどのコードが、どういう風に実行されたか。といったことを調べることが出来ます。

こんなことはありませんか?

  1. 初期化処理がどうやらうまくいっていないようだが、どこまで実行されたか知りたい。しかし、Xcodeのデバッガでステップ実行・ブレークポイント設定などしながら確認するにはプログラムが大きすぎる。
  2. NSLogを埋め込んでどこまで実行されたか確認したいが、そもそもログを埋め込むのが面倒くさい。または、ソースコードをNSLogだらけにして汚したくない。

個人的にはXcodeのブレークポイント設定はなかなかよく出来ていると思っていて、多くのケースではそれで足りると思っています。

Xcodeでブレークポイントを設定してActionにSoundを設定、ブレークポイントは自動的に続行するように設定しておけばその該当箇所が実行されたかどうか音で聞き分けて調べることが出来ます。初期化関連の問題を探すときなんかは個人的にはよく利用しています。

さて、これでも十分なんですが設定したいブレークポイントが増えてくると音の種類も限定されますしどの音がどのブレークポイントだったかよくわからなくなります。Log Messageなどのアクションを設定してログ出力するのもいいのですが、網羅的にブレークポイント設定は面倒くさすぎます。

こういうときに威力を発揮するのがDTraceです。まずは手っ取り早くつかってみましょう。XcodeについているiOSのサンプルプロジェクトLazyTableImagesを題材にしています。Xcodeであなたのデバッグしたいプロジェクトを開いて、シミュレーター上で実行してください。出ましたか?

次にターミナルから次のコマンドを実行します。

$ ps ax | grep Simulator | grep -v iPhoneSimulator

すると次のようにiOSシミュレーター上で実行されているアプリのプロセスIDが分かります。下の例だと85894ですね。

85894   ??  SX     0:00.33 /Users/watermint/Library/Application Support/iPhone Simulator/4.0.2/Applications/993BF578-B81F-4B4C-97FA-B6B6843DD47E/LazyTable.app/LazyTable
88978 s004  R+     0:00.00 grep Simulator

さてこれが分かったら続いてdtraceを実行します。dtraceは他の人の実行しているプロセスなども丸裸にしてしまうなどでき、セキュリティー上の課題があるため一般ユーザでは実行できません。sudoをつかって実行しましょう。下記の85894の部分は調べたプロセスIDに読み替えてください。

$ sudo dtrace -p 85894 -n ‘objc$target:::entry { printf(“[%s %s]\n”, probemod, probefunc); }’

できましたか?できたらアプリを適当にタップするなど動かしてみてください。

このようにObjective Cで実行された関数がリアルタイムに出力されます。どうでしょうか?これでNSLogを仕込まなくてもトレースがとれるようになりましたね。でもちょっと出力が多すぎるようです。これではデバッグの役に立てるにはややしんどいです。出力を抑制してみましょう。

objc$target:::entryのところを、objc$target:クラス名::entryと書き換えてみましょう。ここではLazyTableImageにあるクラスのIconDownloaderとしましょうか。実際に実行するときにはあなたのプロジェクトにあるクラスをつかってください。

$ sudo dtrace -p 85894 -n ‘objc$IconDownloader::entry { printf(“[%s %s]\n”, probemod, probefunc); }’


出力が一気に減って、IconDownloaderだけになりましたね。ご想像の通り、関数名でもフィルタできます。

$ sudo dtrace -p 85894 -n ‘objc$target:IconDownloader:-indexPathInTableView:entry { printf(“[%s %s]\n”, probemod, probefunc); }’

便利ですね。これでたとえば、初期化処理のときに通らなければならない関数がちゃんと実行されているかどうか分かるようになりました。クラス名や関数名にはワイルドカードが使えます。たとえば、IconDownloader以外にIconButton、IconButtonLabelなどのクラスがあった場合にはIcon*というようにアスタリスクを指定するとIconで始まるクラス名にマッチします。

さて、デバッグしていて原因箇所がなんとなくわかってきたときに知りたいのはその関数がいつ、どこから呼ばれたか?です。呼び出し元を知りたいですよね。そういうときは、もうちょっと凝ったdtraceの使い方をしてみましょう。

次のようなファイルを用意して、trace.d というファイル名で保存しておきます

#pragma D option quiet

self int level;

objc$target:::entry
{
  printf("%*s[tid=%lld][%s %s]\n",
    self->level * 2, " ->",
    (long long)tid, probemod, probefunc);
  self->ts[probefunc] = timestamp;
  self->level++;
}

objc$target:::return
{
  time = timestamp - self->ts[probefunc];
  self->level--;
  printf("%*s[tid=%lld][%s %s] [%dms]\n",
    self->level * 2, " <-", (long long)tid,
    probemod, probefunc, time / 1000);
}

ではこれを実行してみましょう。

$ sudo dtrace -p 85894 -s trace.d

このように関数呼び出しをツリー状にしてダンプできます。後は必要に応じてクラス名や関数をフィルタしていけば良いでしょう。

さて、すこし駆け足になりましたがXcodeのデバッガをつかう場合と比べてだいぶ便利な機能があることがお分かりいただけたでしょうか?

コマンドラインからの実行に抵抗があるというかたはInstrumentsからもDtraceは実行できます。

さて、すこし駆け足になりましたが最後に制約事項を説明しておきます。dtraceはOS側に仕込まれたプローブという検出ポイントに対して働きます。このため、トレースしたい内容のプローブがOS側で実装されていることが必須です。このため、dtraceのプローブを持たないiOSにはdtraceを接続できません。必ずiOSシミュレータ上で実行しているアプリに対してdtraceを実行してください。

iPhoneやiPadなどのiOSが動作している実機に対しては別途従来通りのXcodeやgdbを用いたデバッグが必要です。

Mac OS XのdtraceにはObjective Cのためのプローブがいくつか用意されています。詳しくはman dtraceを参照してください。