【まとめ記事】Javaアプリの品質を保つため、Javaソースコードを書くための工夫

Javaアプリの不具合を防ぎたいから、品質が良いJavaソースコードを記述したいです。

Javaソースコードを正しく書くための、ちょっとした工夫について、ご紹介しています。

  • 変数の寿命を限定する書き方。
  • 肯定的な条件を優先して条件分岐する書き方。
  • クラス名の付け方。

品質を意識してソースコードを書くようにしたら、アプリの品質が良くなります。

目次
  1. 少しでも正しくJavaソースコードを書くための工夫
  2. Javaソースコードにおける、プログラムの処理の流れ
  3. Javaプログラミングにおける変数
  4. Javaプログラミングにおける条件分岐
  5. Javaプログラミングにおける、クラスや変数の名前の付け方
  6. Javaプログラミングにおける「値を設定するメソッド」「値を取得するメソッド」
  7. Javaプログラミングにおける、メソッドの再利用
  8. Javaプログラミングにおける、インタフェースと抽象クラスと具象クラスの違い

少しでも正しくJavaソースコードを書くための工夫

Javaソースコード

どんなに気をつけてソースコードを記述していても、うっかり間違ってしまう場合があります。

ソースコードに「間違い」があると、アプリが仕様通りに動きません。

Javaアプリの品質を保つためにも、なるべく正しくJavaソースコードを記述したいです。

ここではJavaソースコードを、少しでも正しく書くための工夫について、ご紹介したいと思います。
参考にしていただけたら幸いです。

なお、Javaソースコードの書き方について、より詳しい記事を読みたい方は、次の記事などを参考にしてください。
「頑健なJavaプログラムの書き方(Writing Robust Java Code)」。※リンク切れ、2016年1月当時。

Javaソースコードにおける、プログラムの処理の流れ

垂直な線だとわかりやすい、プログラムの「正常処理の流れ」

Javaソースコードにおいて、プログラムの「正常処理の流れ」は、なるべく垂直な線であるとわかりやすいです。

ここで言う垂直な線とは、次のようなイメージの線です。
青線が、「正常処理の流れ」を示しています。

正常な処理の流れ
プログラムの正常処理の流れ

ここで言う垂直な線とは、正常処理に関するソースコードの字下げが浅いことを、意味します。

例えば、if文の分岐処理による字下げが浅いほど、「正常処理の流れ」は真っ直ぐな線に近づきます。

「正常処理の流れ」を示す線が真っ直ぐであるほど、プログラムの「正常処理の流れ」を理解しやすいです。

なお、ソースコードの字下げについては、4段階以下にすることが推奨されています。

線の長さが短い方が良い、プログラムの「正常処理の流れ」を示す線

プログラムの「正常処理の流れ」を示す線については、線の長さが短い方が良いです。

ここで言う線の長さが短いとは、正常処理に関するソースコードの行数が少ないことを、意味します。

一般的に、ソースコードの行数が多いほど、プログラムの複雑さや結合度が高くなります。
そのため、不具合が発生しやすくなる、と言われています。

多くの場合で、ソースコードの行数が少ない方が、プログラムの処理を理解しやすいです。

プログラムの処理を理解しやすいソースコードは、間違ったソースコードを記述する可能性を低くする、と思います。

プログラムの「正常処理の流れ」、
その詳細については、以下の記事を参照してください。

なるべく垂直な一本の線が良い、Javaプログラムの正常処理の流れ

Javaプログラムの正常処理の流れは、上から下へ向かう垂直な一本の線に見えます。

適切な場所で例外に対処する、例外を受け取る書き方

プログラム実行中に例外が発生した場合、多くの場合で、その例外に対処する必要があります。

ソースコードの「どの場所」で例外を受け取り、対処するか、それぞれの場合に応じたソースコードの書き方があります。

Javaソースコードにおいて、例外を受け取る書き方には、以下の三つの書き方があります。

  1. メソッド内で、例外を受け取る書き方。
  2. 別のメソッドで、例外を受け取る書き方。
  3. 例外処理を中止する書き方。

例えば1番の「メソッド内で例外を受け取る書き方」では、

例外が発生したメソッド内のcatch文で、例外通知を受け取ります。
そこで例外処理に対処します。

この場合の「例外処理の流れ」は、以下のようなイメージです。
赤線が「例外処理の流れ」を示します。

メソッド内の例外処理の流れ
メソッド内で例外を受け取る書き方

Javaソースコードで例外通知を受け取る書き方、
その詳細については、以下の記事を参照してください。

