Processingで学ぶ 実践的プログラミング専門課程

第13回クラスはプログラミングを楽にする道具(1)

導入

今回から数回に渡ってオブジェクト指向について学習します。今回はクラスが何者で、クラスを使うと何が便利なのかを理解しましょう。そして、Processing、あるいはJava言語が用意してくれている便利なクラスを活用してみます。クラスという仕組みがあることで、私たちプログラマはとても楽ができるのです。

展開

クラスとは

クラスとは、プログラミングを楽にするために考案されたコードの部品化の方法です。

ずいぶん思い切った定義・解説と感じるかもしれません。きちんとした書籍やサイトでは、もっと一般的な見地から「クラス」の解説を行っています。ただ、この連載において皆さんにお伝えしたいのは「実践的」な部分ですので、こんなまとめ方にしました。

クラスを使ったばっかりに脂汗が出るようならば、どこか間違っているのです。コーディングが楽にならないようならば、そこにそのクラスが必要なのか、そのクラスに必要な機能があるのか、そのクラスの使い方を誤っていないかを考えましょう。

私はこの認識がとても大切だと思っています。

多くの場合、既に優れた先輩プログラマ達が便利なメソッドを持ったクラスを作成し公開してくれています。Processingそのものがそのような便利なクラスの集合体です。Java言語のパワーをより簡単に使えるよう、Processingというクラスパッケージにまとめてくれているのです。私たち後進の者は、効率良くソフトウエアを作成したいならば、まずこれらを調べ、学び、上手に使いこなしましょう。なにか解決すべき課題があるとき、既に用意されているメソッドで解決できないかを調べるのです。次に、クラスのインスタンスを生成して解決するような手段が無いかを調べます。それでもだめなら、自分でクラスを作成するという筋道をたどるべきなのです。⁠巨人の肩」へ登りましょう。

クラスを使うメリット

では、どうしてクラスを使うとプログラミングが楽になるのでしょうか。作成するソフトウェアの規模によって変わりますが、確実に言えるのは「コードが大きくなるほどクラスの恩恵が大きくなる」ということです。以下にその恩恵を、おおよそコードの大きさに応じて対応するように列挙します。

  • 既に便利な部品・仕組みがたくさんあるので、自分で新しく作らなくて済む。
  • 便利な部品・仕組みを後付けしやすいので、必要に応じた最低限のコード追加で済む。
  • 段取りと後始末を自動で行わせられるので、うっかりミスを防げる。
  • バラバラだった、あるいはゆるやかだった「コードのまとまり」を明確にできる。
  • 必要なコードを見て、不必要なコードを見ないようにできるため、コードが大きくなっても途方に暮れることを防げる。
  • デザインパターンやリファクタリングを学べば、プログラマ同士の意思共通理解手段を得られる。

挙げればまだたくさんあるのですが、上に述べたような様子でプログラマのストレスを軽減します。私に確実に言えるのは、プログラミング言語を学んでこれから学ぶようなことを学ばないのは、道具を買って手に入れても使い方を学ばないに等しいということです。

まず今回と次回は特殊な場合から紹介します。特殊から一般へ。入門書や文法書では逆の道を辿るのが正統でしょうが、あえてこの道を行きましょう。結局、まずよく使うのは以下の特殊な場合なのですから。もう少し具体的に言えば、既にできあがっているクラスの使い方を学びます。優れたプログラマ達が作ってくれた便利なクラスを使うことで、自分が新たにクラスを作る際のお手本を知ることもできます。

数学関数の充実したMathクラス

Mathクラスはとても便利です。ProcessingやJava言語を使ったことがあれば、各関数の値を取得するためにたくさん使ってきたことでしょう。このように便利な機能をパッケージ化してくれているクラスは大変ありがたいものです。

MathClassSample.pde
println("便利なMathクラス   (期待される値)   =  得られた値");
println("PI               ( = 3.14...)   = " + Math.PI);
println("3^2              ( = 9 )        = " + Math.pow(3,2));
println("-2の絶対値        ( = 2 )        = " + Math.abs(-2));
println("Pi[rad] -> [deg] ( = 180[deg] ) = " + Math.toDegrees(Math.PI));
println("sin(30[deg])     ( = 0.5 )      = " + Math.sin(Math.toRadians(30)));
println("IEEEに従った7/4の剰余( = -1 )     = " + Math.IEEEremainder(7.0,4.0));

MathクラスについてはJava言語のドキュメントを参照してください。Mathクラスにどんなメソッドやフィールドがあるかを知っておくことは必要ですし、メソッドにどんな命名がされているか、引数や戻り値がどのように仕組まれているかを学ぶと大変参考になります。

例えばMathクラスのほとんどのメソッドの引数や戻り値はdouble型です。実数を取り扱う場合、float型の方が良い理由があれば別ですが、float型は基本的に使うべきではありません。float型は有効桁数が少ないので、簡単に大きな誤差を生じやすいのです。このことについては以下のリンク先で丁寧に解説しましたのでお読みください。

