日々常々

ふつうのプログラマがあたりまえにしたいこと。

Javaであまりしないコーディング

Java Advent Calendar 2014 - Qiita の9日目です。9日ったら9日です。 なんか難しいエントリが多い中ですが、空気を読まずに軽めでいきます。

Javaでは色々なコードの書き方ができるけど、実際あまりやらないよなーって思うコーディングについて、やらない理由を無理矢理書いてみた。

決して「やってはいけないコーディング」と言う意味ではないです。単に「私はあまりしない」程度で、一般的な(?)業務開発でもあまり見ない、くらいの位置付け。理解した上でやる分には全く問題無いですし、そう言うもんだと思って使っても良いとは思います。

複数変数を同時に宣言する

int i, j = 2, k;

同じ型の変数(フィールドでもローカル変数でも)は同じ文で宣言できます。 けど、あまり使ってるのは見ません。 同じ型でしか使えないし、フィールドだとJavaDocコメント書き辛いし、初期化式書きにくいし、まとめる単位どうするのとか面倒な話になるし。

これが禁止されてるコーディング規約とかはよく見る。 禁止するほどじゃないとは思う。テストとか、閉じた中で使う分には少し楽なこともあるし。 でもたいして変わらないので禁止でもいいかー。

代入式を一文でいっぱいやる

int i = 1;
int j = 2;
int k = i = j = 3; // ←これ

i,j,kは全部3になります。 これもあまり使わないけど、代入演算子(=)が値を返すことと、右側から順番に評価されることは知っておかないと事故るかも。 初期化時や値を返したいときに代入したいことはたまにあるので、その時は出番。forの中とかでたまに使う気がする。 演算順に迷うならとりあえず括弧で包んでおけば良いです。その方が意図は明白になるし。 でも順番に悩むようなコードを書かない方が良いとは思います。

それほど便利なわけでもないので、普段はあまり意識されてなかったりする。 だからif文でbooleanと比較するのを間違って代入しちゃってバグったりするわけだけど。

if (value = true) return; 

このコードはtrueの場合にreturnしたいんだろうけど、valueに常にtrueが代入されて、その値が返されるので、いつでもreturnすることになる。このバグを起こさないためにifの「booleanリテラルは先に書け」なんてのをたまに見るけれど、そんなことよりbooleanなら比較とかしようと思わなきゃ良いだけです。

// これで「書き間違えてもコンパイルエラーになる!べんり!!」って言ってないで……
if (true = value) return; 

// こう、trueとかfalseとか書くのやめましょうよと。
if (value) return; 

変数名がvalueとかダメな感じがひしひしとはするのですが、ここはスルーしてくださいまし。

ただのブロック

void method() {
  {
    int i = 0;
    // iのスコープはこの中だけ
  }
  // ここではiは見えない
}

if文やfor文などと一緒に使われない、ただのブロック。 メソッドコンストラクタの中で登場するやつね。インスタンスイニシャライザとは別。 知っているとたまに便利だけど、あまり使わない。 無駄に階層が深く見えるし、メソッド内でこのようなことをしたいと思ったら、たぶんメソッドが大きすぎる。

用途が無いように見えるかもしれないけど、コピペで要素を増やしたい時とかには使えなくも無い。こんな感じで。

List<Entry> list = new ArrayList<>();
{
    // 1つめ
    Entry entry = new Entry();
    entry.setValue("A");
    list.add(entry);
}
{
    // 2つめ
    Entry entry = new Entry();
    entry.setValue("B");
    list.add(entry);
}

同じ変数名が使えるからコピペできる!コピペして変数名書き換え忘れるって地味なバグがなくなる!べんり!!……いやちょっと待てよ、と。まぁメソッド作れってのが正道ですね。

気を取り直して、ブロックを使うとちょっと変なコードが書ける。こんな。

int i = 1;
HOGE: {
  if (i > 0) break HOGE;
  System.out.println(i);
}

まあラベル自体あまり使わないんだけど、ブロックにはラベルがつけれるので、ラベルを指定してbreakすることができる。 ちなみにラベル無しブロックをbreakすることはできないから注意が必要だけど、そもそもブロックを使わないし、ましてやブロックからbreakしないとできない処理って思いつかない。この手のも素直にメソッド作ってreturnすればいいだけだし。

変なコード書いてないで素直に書いた方がよっぽど良いと思う。

ラベルを付ける

A: while(true) {

  B: while(true) {
    break A;
  }
}