Javaソースコードにおいて、例外通知を受け取る書き方

Javaソースコードで例外を受け取る場合、catch文で例外通知を受け取ります。 そこで例外処理を行ないます。

Javaプログラミングにおける変数

波括弧 {} を使用して、変数の寿命を限定する

変数の寿命は、波括弧 { } を使用して決めることができます。

{  //波括弧その1
    UserInfo userInfo = new UserInfo(username);
    ...
}
{  //波括弧その2
    ...
    session.setAttribute("boardname", userInfo);//コンパイルエラーになる 
}

上記のソースコードにおいて、

波括弧その1 { } を用いて、userInfo変数の寿命(有効範囲)を限定しています。

よって、波括弧その2 { } 内でuserInfo変数を使用できない、というコンパイルエラーになります。
このコンパイルエラーで、変数を間違って使用したというプログラムの記述ミスに気づきます。

以上のように波括弧を使って、変数の寿命(有効範囲)をコントロールすることは、変数が間違った場所で使用されるのを防ぎます。

これは、ソースコードの品質を保つのに役立ちます。

波括弧 {} を使用して変数の寿命を限定する、
その詳細については、以下の記事を参照してください。

Javaソースコードにおいて、波括弧 {} を使用して変数の寿命を限定する書き方

Javaソースコードにおいて、波括弧 { } を使用して、変数の寿命(変数の有効範囲)を限定できます。 変数の有効範囲を限定することは、変数が間違った場所で使われること…

Javaプログラミングにおける条件分岐

ユーザー操作が作り出した情報、if文に指定する条件

if文に指定する条件は、通常、ユーザー操作によって生み出された情報です。
例えば、ユーザーがマウスをクリックした、という情報です。

if文に指定する条件は、アプリ実行時に作成される情報と言えます。

Javaソースコードでif文を書くことは、アプリ実行時のユーザー操作に対応した処理を行うためになります。

Javaソースコードの条件分岐、
その詳細については、以下の記事を参照してください。

ユーザー操作によって発生した情報、Javaソースコードの条件分岐

Javaソースコードのif文に指定する条件は、一般的に、ユーザー操作によって発生した情報です。 例えば、マウスをクリックしたという情報があります。

わかりやすい、if文の条件指定が肯定文であること

if文の条件指定において、肯定文で条件を書く方がわかりやすい、と思います。
その場合、肯定的な条件を優先する書き方があります。

例えば、

「; セミコロン」だけ、つまり何もせず処理を続ける、という書き方があります。

if (super.action.equals("copy")){
    ;  //何もせず処理を続ける
}
else{
    super.servlet.log("無効なアクション=" + action);
    return errors;
}

上記のソースコードでは、

最初に、action変数が文字列"copy"と同じであるなら処理を続ける、と書きます。
次に、そうでないなら無効な命令であると判断してreturn文を実行する、と書きます。

if文の条件分岐において、「action変数が文字列"copy"と同じである」と肯定文で書きたいので、「; セミコロン」を使っています。

プログラム処理の文脈を、肯定文で理解したい。
「~であるなら、~する」と理解したい。

このように意識している場合、if文の条件指定において、肯定的な条件を書いてみましょう。

If文で肯定的な条件分岐を優先する、
その詳細については、以下の記事を参照してください。

Javaソースコードにおいて、if文で肯定的な条件分岐を優先する書き方

Javaソースコードのif文において、肯定的な条件分岐を優先する書き方があります。 肯定的な条件が成り立つ場合、セミコロン(;)だけを記述する書き方です。

エラーを示すログ情報を出力しておく、if文の「その他の場合」

if文で処理を分ける場合、
その他の場合」において、エラーを示すログ情報を出力しておくと、デバッグするのに役立ちます。

「その他の場合」は、アプリの仕様において絶対に実行されない場合です。
もしも「その他の場合」が実行されたら、ソースコードに間違いがある、と言えます。

「その他の場合」が実行された時に、エラーを示すログ情報を出力しておくと、プログラムの不具合に気づきやすいです。

そして、エラーを示すログ情報を元に、プログラムの不具合の原因部分を探せます。

プログラム処理を中断するエラーを示す、RuntimeExceptionクラス

public ActionForward perform(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response){
    ...
    MessageBoardForm boardForm = (MessageBoardForm)form;
    String action = boardForm.getAction();
    if (action.equals("forward")){
        int boardID = boardForm.getBoardID();
        ...
    }

中略

    else{
        //プログラム仕様外の処理なので、プログラム処理を中止する
        throw new RuntimeException("invalid action=" + action);
    }
    ...
}