もっと表面的なところでは、定数フィールドが一般に共通認識されている綴りできっちり大文字になっていることPIや、メソッドであっても略語で一般に共通認識されているものはメソッド名先頭から大文字であることIEEEremainderがわかります。ルールにきちんと則りながらも、必要なところではルールに縛られず理解しやすい素晴らしい命名です。

スタティック

さて、これまでにクラスについて勉強したことのある方は、Mathがクラスであることとや、Mathクラスの使われたsketchを読んで「あれっ?」と思いませんでしたか。

TypeInt.pde
クラスは使用する際に演算子`new`によってインスタンスを生成します。

この一文の理解に苦しむ人は、入門書に戻って良く理解しておいてください。このあとの話はそれを理解しているものとして進めます。Mathクラスのすべてのフィールドやメソッドは、newによってMathクラスのインスタンスを生成せずに使えます。このようなフィールドやメソッドのことをスタティックフィールドスタティックメソッドと呼びます。便利な機能を「ちょっとだけ」使いたいときに、いちいちクラスをnewして新しいインスタンスを生成して使うのは面倒です。ちょっと使うだけのインスタンスの名前を考えるのは無駄な仕事であり、いい加減な名前をつけてコードに残すとトラブルの元です。スタティックなクラスがこのように使えると、コードをタイプする量も、考える量も減らせます。次に紹介するIntegerクラスはインスタンスを生成して使うことができますが、スタティックな部分はMathクラスのように使えます。適宜使い分けて楽をしましょう。

基本データ型のラッパークラス

基本データ型とは、具体的にはint型やfloat型といった、プログラミング言語に用意されているシンプルな変数の型を意味します。Processingで使える基本データ型については、Java言語と同じですので参考書やWEBで確認してください。ラッパークラスとは、ラップ(Wrap)する、つまり薄い皮で包み込むようなイメージのクラスです。実際に薄いかどうかは別として、ここでは基本データ型に便利な機能を「被せて」クラスにしたもののことを指します。

単純で分かりやすい例はIntegerクラスです。Integerクラスは整数を取り扱うために用意された既存のクラスです。入門書ではあまり紹介されることが無いでしょう。基本データ型であるint型にはない便利な機能をたくさん持っています。

int型の変数で取り扱える値の範囲はどれだけだったっけ?」

基本データ型であるint型は単純に数値を代入したり、代入済みの値を参照したりするだけのものです。ここで例えば、コードの中で値のチェックのために必要になり、int型の変数で取り扱える値の範囲はどれだけだったっけ?」ということを考えてみましょう。

おもむろに書籍やWEBで調べればすぐに分かることですか? int型の変数のビット長が32ビットの符号つき整数で-2147483648~2147483647ですね。これを自分のコードの中に打ち込めば、確かにその値は変更されることは無いでしょう。……本当に?

例えばこんなコードです。

final int MIN = -2147483648;
final int MAX = 2147483647;

long val1     = 123;
long val2     = -2147483649L;

if ( MAX < val1 ) {
  println(val1 + "は、int型には大きすぎる値です。");
} else {
  println(val1 + "は、int型で取り扱えます。");
}

if ( val2 < MIN ) {
  println(val2 + "は、int型には小さすぎる値です。");
} else {
  println(val2 + "は、int型で取り扱えます。");
}

きちんと調べて打ち込んで、確認もしたんだから大丈夫!と言いたいところですが、その後このコードを触っているうちに、誤ってMINMAXの値を変更してしまうリスクがあります。そもそも、調べて打ち込んで確認した「つもり」なだけで、間違って打ち込むことだって有り得るのです。

こんなとき、Integerクラスを使っていると便利です。

UsingClassInteger.pde
long val1     = 123;
long val2     = -2147483649L;

println("Integerクラスで取り扱える最大値は" + Integer.MAX_VALUE);
println("Integerクラスで取り扱える最小値は" + Integer.MIN_VALUE);

if ( Integer.MAX_VALUE < val1 ) {
  println(val1 + "は、Integerクラスには大きすぎる値です。");
} else {
  println(val1 + "は、Integerクラスで取り扱えます。");
}

if ( val2 < Integer.MIN_VALUE ) {
  println(val2 + "は、Integerクラスには小さすぎる値です。");
} else {
  println(val2 + "は、Integerクラスで取り扱えます。");
}

