第21回 Javaらしいプログラミングを目指す

<<前へ  戻る  次へ>>


ここまでの説明で、かなり高度なことをJavaプログラムでやらせることが可能となりました。
Java入門講座最終回となる今回は、良い意味で「Javaらしい」プログラミングとは何かについて考えていきましょう。

■Javaの特徴とは
第0回でも説明したように、Javaはオブジェクト指向言語です。1つのオブジェクトで事足りることもありますが、殆どの場合複数のクラスを使用して処理を行わせることになります。これまで多用してきたSystem.out.println()メソッドも、厳密にいうならば
「java.langパッケージ内に含まれたSystemクラスで定義されているSystemクラスの静的変数である標準入力ストリーム(java.io.InputStreamクラスオブジェクト)のインスタンスメソッドprintln()」
という意味がありますが、実際に使う際はそこまで気を回す必要はありません。
これに限らず既存のクラスを使用する場合、そのクラスの使い方さえ分かっていればその中で何が行われているか分からなくても構わないのです。

■結合度
Javaのクラス間の関係を示す言葉として結合度(coupling)があります。これは或るクラスが別のクラスに依存している度合いを意味します。
お互いのクラスの詳細に依存せず、public等で公開されている部分のみを使っていれば結合度は低くなりますが、非公開部分にも関わってくるような深い関係を持っている場合結合度は高くなります。
レベル種類説明
1データ結合相互にデータの要素だけを
やり取りしている状態
2スタンプ結合受け渡しされるデータ内に
使用されないものが含まれている状態
3制御結合下位のメソッドに対して
処理を分ける為の制御データを
渡している状態
4ハイブリッド
結合
メソッド間で受け渡しされるデータに
状況によって違う意味を持たせたり
複数の意味を持たせている状態
5共通結合グローバルデータでメソッド間の
値を受け渡ししている状態。
6内容結合自身の持っている変数が他のクラスの
メソッド内から直接操作される状態。
レベル3の制御結合までは不可避ですが、それ以上の高レベル結合度は極力控えましょう。
結合度が高い=依存度が高いクラスは、一方の仕様変更や修正が広範囲に拡散してしまい、作業量が膨大になります。結合度が低く、他のクラスからの独立性が高い疎結合のクラスこそが望ましいクラスです。

■凝集度
結合度がクラス間の依存度を示しているのに対し、凝集度は或るひとつのクラスについて適切な構造をしているかを示します。
プログラム内のクラスが適切に分割されていないと、互いに関連するメソッドや変数があちこちに分散していざという時に見つけられなくなったり、仕様変更や修正の必要性が発生した場合影響範囲が大きくなってしまいます。これが凝集度が低い状態です。
レベル種類説明
7機能的凝集クラスは1つの機能を
実行するようになっている。
6逐次的凝集1クラス内に複数のメソッドが含まれているが、
それらのメソッドは関連性が高く、分散する意味がない
5通信的凝集或るデータの処理をする機能が集まっているが、
処理の順序には制限がない
4手順的凝集共通するデータを扱う訳ではなく
単に処理の順番になっているものを幾つか
まとめて1メソッド内に収めた状態
3一時的凝集初期設定やエラー処理等のように
ある時点でのみ実行するメソッド内に
いくつかの機能が含まれた状態
2論理的凝集クラス内に含まれるメソッドが
呼び出し元からの指示で選択・実行される
1偶発的凝集何の共通点もないメソッドや変数が
無秩序に集められた状態
凝集度は結合度とは逆にレベルが高い方が望まれます。

結合度が低く(疎結合であり)且つ凝集度が高いプログラムが良いJavaプログラムです。そうした良いプログラムの利点は次の通りです。
低結合度・高凝集度プログラムの利点
仕様変更や修正の必要が発生した際、影響箇所が広がらない

プログラムは一度作って動いてしまえば終わりというものではなく、改修・改善が求められるものが多いのです。ですがそれが強結合度・低凝集度のプログラムであった場合、或る改修・改善に伴って必要となる修正が広がってしまいます。その為修正漏れなどのミスが生じる危険性もありますし、またミスを確認するテストの手間も増してしまいます。
逆に弱結合度・高凝集度のプログラムであれば、或る箇所の修正に伴って必要となる修正を必要最小限に抑えられます。修正する箇所が少なくなれば修正の際のミスも起こりにくくなりますし、テストの手間も少なく済みます。

