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

第4回コードフォーマットのルール

導入

前回に続いてコーディングのルールを学習しましょう。今回はコードフォーマットのルールです。コードフォーマットとは、コードの体裁のことです。適切に体裁が整えられたコードはとても読みやすく、それによってバグを防ぐことさえできます。仮に自分だけが使うコードであってもコーディングルールを定めて守り続けましょう。コーディングルールの守られたコードは、将来のあなたを過去から支援してくれる強力な味方になります。

展開

コードフォーマットのルールについて、小さなものから順に紹介していきます。コードを書く際に、まずは小さな部分を丁寧に書き上げましょう。何行にもわたるコードを見渡すとき、細かいところが不統一だと全体としても落ち着きません。

基本的には自由

Java言語のソースコードはとても自由に書くことができます。一行にずらずらっと続けて書いても良いし、数行の空白をあけて、広々と空間をとりながら書いても良いのです。例えば、次のコードfreewheeling.pdeのように自由奔放な書き方でもきちんと動きます。

freewheeling.pde
int c = 1;boolean P;
for(int i = 0; i<10; i++)for(int j = 0; j<10; j++){
P = true;if(c == 1)P = false;
else for(int n=2; n<c; n++)
if (c % n == 0){P = false;
break;}if (P)fill(255,0,0);
else fill(255,255,255);rect(j*10,i*10,10,10);c++;}

さてしかし、こんなに自由奔放に書かれたコードを読むことになった「別の人」は大変なめにあいます。一目見ただけでは何をするプログラムか分からないことでしょう。

プログラミングの世界には「3日後の自分は他人」という格言があります。自分で書いたコードでも、3日経つと意図を忘れてしまっていてコードを読むのに苦労するよ、という戒めの意味があります。将来読んで分かりにくくないように体裁よくコーディングしようと勧めているわけです。将来読んで分かりやすいコードは、一定のルールにしたがって書かれたものです。ルールが将来のあなたの時間を確保し、ストレスから守ってくれると思えば、このルールは是非学び、使っておくべきです。ルールがあなたに自由を与えるのです。

[作業] freewheeling.pdeが何をするsketchか読み取ってみましょう。5分間で結構です。それ以上の時間をかけるのは時間の無駄ですから。

式は自然な形で書きましょう

コードの書き方は様々です。目的に応じて書き分けます。最もポピュラーなのが「実行速度を優先するのか、コードの読みやすさを優先するのか」という書き分けです。おおよそ実行速度を優先すると、コードは読みにくくなりがちです。本来の計算の意図以外の要素が入り込むからです。

最も単純な例として次のコードSpeedOrClear.pdeを紹介しましょう。

SpeedOrClear.pde
//スピード重視
int x = 2;
x = x << 1;
println("x * 2 = " + x);

//分かりやすさ重視
int y = 2;
y = y * 2;
println("y * 2 = " + y);

スピード重視の計算式では変数xに格納した整数値を1ビット左へシフトしています。これは2進数の数値を左へ1ビットシフトすることが、数値を2倍することと同じであることを利用しています。分かりやすさ重視の計算式では、そのものずばり、変数yの値を2倍しています。

ほんのわずかでも実行速度を高めたいならば、ビット演算のような「一般の人には普通ではない」計算方法を使っても良いでしょう。1回だけの計算では差は出ませんが、これを数多く繰り返せば差が出ます。

しかしながら、数値の計算式の中に突然ビット演算子が現れれば「どっきり」するのが人情です。よほど速度にシビアな計算を行うのでなければ、素直にかけ算の記号*を用いた方が誤解が無いでしょう。

[作業] ビットシフト演算子を使って整数値の2を10回2倍する操作を100万回行う時間を測定しましょう。乗算で同じことをするのとどれほど時間の違いがあるでしょうか。時刻はmillis()メソッドで取得できます。

不等式では右ほど大きく