たったこれだけのことですが、様々なことを勘案しながらコードを書く中で、きちんと保証された値を取得し利用できることは有り難いのです。int型を使わずIntegerクラスを使うのは、コードの「KISS原則(Keep it short and simple⁠⁠」に通じるところです。今回の例は「車輪の再発明」と表現するには小さなことですが、経験の浅い人はプログラマに限らず、既に立派な専用の道具が用意されているのに、自前の手間のかかる道具を利用し続けたりするものです。それはその人に非があるのではなく、単に使い方を知らないからです。もったいないことです。

「整数を2進数表示したいんだけど?」

整数を2進数表示することも学生ならば一度は経験した苦行では無いでしょうか? 桁が小さいうちは良いのですが、大きな数値だと面倒なものです。それをプログラミング言語でやってしまおうというのは、プログラマの三大美徳を備えた人なら当然のこと。しかし、次のようにしていたら、もったいないことです。

IntToBinary.pde
void setup(){
  int     a = 21;
  Integer b = 85;

  println("<<Reinventing the Wheel>>");
  showBinary(a);
  showBinary(b);
  showBinary(0);
}

void showBinary(int v){
  if ( v == 0) {
    println(v + " = (0)B");
  } else if ( 0 < v ){
    print(v + " = (");
    while( 1 <= v ){
      print(v%2);
      v /= 2;
    }
    println(")B");
  } else {
    println("Error!");
  }
}

これは、Integerクラスを使うとこれだけで済んでしまいます。

IntegerToBinary.pde
void setup(){
  int     a = 21;
  Integer b = 85;
  Integer c = -5;

  println("<<Existing Wheel>>");
  showBinary(a);
  showBinary(b);
  showBinary(c);
  showBinary(0);
}

void showBinary(Integer v){
  println(v + " = ("+Integer.toBinaryString(v) + ")B");
}

そもそもコンピュータは、単純だけど人間が行うととんでもなく手間のかかる仕事を代わりにやってくれる便利な機械です。コンピュータを使うからには人間はなるべく楽ができるべきです。コードの入力も楽であるべきです。なるべくコードを打たずに済ませるべきです。余分なコードはバグの温床です。ならば、ラッパークラスを使いましょう。

既存のクラスを積極的に使って楽をしよう

Mathクラスはどの入門書でも必ず紹介される便利なクラスです。これがクラスとして提供されていることは興味深いことです。やがて必要なクラスを自分で作ることを学びますが、例えばMathクラスの出力に細工をした新しいクラスを作りたい場合、Mathクラスをラップしたクラスを考えても良いのです。クラス、あるいはオブジェクト指向というのはプログラマの思考を助ける、また拡張する素晴らしい概念です。また、入門書では話を単純にするために、各種の値の取り扱いに基本データ型を多用します。しかしながら、実際にコードを組む場合には、なるべく基本データ型に追加機能が被せられたラッパークラスを用いるべきです。車輪の再発明を防ぐためでもありますし、デザインパターンやリファクタリングといったより実用的な概念を使えるようになると、ラッパークラスを使うメリットを実感するようになります。

演習

演習1 (難易度: easy)

二次方程式の解の公式を使って次の方程式を解くsketchを作りましょう。Mathクラスを上手に使ってください。sketch名はQuadraticFormula.pdeとしてください。

x^2 + 2x + 1 = 0 

演習2(難易度:middle)

IntToBinary.pdeshowBinaryメソッドに負の値を与えると、IntegerToBinary.pdeと同じ結果が得られるようにIntToBinary.pdeを変更しましょう。変換の処理にはIntegerクラスを使わないでください。実行結果の確認のために使うのは良しとします。sketchの名称はIntToBinary2.pdeとしてください。

まとめ

  • クラスを使うとプログラミングが楽になることを学習しました。
  • スタティックフィールドとスタティックメソッドについて学習しました。
  • MathクラスとIntegerクラスを例に挙げました。

学習の確認

それぞれの項目で、Aを選択できなければ、本文や演習にもう一度取り組みましょう。

  1. クラスを使うと楽になる意味が理解できましたか?
    1. 理解できた。気持ちよく納得した。
    2. 理解できた。しかし、今ひとつスッキリしない。
    3. 理解できない。
  2. スタティッククラスやスタティックメソッドを使うとプログラミングが楽になる意味が理解できましたか?
    1. 理解できた。気持ちよく納得した。
    2. 理解できた。しかし、今ひとつスッキリしない。
    3. 理解できない。

参考文献

  • 『本格学習Java入門[改訂新版⁠⁠佐々木整 著、技術評論社
    • Java言語の基本的な文法が良くまとめられています。また、大変広い範囲をカバーしているので、はじめてJava言語に触る人が、Javaがどんな言語なのかを知るために役立つ資料になるでしょう。
  • 『Javaで学ぶコンピュータ数学』(平田敦 著、カットシステム)
    • 高校生から学べるコンピュータ数学のテキストです。P.23から「実数型のしくみ」としてコンピュータ特有の問題をまとめています。

演習解答

  1. QuadraticFormula.pde
  2. IntToBinary2.pde

おすすめ記事

記事・ニュース一覧