■弱い結合度と高い凝集度の両立の為に
では、弱い結合度と高い凝集度が両立した良いプログラムを作成する為には、どうしたら良いでしょうか?

○事前に入念なクラス設計を行う
まずは何よりも、プログラム前によく考えることです。
自分がそのプログラムで何をやらせたいか、その実現の為にはどうすればいいかをまとめていきます。
事前に考えず作りながら考えていこうとすると、プログラムの軸がないままになってしまいます。そんな状態で後から要素を足していくと、完成予想図を作らずに建て増しを重ねた建築物のように「作った本人にしか構造が分からない」プログラムになってしまいます。自分ひとりで作っているのであればそれでもいいかもしれませんが、巨大プロジェクトの一環となるプログラムがそのようでは周囲の人々に迷惑がかかります。
これが更にひどくなると「作った本人にさえどうしてこう作ったのか説明ができない」プログラムにまで悪化します。そうならないようにする為にも、プログラミングの実作業の前に方針を固めておかなければなりません。

○インスタンス変数の参照・操作はメソッドを通じて
これまでクラス内・メソッドの外で宣言されたstaticを付与しないインスタンス変数やstaticを付与した静的変数はどちらもメソッド内で宣言されたローカル変数同様に参照・操作していました。
OperateFields.java
public class OperateFields extends java.lang.Object{
    public static void main(String [] args){
        SubClass sc = new SubClass();
        System.out.println("*** 生成直後 ***");
        System.out.println("bData   = " + sc.bData);
        System.out.println("iData   = " + sc.iData);
        System.out.println("strData = " + sc.strData);
        System.out.println("dData   = " + sc.dData);
        sc.bData = false;
        sc.iData += 500;
        sc.dData -= 1000;
        sc.strData = "edited";
        System.out.println("*** 値変更後 ***");
        System.out.println("bData   = " + sc.bData);
        System.out.println("iData   = " + sc.iData);
        System.out.println("strData = " + sc.strData);
        System.out.println("dData   = " + sc.dData);
    }
}

class SubClass extends java.lang.Object{
           boolean bData;
           int     iData;
           String  strData;
    static double  dData;
    
    //コンストラクタ
    SubClass (boolean bData, int iData, String strData){
        this.bData   = bData;
        this.iData   = iData;
        this.strData = strData;
        this.dData   = 50.0;
    }
    
    SubClass (){
        this(true, 50, "Untitled");
    }
}
このプログラムはpublicクラスのmain()メソッド内でSubClassクラスのインスタンス生成を行い、このクラスのインスタンス変数に直接アクセスしています。
これが今までやってきた方法ですが、これが一体何が問題があるのでしょうか?
まず、第0回で触れたカプセル化についての説明を思い出してください。あなたという人間の情報をクラス化するにあたり、銀行の口座番号と暗証番号を公開する人はいないでしょうし、徒にeメールアドレスを公開してしまったら迷惑メールに悩まされます。そうかといってなんでもかんでも非公開にしてしまったら今度は電話やeメール等を受けることもできなくなります。アクセス修飾子を使って外部クラスからのアクセス可否を切り替えることができるのですが、プログラムの動作状況によって動的に切り替えるといったことはできません。
操作内容を事前にチェックできず、データの破壊も許容してしまうのも問題です。このプログラムを実行してみてください。
OperateFields2.java
public class OperateFields2 extends java.lang.Object{
    public static void main(String [] args){
        SubClass sc = new SubClass();
        System.out.println("*** 生成直後 ***");
        System.out.println("bData   = " + sc.bData);
        System.out.println("iData   = " + sc.iData);
        System.out.println("strData = " + sc.strData);
        System.out.println("dData   = " + sc.dData);
        sc.bData = false;
        sc.iData += Integer.MAX_VALUE;
        sc.dData -= 1000.0;
        sc.strData = "";
        System.out.println("*** 値変更後 ***");
        System.out.println("bData   = " + sc.bData);
        System.out.println("iData   = " + sc.iData);
        System.out.println("strData = " + sc.strData);
        System.out.println("dData   = " + sc.dData);
    }
}

