ADVANCEDモードでコンパイルしたJavaScriptをデバッグするテクニック

こんにちは。kintone開発チームの天野(@ama_ch)です。前回は、JavaScriptをClosure CompilerのADVANCEDモードでコンパイルする方法を紹介しました。

コンパイルが無事に通っても、素直に動いてくれることは滅多にありません。コンパイル後のコードが動かない原因をひとつずつ特定し、修正するという作業が待っています。


ゴゴゴゴゴ...

今回は、僕が実践しているデバッグのテクニックを紹介します。きっちりとデバッグをして、ADVANCEDモード対応を完璧なものにしましょう。

コンパイル後に動かなくなる原因

コンパイル後のコードが動かない原因は、次の2点に大別することができます。

  • プロパティへのアクセスが正しい手順でできていない
  • エントリーポイントが存在せずコードが消されてしまっている

特に前者が原因になっていることがほとんどです。そのため、コンパイル後のコードを追う時は「どのプロパティへのアクセスで失敗しているか」という観点で見るのが重要です。

デバッグ用コンパイルオプションを設定する

Closure Compilerにはデバッグ支援用のオプションが用意されています。これらのオプションを使うことで、効率的にデバッグが進められます。

debugオプション

compiler.jarのヘルプを見ると「Enable debugging options」と非常にそっけない説明しか書いてないのですが、かなり有用なオプションです。 debugオプションをつけると、リネーム処理がコンパイル前の名前を維持しつつ行われるようになります。

コンパイル前 コンパイル後(debugオプションなし) コンパイル後(debugオプションあり)

debugオプションをつけたコードは長くて読み難いですが、基本的にはname→$name$のようなルールでリネームされており、コンパイル前のメソッド名やプロパティ名を読み取ることができます。リネーム方法が変わるだけで、コードとしての挙動は全く同じなので、コンパイル前の名前を参照しつつコンパイル後のコードを動かすことができるようになります。

formattingオプション

コンパイル後されたコードのフォーマットを指定するオプションです。PRETTY_PRINTとPRINT_INPUT_DELIMITERが指定でき、両方同時に指定できます。 PRETTY_PRINTを指定すると、コンパイル後のコードに改行とスペースが挿入され読みやすくなります。整形済みのコードはブレークポイントも設定しやすくなるので重宝します。Chrome Developer Toolsにあるコード整形機能をClosure Compilerにやってもらう感覚です。 PRINT_INPUT_DELIMITERを指定すると、以下のようにファイルの区切りにコメントが挿入されるようになり、コンパイル前のファイルパスなどが素早く調べられます。

問題箇所を絞り込むために、次のようなテクニックも有効です。

debuggerステートメントやconsole.logを書き込んでコンパイル

debuggerステートメントやconsole.logはコンパイルしても消えないので、コンパイル後の目印として使うことができます。ステップ実行をしながら値を確認していきたい場合はdebuggerステートメントが便利です。イベントなどの関係で大量に通るポイントは、console.logで値を出力したり、if文で括ったdebuggerステートメントを使って特定の条件でブレークさせるのが良いでしょう。

ここまでの方法で、開発時のデバッグはサクサクできるのではないかと思います。デプロイ後の環境で動かないという緊急事態や、debugオプションなどに頼らず脳をコンパイラ対応していきたいという方のために、さらなるテクニックも紹介します。

JavaScript整形機能

本番環境では整形出力などしてくれないので、Chrome Developer Toolsのjs整形機能はデフォルトでONにしておきます。

Break on Exception

Chrome Developer Toolsの例外発生時にブレークさせる機能です。実際には「例外が出るなんてすごい親切!!」ってくらい例外すら出ない症状に苦しめられるのですが、いざ例外が出た時には便利な機能です。

DOM BreakpointsとXHR Breakpoints

こちらもChrome Developer Toolsの機能です。DOM操作やリクエスト送信時にブレークさせることができます。JavaScriptはイベントドリブンなコードがとても多いので、これらの機能を使ってデバッグできることも多いです。 この機能でブレークさせた時は、コールスタックに注目します。コンパイルされたコードは依存グラフの根に近いもの(≒ライブラリのコア)が上の方に配置され、葉に近いもの(≒アプリケーションの実装)が下の方に配置されるという特徴があります。そのため、コールスタックを見て

  • 行番号が小さいものはライブラリ側のコード
  • 行番号が大きいものはアプリケーションのコード

であるとざっくり推定することができます。下記の図なら、赤線の引いてある19000行付近が求めているアプリケーションロジックの可能性が高いです。

文字列を探す

「文字列リテラルは決して置き換えられない」という大原則に基づき、文字列リテラルをエントリーポイントにしてコードを読みます。当然ながら、目的のコード付近に文字列リテラルがない場合は使えません。

コンパイルされたコードを読む時は、今までに出てきたような方法で、ブレークポイントを設定してステップ実行しながら読み進めます。慣れてくると、スコープ内の変数の雰囲気やコードブロックの形から、何となくどのあたりの処理をしているのか分かるようになってきます。このくらいになると、脳がコンパイラに対応してきた実感が湧いてきます!

Chrome Developer Toolsの機能を活用したテクニックは、コンパイル済みのコードでなくても役立ちます。モダンなツールと少しの気合で、日々のデバッグを効率的にしていきましょう!