「その他の場合」が実行された時、プログラム仕様外の動作のため、プログラム処理は中断されることになります。

エラーによってプログラム処理を中断する場合は、
非チェック例外であるRuntimeExceptionクラスを使用します。

「その他の場合」のif文、
その詳細については、以下の記事を参照してください。

Javaソースコードにおける、プログラムで実行されない「その他の場合」のif文

if文の「その他の場合」においては、エラーを示すログ情報を出力する処理を、記述しましょう。 ログ情報を見ることで、プログラム処理でどういう不具合が起きているか、わ…

近いほど「プログラム処理の流れ」がわかりやすい、if文とelse文の距離

if文とelse文の距離が近いほど、if文の分岐処理を一覧できます。
よって「プログラム処理の流れ」を一覧できてわかりやすい、と思います。

public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response){

    TimesheetForm timesheetForm = (TimesheetForm)form;
    String action = timesheetForm.getAction();

    if (action.equals("init")){
        //TimesheetFormに初期値を設定するため、
        //timesheetAction.do?action=init が必要
        //timesheetFrame.htmlから、ここを呼び出す
        return this.init(mapping, timesheetForm, request, response);
    }
    else if (action.equals("update")){
        return this.update(mapping, timesheetForm, request, response);
    }
    else if (action.equals("show")){
        return this.show(mapping, timesheetForm, request, response);
    }
    else{
        throw new IllegalArgumentException("invalid action=" + action);
    }
}

上記のソースコードでは、最初のif文から最後のelse文の終わりまで、15行あります。

15行くらいなら、距離は近いと言えます。よって、if文の分岐処理を一覧できます。

if文からelse文までの距離を短くする方法として、それぞれの場合で行なう処理を、一つのメソッドにまとめる方法があります。

上記のソースコードで言うと、

this.init()メソッドやthis.update()メソッドなどで処理をまとめているので、if文からelse文までの距離が短くなっています。

Javaソースコードにおける条件分岐の距離、
その詳細については、以下の記事を参照してください。

if文とelse文の距離、Javaソースコードにおける条件分岐の距離

Javaソースコードにおけるif文とelse文の距離は、近いほうが分岐処理を一覧できます。

Javaプログラミングにおける、クラスや変数の名前の付け方

名詞にすること、クラス名の付け方

クラス名を名詞にすると、クラスの役割を「分類している感じ」がします。
よって、わかりやすいと思います。

例えばMIDIシーケンスを作成する、というクラス名について、

「作成する・Make」を、「作る人・Maker」という名詞(役割名)にして、クラス名を付けます。

名詞にしたクラス名の例。
MidiSequenceMakerクラス。

役割名とスーパークラス名から名付ける、サブクラス名の付け方

サブクラス名の付け方として、

サブクラスの役割名 + スーパークラス名にすると、わかりやすいと思います。

例として、サブクラス名PostActionの名付け方について、お話したいと思います。

public class PostAction extends Action {
    ....
}

スーパークラス名はActionです。
Actionは、ユーザのマウス操作などに対する処理を行う、ということを示します。

サブクラスの役割名は、Postです。
Postは、投稿という役割を示します。

以上から、PostActionというサブクラス名を付けます。

このように名付けることで、
PostActionサブクラスは、投稿に関係するマウス操作の処理を行う、ということを示します。

わかりやすいクラス名の付け方、
その詳細については、以下の記事を参照してください。

クラス名を名詞にする、Javaプログラミングで、わかりやすいクラス名の付け方

Javaプログラミングのクラス名は、名詞にします。 クラス名を名詞にすると、クラスの役割を「分類している感じ」がして、わかりやすいです。

一般的な英単語を使うこと、変数名の付け方

一般的な英単語の意味は、既存の英語辞書などで調べやすいです。

なので一般的な単語を使って変数名を付けると、その単語の意味から、「変数の働き」を理解するのに役立ちます。

例えば、変数名がmappingの場合、
mappingという英単語の意味を、以下のような辞書やドキュメントで調べられます。

  • 英語辞書
  • コンピュータ用語辞典
  • Java APIドキュメント

これらの辞書やドキュメントにより、
mapping変数は、「キー」と「値」の対応を管理している変数だと、予想できます。

わかりやすい変数名の付け方、
その詳細については、以下の記事を参照してください。

一般的な英単語を使う、Javaプログラミングで、わかりやすい変数名の付け方