多重ループとかを書くと登場することもたまにあるラベルさん。 これ自体はそれほど珍しくも無いけど、やっぱあまり書かない。 「ここはラベルを使ってbreak(もしくはcontinue)だ!」と思って書いたコードも、なんか暫くしたらそんなの無くなってることが多い。とは言え使う時は使います。

で、あまり使わない……と言うか全く使わないのが、ループ以外に対するラベル。こんなの。

A: System.out.println("Aラベル付き");

文法上は付けれる、けど用途が無い……。 もしかしてgotoの名残かな?Javaにgoto文は無いけど、一応キーワードにはなっていて、変数などには使用できないです。

switch文

switch (hoge) {
  case FOO: // ...
  case BAR: // ...
  default: // ...
}

enumが使えようとも、Java SE 7以降ではStringが使えようとも、なんだかんだであまり使わないswitch文さん。私だけ?

使う時は使う気はするのだけれど、null入ったらNullPointerExceptionが出たりしてenumやStringでは使い辛い実情。 switchブロックの中はcaseラベルを並べるだけなのでシンプルになるかとおもいきや、いちいちbreakを入れないと次に行っちゃうので、結局if-else文を並べるよりも行数が多くなって、見通しが悪くなるのも一因。

enumを使う時はswitchで分岐しようとはするのだけど、なんだかんだでenumに処理持たせたり、処理オブジェクト返させたりする方が見通しが良くなって、書きはじめはswitch使おうとしたのに……ってなることが多い印象があったりなかったり。

無名クラスでのインスタンスイニシャライザ

new ArrayList<String>(Arrays.asList("X")) {{
  add("A");
  add("B");
}};

これは微妙なライン。使う場合はがっつり使う。

括弧が二重になってるように見えるけど、実際は無名クラスの中に定義しているインスタンスイニシャライザです。 インスタンスイニシャライザはコンストラクタよりも前に動作するけれど、継承した無名クラスの場合は継承元コンストラクタが実行されてからになります。 と言うことで、このコードの場合はArrayListクラスのコンストラクタで要素が追加されてからaddされるので、[X,A,B]の順で入ります。

このタイミングは結構大事で、もし無名クラスのインスタンスイニシャライザが親クラスのコンストラクタよりも前に動くなら、折角addした値がコンストラクタでの状態の初期化により消えてなくなることになります。 イニシャライザやコンストラクタインスタンスの状態に依存した処理を行うのは避けた方が良いのはインスタンスの状態が安定する前(作成中)の状態で触ることになってしまうからだったりしますが、無名クラスでのインスタンスイニシャライザは親クラスのコンストラクタの後に動きますので、インスタンスの状態は安定してると言えなくは無いので、大丈夫な感じだったりします。

そもそもですが、イニシャライザ自体をあまり見ないので、存在を知らない人も結構いるとかいないとか。 無名クラスはコンストラクタを持てないのでイニシャライザを代わりに使うこともあるけど、不慣れなメンバーが多いなら使用は慎重になった方がいいんじゃないかなぁとか。

あまり使わないと言いつつ、某モックライブラリを使う時とかは多用するので、構文としては押さえておきたいところ。

外からのインナークラスのインスタンス生成

class Hoge { class Fuga { class Piyo {} } };

Hoge.Fuga.Piyo piyo = new Hoge().new Fuga().new Piyo();

しないよね。 そもそもインナークラスもそれほど見ないけど、それはそれとして。 インナークラスのインスタンスを欲しくなる時点で何かおかしい気配がするし、ましてや生成するなんてね。 とは言え作れなくは無いので、何か無理矢理インスタンスを生成して使いたい時は対象インスタンスにドットで続けてnewすればいいです。

このコードではメソッド内のイメージで、ローカルクラスHogeを定義してますけど、ローカルクラスを見ることはもっと無いです。

複数行コメント

// この形式のコメントはよく使うけど

/*
   この形式のコメントは
   あまり
   使わない
*/

コメントアウトとかする時も複数行選択して Command+/ とかするのですよ。 見辛いとか理由にしようとしたけど、実際はIDEでコメントは文字色変わるからそうでもない。

https://twitter.com/takesi_yosimura/status/542533768729219072

あーあー、たしかにー。

セミコロンレスなんちゃら

はい。

まとめ

もっといっぱい書けると思ってたけど、思ったより思い浮かばないものでした。。。 いくつかは「自分はあまり書かない/見ない」ってだけで、世間には溢れてるかも。