ほとんどの場合、プログラムにはたくさんのクラス、そしてたくさんのメソッドが含まれています。当然ながら、それらのメソッドのすべてを、プログラムのmainエントリー・ポイントだけからテストするのは、不可能ではないにしても非常に困難です。
その点、単体テストが役立ちます。私自身を含めて多くのプログラマーやソフトウェア設計者は、堅固なソフトウェアを作成する上で単体テストの重要性を強調しています。しかしながら、プログラムのさまざまな要素にもっと対話式にアクセスしたいという場合には、潜在的なトレードオフがあります。
そのような場合、各結果ごとに新しい単体テストを作成し、コンパイルし、実行するのは、なかなか面倒です。特定の入力に対してプログラムがどう動作するかが前もってわからない場合は、特にそうです (AIプログラムなどの場合)。
では、どうすればいいのでしょうか?
1つのアナロジーとして、コンパイル言語 (JavaやC++ など) と、インタープリター言語 (PythonやSchemeなど) の違いについて考慮してみましょう。
コンパイル言語の場合、コーディング/テスト/デバッグのサイクルには、当然のことながらコンパイルという余分のステップが必要であり、小さな変更がたくさんある場合には、うんざりするような作業になりかねません。この点に関しては、インタープリター言語のほうが流動的であり、修正が容易であると言えます。(ただし、柔軟であることには犠牲が伴います。一般にインタープリター言語では、型検査などコードに関する静的検査が貧弱になる傾向があります。)
再コンパイルなしでプログラムに変更を加えたいということ以外にも、新しい単体テストを追加せずにプログラムの特定の要素を検査したいということがあります。そういう場合、伝統的に「read-eval-printループ」(repl) と呼ばれる処理が役立ちます。
repl は、式を入力とし、特定のプログラムのコンテキストの中でそれを評価し、その結果を表示するためのテキスト・ベースのツールです。結果を表示した後は、再び別の式が入力されるのを待機し、処理を繰り返します。その起源はLispに類する言語にありますが、Pythonなど比較的最近の言語でも使用されています。
そのようなツールを便利に使用できるのは、それらの言語においてだけではありません。Javaプログラマーは、デバッグ以外でもreplを活用できます。
GUIの開発
GUI開発においては、レイアウトを決めて接続する必要のあるコンポーネントが数多くあります。GUI開発については、次のように言うことができるでしょう。
- コンポーネントは、自明でない方法で相互に作用します。
- GUIをとにかく実行するためのコードをすべてコーディングするには相当の時間がかかります。
- 実際にそれがどう表示されるかを確認した後、まず間違いなく、そのGUIのさまざまな面を変更したくなります。
この問題に対する一般的な "解決策" は、JBuilderやForteなどのIDEに含まれるような、GUI開発用グラフィック・ツールを使用することです。私は、個人的にはそのようなアプローチが嫌いです。ツールがどんなJavaコードを生成するのかを知ることは困難だし、生成されたコードを修正することはGUI開発ツールとの互換性がなくなるかもしれないという危険を伴うからです (実際IDEによっては、自動生成コードを直接修正することが禁止されています)。
さらに、多くのGUI開発ツールでは、Javaコード生成において独自のGUIライブラリーが使用されており、GUIの互換性の点で制限があります。
GUI開発は、replを使用するほうがずっと簡単です。対話的にGUIコンポーネントを定義し、それを表示すればいいのです。気に入らない点があれば、ただちに修正できます。それから対話的処理を使用して、それらをプログラムに取り込むことができます。
新しいAPIの探求
Java言語によるプログラミングの最大の長所の1つは、データベースからWeb Servicesやテレビに至るまで、ありとあらゆるものに接続できる膨大なAPIの資産を活用できることです。しかし、各APIのセマンティクスを習得するには、時間がかかります。
一方、JavadocsがAPIの動作のあらゆる側面を記述し切れていないことがしばしばあります。このジレンマを解決する1つの方法は、APIを直接にテストすることであり、それにはreplを使えば、ずっと短時間で済みます。つまり、単にメソッド呼び出しを入力して、その場でその結果を見ればよいのです!
APIテストにreplを使用する付加的なメリットの1つは、ほとんどのプログラマーの主要な行動パターン、つまり実際にやってみて覚える、という傾向を強化することもできる、ということでしょう。
replにはこれだけのメリットがあるわけですから、次に考えるべき点は、Java言語で使用できるreplには、どんなものがあるか、ということです。
Jython (以前はJPythonとも呼ばれていた) は、Python (replを含む) を (100%ピュア) Javaプログラミング言語で実装したものです。実際には、JythonはPythonをコンパイルして、(ちょっと独特な) Javaソース・コードに、または直接バイトコードにします。
Pythonの精神として、JavaとJythonとのシームレスなインターオペラビリティーを提供するためのあらゆる努力が払われてきました。あたかもネイティブにJavaでプログラミングしているかのようにJavaの標準ライブラリーのすべてを利用することができ、既存のJavaクラス・ファイルを利用することも可能です。replを使用すれば、標準ライブラリーだけでなく、バイトコードにコンパイルした独自のJava (またはJython) クラスも処理できます。
Jython replを使用する際に、考慮するべき1つの重要な点は、コーディングするのがPythonの式 であって、Javaの式ではないということです。それには、簡潔で美しいPythonの構文を利用できるという積極的な面があります。
たとえば、a を1に、b を2に、そしてc を3に対応させる新しいハッシュ・テーブルを作成するとしましょう。Jythonなら、次のようにコーディングするだけです。
>>> h = {'a':1, 'b':2, 'c':3} |
新しい入力行の前に、インタープリターによって> が表示されます。
Jythonの構文は、新しいGUIのデザインをいろいろ探る場合にも、大きなメリットがあります。1つの点は、GUI要素のさまざまなフィールドを、コンストラクターへのキーワード引き数として指定できるということです。たとえば、
>>> from javax.swing import * >>> f = JFrame(visible=1) |
この例は、JythonとJava言語の相違点のいくつかを示しています。
- importステートメントの構文が大きく異なっています。
- ブール値の代わりに整数値が使用されています (1がtrue、0がfalseに対応)。
Jythonコードのほうが入力量が少なくて済むことを示す、別の例を示しましょう。GUI要素にアクション・リスナーを追加するコードです。通常、そのようなリスナーは、Command Patternを使用することにより、無名の内部クラスのインスタンスとして指定します。Python (およびその他の多くのスクリプト言語) では、対話式関数定義を使用することにより、そのようなコマンドを非常に簡潔に指定できます。たとえば、上記の対話式セッションにおいて、シンプルなアクション・リスナーをJButtonに追加してみましょう。
>>> def listener(event): ... print 'thank you' >>> |
これは、Jythonでの関数定義の例です。インタープリターが省略記号を表示することで、ステートメントが次の行に続くことを知ることができます。この関数は、引き数が1個であり、"thank you" という文字列を標準出力に出力します。これは、次のようにしてアクション・リスナーとして利用できます。
>>> panel = JPanel()
>>> panel.add(JButton('press me', actionPerformed=listener))
>>> f.getContentPane().add(panel)
>>> f.pack()
|
これで画面にウィンドウが表示され、そこに "press me" というボタンが表示されます。このボタンを押すと、"thank you" という文字列が標準出力に出力されます。Javaコードでこれをコーディングしたら、どれだけのコード量になるかを想像してみてください。
もちろん、デメリットもあります。たとえば、
- 静的型検査の機能は利用できません (静的検査がreplでどれだけ利用価値があるかは疑問ですが)。
- replで入力する式はJavaコードではないので、replからプログラムに、変換なしで式をそのままコピー・アンド・ペーストすることはできません。
- JavaコードでJythonを使用する場合、頭の中で同時に2つの言語で考えることになります (人によっては面白いと感じるかもしれませんが)。
Javaで使用できるもう1つのreplはDynamicJavaです。これは、本当にJavaベース (まあ、そう言ってよいでしょう) のオープン・ソース・ツールです。これには、若干の相違点があります。
- repl言語では、変数宣言で変数の静的型指定の必要がありません。
- ステートメントの終わりにセミコロンを入力する必要がありません。また、インタープリターは、ステートメントの評価結果として
nullを戻します。(ステートメントが値を戻さないよりは、このほうが優れています。) - replからは、オブジェクトのprivateフィールドへのアクセスに関して制限はありません。
Javaプログラマー初心者にとっては、これらの相違点は混乱の元になるかもしれません。経験を積んだプログラマーであれば、制限が緩くて楽だと感じることでしょう。いずれにしても、DynamicJavaは堅固であり、非常に便利なソフトウェア・プロダクトです (おまけにそれはフリーです)。
今回は、再コンパイルすることなくJavaプログラムの式やステートメントを対話式に評価するためのツール ("read-eval-print-loop"、repl) について紹介しました。また、GUI開発において、また豊富なJava APIを手軽に探求するための手段として、replがいかに重要かを示しました。
-
Jython 2.1 は、高度でダイナミックなオブジェクト指向Pythonの1つの実装であり、これによって任意のJavaプラットフォームでPythonを実行できます。(その先駆となった言語については、Python 2.2 を参照。)
-
developerWorks Linuxゾーンにある 「魅力的なPython:JPythonとPython for .NETの内幕」の記事では、JPythonの内部が紹介されています。
- Javaソース・インタープリターDynamicJava もご覧ください。
- Eric Allenの 「Javaコードの診断」 の全記事をご覧ください。
- バグ・パターン: Javaプログラムで頻発しがちなバグの分析と修正
- 「宙ぶらりん複合型」バグ・パターン: ヌル・ポインター例外の最もよくある原因を鎮圧する
- 「ヌル・フラグ」バグ・パターン: 例外状況を表すフラグとしてヌル・ポインターを使うことを避ける
- 「二段たどり」バグ・パターン: 再帰的なクラス・キャストという概念上のエラーを最初から克服する
- うそつきビューのバグ・パターン: GUIの最良の友になってうそつきビューを暴き出しましょう
- 破壊工作データのバグ・パターン: 隠れたデータ爆弾が奇妙なクラッシュの原因かもしれません
- 破綻したディスパッチのバグ・パターン: 引き数のアップ・キャストによって、不正確なメソッドの呼び出しを修正する
- Javaコードのパフォーマンスを向上させる: 末尾再帰変換はアプリケーションの速度を向上させる可能性はあるが、すべてのJVMで可能な操作ではない
- 正しいメソッド呼び出しのためのRecorderによるテスト: メソッドの呼び出しを順序正しく行うために、ユニット・テストのためのRecorderを記述する
- 型詐欺師のバグ・パターン: タグを使用したオブジェクトの型の区別は、ラベルの貼り違えにつながる可能性がある
- クリーンアップ・コード散在バグ・パターン: リソースの獲得および解放を同時に実行する
- 虚偽の実装というバグ・パターン: 第1回: 前提とした不変条件がインターフェースの破壊を招くこともある
- 虚偽の実装というバグ・パターン: 第2回: 表明とユニット・テスト - バグを除去するための実行可能なドキュメンテーション -
- 「みなし子スレッド」バグ・パターン: マスター・スレッドが自滅し、その他のスレッドが生き残っていると、どうなるか?
- 「テスト可能な」アプリケーションの設計: 以下に示す7つの原則は、テストを念頭においてコード設計を行う際のもとになるものです
- 拡張可能アプリケーションの設計 第1回: ブラック・ボックス、オープン・ボックス、またはガラス・ボックス: どんな場合にどれがふさわしいか?
- 拡張可能アプリケーションの設計 第2回: ガラス・ボックスはどんな時、どこで、どのように最もよく機能するかを調べる
- 拡張可能アプリケーションの設計 第3回: ブラック・ボックスはどんなとき、どこで、どのように最もよく機能するかを調べる
- 拡張可能アプリケーションの設計 第4回: S式がどのように軽量のブラック・ボックス拡張性を実現するか
- 深さ優先visitorと、破綻したディスパッチ: このVisitorパターンの変形を使えば、コードをより簡潔にできます
- 仕様という綱渡り: 明確に定義された仕様が、ソフトウェア・システムにとってなぜ重要か
-
developerWorks Java technologyゾーンには、Javaに関するその他の参考文献があります。
Eric Allen氏は、コーネル大学でコンピューター・サイエンスと数学の学士号を取得しています。現在は、ライス大学の博士課程の大学院生としてJavaプログラミング言語チームに加わっています。学位を終了するためにライス大学に戻るまでは、Cycorp, IncでJavaソフトウェア開発主任として勤務していました。彼は、JavaWorldで「Java Beginner」ディスカッション・フォーラムの司会者も務めています。主な研究対象は、Java言語のセマンティック・モデルと静的分析ツールの開発であり、いずれもソース・レベルとバイトコード・レベルで研究しています。Ericは、NextGenというプログラミング言語 (汎用ランタイム型によるJava言語の拡張版) のためのライスのコンパイラーの開発にも携わってきました。連絡先は、eallen@cs.rice.edu です。