Javaプログラミングの変数名を付ける際、一般的な英単語を使いましょう。 英単語が表す意味から、「変数の働き」を予想しやすいからです。

Javaプログラミングにおける「値を設定するメソッド」「値を取得するメソッド」

「値を設定するメソッド」「値を取得するメソッド」の名前の付け方は、おおよそ決まっています。

メンバ変数の値を設定するsetterメソッド

メンバ変数の値を設定するメソッドの名前は、英単語のsetを使います。
このようなsetから始まるメソッドを、setterメソッドと言います。

setterメソッドのソースコードの例。

public class Person {
  private String name; // 名前
  private int age; // 年齢

中略

  // nameのsetterメソッド
  public void setName(String name) {
    this.name = name;
  }

  // ageのsetterメソッド
  public void setAge(int age) {
    this.age = age;
  }
}

メンバ変数の値を取得するgetterメソッド

メンバ変数の値を取得するメソッドの名前は、英単語のgetを使います。
このようなgetから始まるメソッドを、getterメソッドと言います。

getterメソッドのソースコードの例。

public class Person {
  private String name; // 名前
  private int age; // 年齢

中略

  // nameのgetterメソッド
  public String getName() {
    return this.name;
  }

  // ageのgetterメソッド
  public int getAge() {
    return this.age;
  }
}

オブジェクトの属性や状態を変更するミューテータメソッド

setterメソッドは、ミューテータメソッドです。

ミューテータメソッドとは、オブジェクトの属性や状態を変更するためのメソッドです。

ミューテータメソッドには、例えば、
オブジェクトのデータを不正な値から保護できる、という利点があります。

オブジェクトの内部状態を観測するアクセッサメソッド

getterメソッドは、アクセッサメソッドの一つです。

アクセッサメソッドとは、オブジェクト内部のメンバ変数に、外部から読み取りや書き込みを行うメソッドです。

アクセッサメソッドの一つであるgetterメソッドには、例えば、
メンバ変数のデータを保護しつつ、メンバ変数のデータを観測できる、という利点があります。

「値を設定するメソッド」「値を取得するメソッド」、
その詳細については、以下の記事を参照してください。

値の設定と取得をまとめて管理する、Javaプログラミングにおける「値を設定するメソッド」「値を取得するメソッド」

「値を設定するメソッド」「値を取得するメソッド」を、適切に作成してください。 そうしたら、メンバ変数の値を保護できます。

Javaプログラミングにおける、メソッドの再利用

多くのメソッドを再利用しやすい、クラスを継承する方法

多くのメソッドを再利用する方法の一つに、クラスを継承する方法があります。

例えば、スーパークラスのメソッドについて、50%以上のメソッドを再利用したい場合、
クラス継承という方法でメソッドを再利用します。

4個のメソッドがあるスーパークラスです。

public class SuperClass{
    public void superMethod1(){...}
    public void superMethod2(){...}
    public void superMethod3(){...}
    public void superMethod4(){...}
}

サブクラスにおいて、スーパークラスの次の2個のメソッドを再利用したい場合、

public void superMethod1(){...}
public void superMethod2(){...}

スーパークラスの2個のメソッドを再利用したサブクラスは、次のようになります。

public class SubClass extends SuperClass{
    //
    // 継承によって、2個のメソッドを再利用します。
    //  public void superMethod1(){...}
    //  public void superMethod2(){...}
    //

    public void subMethod900(){...}
    public void subMethod901(){...}
}

多くのメソッドを再利用しやすいクラス継承という方法、
その詳細については、以下の記事を参照してください。

Javaプログラミングで、多くのメソッドを再利用しやすいクラス継承という方法

Javaプログラミングで、クラスを継承する方法を用いたら、多くのメソッドを再利用しやすいです。

抽象クラスを使う、独自処理をするメソッドの実装を義務付けて、共通処理をするメソッドを再利用する方法

抽象クラスを使って、共通処理をするメソッドを再利用する方法があります。

抽象クラスは、「メソッドの宣言」と「メソッドの実装」を持つクラスです。

この抽象クラスを継承することによって、
独自処理をするメソッドの実装を義務付けて、抽象クラスの共通処理をするメソッドを、再利用できます。

以下のようなAnimal抽象クラスを作成します。

abstract class Animal {

  // 共通の処理をするメソッド
  public void eat() {

    System.out.println("食べる");

    // 独自の処理をするメソッドを呼び出す
    makeSound();
  }

  // 独自の処理をするメソッドを宣言する
  abstract void makeSound();
}

Animal抽象クラスでは、

共通処理をするeat()メソッドを定義します。