class SubClass extends java.lang.Object{
           boolean bData;
           int     iData;
           String  strData;
    static double  dData;
    
    //コンストラクタ
    SubClass (boolean bData, int iData, String strData){
        this.bData   = bData;
        this.iData   = iData;
        this.strData = strData;
        this.dData   = 50.0;
    }
    
    SubClass (){
        this(true, 50, "Untitled");
    }
}
iDataの値が正数に正数を加算しているのに、その結果が負数となっていることに注目してください。ラッパークラスIntegerの静的変数MAX_VALUEはint型の最大値(=2147483647)なので、下線部の加算はこの最大値を超えてしまい、結果が異常になっています。
変数への直接アクセスを禁止して、尚且つ最大値以上の加算/最大値以下の減算を行わせないようにするには、こうします。
OperateFields3.java
public class OperateFields3 extends java.lang.Object{
    public static void main(String [] args){
        SubClass2 sc2 = new SubClass2();
        System.out.println("*** 生成直後 ***");
        System.out.println("bData   = " + sc2.getBData());
        System.out.println("iData   = " + sc2.getIData());
        System.out.println("strData = " + sc2.getStrData());
        System.out.println("dData   = " + sc2.getDData());
        sc2.setBData(false);
        sc2.addIData(Integer.MAX_VALUE);
        sc2.setDData(sc2.getDData() - 1000.0);
        sc2.setStrData("");
        System.out.println("*** 値変更(1)後 ***");
        System.out.println("bData   = " + sc2.getBData());
        System.out.println("iData   = " + sc2.getIData());
        System.out.println("strData = " + sc2.getStrData());
        System.out.println("dData   = " + sc2.getDData());
        sc2.addIData(Integer.MIN_VALUE);
        sc2.setStrData("X");
        System.out.println("*** 値変更(2)後 ***");
        System.out.println("bData   = " + sc2.getBData());
        System.out.println("iData   = " + sc2.getIData());
        System.out.println("strData = " + sc2.getStrData());
        System.out.println("dData   = " + sc2.getDData());
        sc2.addIData(Integer.MIN_VALUE);
        System.out.println("*** 値変更(3)後 ***");
        System.out.println("iData   = " + sc2.getIData());
    }
}

class SubClass2 extends java.lang.Object{
    private boolean bData;
    private int     iData;
    private String  strData;
    private static double  dData;
    
    //コンストラクタ
    SubClass2 (boolean bData, int iData, String strData){
        this.bData   = bData;
        this.iData   = iData;
        this.strData = strData;
        this.dData   = 50.0;
    }
    
    SubClass2 (){
        this(true, 50, "Untitled");
    }
    
    //Setterメソッド
    public void setBData(boolean bData){
        this.bData = bData;
    }
    public void setIData(int iData){
        this.iData = iData;
    }
    public void setStrData(String strData){
        this.strData = strData;
    }
    public void setDData(double dData){
        this.dData = dData;
    }
    //Getterメソッド
    public boolean getBData(){
        return this.bData;
    }
    public int getIData(){
        return this.iData;
    }
    public String getStrData(){
        return this.strData;
    }
    public double getDData(){
        return this.dData;
    }
    //addメソッド
    public void addIData(int iData){
        long lResult;
        lResult = (long)this.getIData() + (long)iData;
        if (lResult > Integer.MAX_VALUE){
            lResult = Integer.MAX_VALUE;
        }
        else if (lResult < Integer.MIN_VALUE){
            lResult = Integer.MIN_VALUE;
        }
        this.setIData((int)lResult);
    }
}

実行結果がどうなるか、iDataの変化を確認してください。

以上でJava入門講座をひとまず終わりとさせていただきますが、Javaはまだまだ奥深いプログラミング言語です。後回しにしている説明すべき項目はまだ残っています。
そうした後回しにした部分や、本入門講座で曖昧にしていた部分は今後開講を予定しているステップアップ編で説明していく予定です。
それまでは今まで説明した箇所の復習や、サンプルプログラムのテストや更なる改良をしながらお待ちいただければ幸いです。
質問はこちらへお願いします(件名はそのままで)。

<<前へ  戻る  次へ>>