数値の大小を条件判断文で判定する場合、大きな値であることが期待されるものを右側に置くようにしましょう(sketch GoodIsLess.pde⁠。どちらが大きくなるか全く予想がつかない場合にも、比較演算子の<を使って式をたてましょう。文化的に、習慣的にその方が自然である人が多いと思われるからです。

GoodIsLess.pde
int a = 1;
int b = 2;

// Good example
if (a < b) {
  println("That's natural!");
} else {
  println("That's funny!");
}

// Bad example
if (a > b) {
  println("That's natural!");
} else {
  println("That's funny!");
}

カッコを使って式の曖昧さを解消しましょう

早速ですが次の例題2問を検討してください。

例題1

整数変数a, bに代入された数値の平均を取りたいとします。次の式が正しい結果を得られるように変更してください。

Divide_Conquer.pde
x = a + b / 2 

このまま計算すると、除算が優先されabの半分」の合計をとることになります。題意にそった結果を得るためには、式を次のように書き換える必要があります。

x = (a + b) / 2

果たして正しい平均値が得られるはずです。

例題2

次の式を正しく計算するコードを書いてください(図4.1⁠⁠。

ちょっとした分数計算
画像

先ほどの例題とよく似た形です。今度は分母にも注意が必要です。次のようにコードを書くと正しく計算されません。

x = 9.53 + 5.32 / log(1.81) * sqrt(7.52)

理由は2つあります。

  • 分子の加算をカッコで囲む必要があります。加算より除算が優先されるからです。
  • 分母全体をカッコで囲むか、平方根の項の直前の乗算を除算に変更する必要があります。カッコが無ければ、対数の直前の除算演算子で表現したかった分数が平方根の直前で終了してしまうからです。

正しく計算するためには次のように書きます。

x = (9.53 + 5.32) / (log(1.81) * sqrt(7.52)) ...(A)

あるいは次のように書きます。

x = (9.53 + 5.32) / log(1.81) / sqrt(7.52) ...(B)

式(A)の方が例題の式の形をイメージしやすいので、(A)の書き方をお勧めします。煩雑にならない程度に、カッコを几帳面に使いましょう。

[作業] 次の式を誤解の無いように、分かりやすいコードで書いてください。

               273 + t
   v = 287.03 ---------
                  p

      v = 体積[m^3]
      p = 圧力[Pa]
      t = 温度[℃]

複雑な式は分割しましょう

さて、先ほどの式(図4.1)程度の簡単な式なら問題になりませんが、次のような複雑な式ではどうでしょうか。

かなり複雑な式(減衰振動の式)
画像

この式に仮の値を代入して計算するsketchは次のようになります(sketch Divide_Conquer.pde⁠。

float x  = 0;
float c  = 1;
float m  = 2;
float t  = 3;
float x0 = 4;
float k  = 5;
float v0 = 6;
//----------(1)
x = exp(-c/(2*m)*t)
      * (
           x0 * cos((sqrt(4*m*k - pow(c,2)))/(2*m)*t ) 
         + (2*m*v0 + c * x0)/(sqrt(4*m*k - pow(c,2)))
         * sin( (sqrt(4*m*k - pow(c,2))/(2*m) * t ) )
        );
println("x  = "+x);

float val1 = sqrt(4*m*k-pow(c,2));
float val2 = val1 / (2*m);
float x2 = 0;
//----------(2)
x2 = exp(-c/(2*m)*t)
    * (x0 * cos(val2 * t) + (2*m*v0 + c*x0)/val1 * sin(val2 * t) );
println("x2 = "+x2);

(1)も(2)も同じ計算をしています。どちらが式を読み取りやすいでしょうか。

(1)の式が正しく記述されているかを確認するには骨が折れそうですね。(2)はプログラミングの金言分割統治を実行しています。

分割統治とは、プログラムが大きくて取り扱いにくい場合、小さくて取り扱いやすい部分に分割することです。小さい部分が十分に取り扱えれば、それをまとめた大きなプログラム全体も取り扱えるという仕組みです。

(1)の式をよく見ると式の中には共通した形が数カ所あります。それらを(2)の式のようにまとめてみましょう。(2)の式は随分見通しが良くなりました。val1val2の定義も直前にありますから迷うことは無いでしょう。

このように、込み入ったコードは分割統治により、小さな部分を確実に処理して、次のステップに進む仕組みになるように書きましょう。

適切に改行し、適切にインデントをしよう

今回冒頭で紹介した悪夢のようなコードを再度取り上げます。

freewheeling.pde
int c = 1;boolean P;
for(int i = 0; i<10; i++)for(int j = 0; j<10; j++){
P = true;if(c == 1)P = false;
else for(int n=2; n<c; n++)
if (c % n == 0){P = false;
break;}if (P)fill(255,0,0);
else fill(255,255,255);rect(j*10,i*10,10,10);c++;}

このコードはまず改行が不適切です。はるか昔……、メモリが黄金よりも貴重だった頃には、こんなコードが珍重されたものでした。1バイトでもソースコード用のメモリを節約し、魔法のようなプログラムを作る「ワンライナー職人」がいたものでした。しかし、今はそんな時代錯誤をする必要はありません。趣味でワンライナーを楽しむのは良しとしても。

freewheeling.pdeに適切と思われる改行を与えたものが次のsketchNewLines.pdeです。

NewLines.pde
int c = 1;
boolean P;
for(int i = 0; i<10; i++)
for(int j = 0; j<10; j++){
P = true;
if(c == 1)P = false;
else
for(int n=2; n<c; n++)
if (c % n == 0){P = false;break;}
if (P)fill(255,0,0);
else fill(255,255,255);
rect(j*10,i*10,10,10);
c++;
}

まだ明瞭とは言えませんが、さきほどよりはかなり良くなりました。このコードをさらに読みやすくするにはインデント(字下げ。行の頭を右へずらすこと)を加えます。

当たり前のようですが、意外とおろそかにされがちです。ただインデントするのではなく、コードのまとまりごとに、そして実行のレベルが同じ部分はなるべく同じ深さでインデントを与えます。

そのように手を入れたのが次のsketch GoodIndents.pdeです。

GoodIndents.pde
int c = 1;
boolean P;
for(int i = 0; i<10; i++)
  for(int j = 0; j<10; j++){
    P = true;
    if(c == 1)
      P = false;
    else
      for(int n=2; n<c; n++)
        if (c % n == 0){
          P = false;
          break;
        }
    if (P)
      fill(255,0,0);
    else
      fill(255,255,255);
    rect(j*10,i*10,10,10);
    c++;
  }

このコードを実行された方は、連載第1回の演習問題3番のsketchと同じであることに気づくでしょう(連載第1回演習3の解答のコード⁠。

インデントを加えることでかなり読みやすくなっていますが、まだ足りません。そこで必要なのが次の項目です。

[ことば]インデントとネスティング
インデント(indent)は単に行頭を下げることを意味します。ネスティング(nesting)はある構造の中に別の構造を含むことを意味します。Java言語でインデントは通常ネスティングしている場合です。区別できるようにしてください。

ifやforのコードブロックは必ずブレースで囲むこと

if文やfor文の抱えるコードブロックは必ずブレース{}で囲みましょう。どこからどこまでがそのifforの抱えるコードブロックなのかを明確にするためです。また、終端のブレースはif文やfor文のインデント位置にそろえましょう。

次のような書き方はやがて自分の首を絞めます(sketch BadBlock1.pde, BadBlock2.pde⁠。

BadBlock1.pde
int a = 0;
int b = 1;
//First version
if (a==b)
  println("a = b");
//Second version  
if (a==b)
  println("a = b");
  println("a is equals b");
BadBlock2.pde
int a = 0;
int b = 1;
//First version
if (a==b) {
  println("a = b");}
//Second version  
if (a==b) {
  println("a = b");
  println("a is equals b");}

BadBlock1.pdeでは最初、a=bが成り立ったら、式を表示して表明したかったと考えられます。やがて次のバージョンを書いたとき、式と言葉で表明したくなったとします。そしてSecond versionのように書き足したのです。その結果、条件が成り立たないのに成り立ったような表示をしてしまっています。

最初から次のGoodBlock.pdeのように書いていれば、このようなミスをせずに済んだのです。BadBlock2.pdeではそのようなミスが起きにくいでしょうが、コードを読む人は面食らうでしょう。

GoodBlock.pde
int a = 0;
int b = 1;
//First version
if (a==b) {
  println("a = b");
}
//Second version  
if (a==b) {
  println("a = b");
  println("a is equals b");
}

for文も、コードブロックはブレース{}で囲みましょう。ブロック内のコードがたった1行だけでもです。

[作業] NewLines.pdefor文やif文のコードブロックに不足しているブレースを書き加えましょう。そうするとインデントを容易に入れられるようになります。コードの動きを変えないように注意深く書き加えてください。最後は連載第1回演習3の解答のコードと比較して確認しましょう。

自然に書きましょう

例えばループの使い方ひとつをとっても、次のsketch GoodAndBadLoopExamples.pde//Bad exampleのように「ややこしく」書かないようにしましょう。

GoodAndBadLoopExamples.pde
int i = 0;
int n = 10;
float[] array;
array = new float[n];

// Bad example 1
while(i<=n-1) array[i++]=1.0;

// Bad example 2
for(i=n;--i>=0; )array[i]=2.0;

// Good example 3
for(i=0;i<n;++i) array[i]=3.0;

// Good example 4
for(i=0;i<array.length;++i) array[i]=4.0;

// Good example 5
i=0;
while(i<array.length){
  array[i]=5.0;
  ++i;
}

// Good example 6
for(i=0;i<array.length;++i){
  array[i]=6.0;
}

// Check code 1
for(i=0;i<array.length;++i) {
 println("array["+i+"] "+array[i]);
}

// Check code 2
i=0;
for(float val : array){
  println("array["+i+"] "+val);
  ++i;
}

次のことがややこしいと思うはずです。

Bad example 1

i<=n-1は単にi<nの方が良いでしょう。array[i++]=1.0i++を上手に使っているのですが、 次のように2段階に分けた方がより明瞭です。

array[i]=1.0;
++i;
Bad example 2

for(i=n;--i>=0; )は素直にfor(i=0;i<=n;++i)と書いた方が分かりやすくなります。カウントが減る方向で処理する理由がないならなおさらです。せめてfor(i=n-1; 0<=i; --i)のように書きましょう。

forwhileといった制御構造には、読みやすい、慣例的なお決まりの書き方があります。配列を巡回するためのループならば添字は0から始めて「要素数より小」の条件で終了するのが自然です。

ひとつずつカウントする用途に、while文を使うのがそもそも不自然ですが、あえて使うならば//Good example 5のように書くべきです。ついでに補足するならば、//Good example 6のように、for文のコードブロックは、たった1行でもブレースで囲むべきです。

なるべく一画面に納めよう

なるべく一画面に納めようとするルールは利用するハードウエアの画面サイズに依存するため、かっちり決めるのもどうかと思います。しかし、人間が一目で見渡す事のできる範囲は意外と狭いのです。コードの文字を読み取りつつ、コントロールの効く範囲について、⁠Javaルールブック ~読みやすく効率的なコードの原則』では「メソッドは20行以下に、クラスは600行以下に」という目安を示しています。

メソッド20行の基準はおそらく、最も古い時代のキャラクタ端末、例えばDECのVT-100などを基準にしたのではないでしょうか。これは横80桁から130桁、縦24行という恐ろしく狭い表示範囲です。今でもUNIXの端末エミュレータを使うと、この画面範囲で利用することになります。狭いのですが、この分量程度のメソッドが、人間にとって取り扱いやすいのだというのです。実際、これよりも長くなるメソッドは分割した方が取り扱いやすいでしょう。

ひとつのクラスの大きさ、行数は600行程度が良いと提案されています。A4用紙1枚に40行程度とすると15枚分程度の分量です。これがひとつのクラスの機能を把握するために適当な最大量だとしています。概して妥当な値だと思います。

演習

演習1(難易度:easy)

作業で取り上げた次の式は、温度と圧力に応じた気体の体積を計算する式です。

v = 287.03 × (273 + t) / p
  v = 体積[m^3]
  p = 圧力[Pa]
  t = 温度[℃]

温度tに応じて体積がどう変化するかを表すグラフをディスプレイウインドウに表示してください。温度の範囲は0℃から100℃とします。圧力を0.5Paから1.5まで0.2Paごとに変えて6本のグラフを描きましょう。sketchはCalcVolume.pdeとしてください。

CalcVolume.pdeの実行結果
画像

演習2(難易度:middle)

素数のマスを赤く塗りつぶすsketchを、for文を使わずwhile文で書き直してください。ブレースの使い方や改行、インデントなどを適切に用いましょう。sketchはPrimeWithWhile.pdeとしてください。

演習3(難易度:easy)

次のデータの集合について、合計、平均、最小値、最大値、中央値を求めるsketchを作りましょう。sketchはStatistics.pdeとしてください。for文だけを使う場合、while文だけを使う場合、そしてなるべく繰り返し構文を使わずProcessingの便利なライブラリをフル活用する場合を書いてください。

x = {20,19,11,42,36,30,25,44,29,33}

まとめ

  • コードフォーマットのルールを学習しました。
  • コードは自然に、読みやすくシンプルに記述しましょう。
  • メソッドもクラスも極力短く書きましょう。

学習の確認

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

  1. コードフォーマットの大切さが理解できましたか?
    1. 理解できた。自分のコードに活かす決心をした。
    2. 理解できた。しかし、自分のコードに活かす必要を感じない。
    3. 理解できない。
  2. 読みやすいコードの書き方を理解できましたか?
    1. 理解できた。
    2. 理解はできるが必要を感じない。
    3. 理解できない。

参考文献

  • 『Javaルールブック ~読みやすく効率的なコードの原則』⁠電通国際情報サービス 監修、大谷晋平、米林正明、片山暁雄、横田健彦 著、技術評論社
    • Javaのコードを書く人は必携。ほぼ見開きでコンパクトながら意を尽くした解説がある。

演習解答

  1. CalcVolume.pde
  2. PrimeWithWhile.pde for文のありがたみを痛感しますね。
  3. Statistics.pde

おすすめ記事

記事・ニュース一覧