独自処理をするmakeSound()メソッドを、宣言します。

Animal抽象クラスを継承したクラスを、作成します。
以下のようなDogクラスとCatクラスを作成します。

class Dog extends Animal {

  // 独自処理をするメソッドを定義する
  void makeSound() {
    System.out.println("ワンワン");
  }
}

class Cat extends Animal {

  // 独自処理をするメソッドを定義する
  void makeSound() {
    System.out.println("ニャー");
  }
}

Animal抽象クラスの共通処理をするeat()メソッド内から、
DogクラスとCatクラスに定義した独自処理をするmakeSound()メソッドを、呼び出します。

以下のようなMainクラスを作成します。

class Main {
  public static void main(String[] args) {
  
    // Animal型の変数にDog型のオブジェクトを代入
    Animal dog = new Dog();

    // Animal型の変数にCat型のオブジェクトを代入
    Animal cat = new Cat();

    // 共通の処理をするメソッドを呼び出す
    dog.eat(); // 「食べる ワンワン」を出力する
    cat.eat(); // 「食べる ニャー」を出力する
  }
}

例えばdog.eat()メソッドが実行された際、
共通処理をするeat()メソッドから、独自処理をするmakeSound()メソッドが呼び出されます。

そして、「食べる ワンワン」を出力します。

Dogクラスは、共通処理をするeat()メソッドを再利用しています。

Catクラスについても、同様に、共通処理をするeat()メソッドを再利用します。

メソッドの実装を義務付けて、メソッドを再利用する方法、
その詳細については、以下の記事を参照してください。

Javaプログラミングで、メソッドの実装を義務付けて、メソッドを再利用する方法

Javaプログラミングで抽象クラスを使うと、以下の二つの事を同時に実現できます。 クラスのメソッドの実装を義務付け。 クラスのメソッドを再利用。

少ないメソッドを再利用しやすい、メンバ変数のメソッドを呼び出す方法

クラスAとクラスBの結び付きが弱い場合に、メソッドを再利用したい時、

クラス内で保持するメンバ変数のメソッドを呼び出して、メソッドを再利用する方法があります。

以下に、NoteListクラスが、ArrayListクラスのメソッドを再利用している例を示します。

public class NoteList{

    private List noteList = new ArrayList();


    public void add(int index, Note note){
        this.noteList.add(index, note);
    }

    public Note get(int index){
        return (Note)this.noteList.get(index);
    }

    public int size(){
        return this.noteList.size();
    }
    
    
    public void setOctave(int octave){
        for (int i = 0; i < this.noteList.size(); i++){
            Note note = (Note)this.noteList.get(i);
            note.setOctave(octave);
        }
    }
    
中略

}

以下のように、noteListメンバ変数のメソッドを呼び出すことで、
NoteListクラスは、ArrayListクラスのメソッドを再利用しています。

this.noteList.add(index, note);
this.noteList.get(index);
this.noteList.size();

少ないメソッドを再利用しやすい、メンバ変数のメソッドを呼び出す方法、
その詳細については、以下の記事を参照してください。

Javaプログラミングで、少ないメソッドを再利用しやすい、メンバ変数のメソッドを呼び出す方法

Javaプログラミングで作成したクラス内で保持するメンバ変数から、メソッドを呼び出す方法があります。 メンバ変数のメソッドを呼び出すことで、再利用したいメソッドを限…

Javaプログラミングにおける、インタフェースと抽象クラスと具象クラスの違い

インタフェースと抽象クラスと具象クラスには、以下の点に違いがあります。

  • 「メソッド宣言」の部分
  • 「メソッド実装」の部分

インタフェースと抽象クラスと具象クラスにおいて、
「メソッドの宣言」と「メソッドの実装」の存在を比べると、以下の違いがあります。

  • インタフェースは、「メソッドの宣言」だけがあります。
  • 抽象クラスは、「メソッドの宣言」と「メソッドの実装」があります。
  • 具象クラスは、「メソッドの実装」だけがあります。

以上の違いより、

例えば具象クラスは、「メソッドの実装」だけが存在しているクラスです。
なのでクラス的に見ると、「完成された物という感じ」がします。

インタフェースと抽象クラスと具象クラスの違い、
その詳細については、以下の記事を参照してください。

Javaプログラミングにおける、インタフェースと抽象クラスと具象クラスの違い

インタフェース、 抽象クラス、 具象クラス、 これらの違いには、何がありますか? 「メソッドの宣言」または「メソッドの実装」において、個数に違いがあります。