BBH
-Biz Branding Hub-
投稿日 : 
2020/06/26
更新日 : 
2020/06/26

プログラムの変更を楽にする書き方

なぜプログラムの変更が大変になるのか

プログラムの変更が大変になるのは、設計が良くないからです。
設計が良くないとは、以下のことを指します。
・メソッドが長い
・クラスが大きい
・引数が長い

なぜ設計が崩れていくのか

今設計が良くないプログラムももともとそうだったわけではありません。
何度も変更が重なったことによって設計が崩れていくのです。
最初はちょっとしたif文や引数の追加だったかもしれません。
しかし、そういった変更が何度も積み重なることによってプログラムの構造は複雑化していきます。
そうして変更がしにくいプログラムが出来上がるのです。

変更しやすいプログラムを書く方法

変更しやすいプログラムを作成するためには、先述した
・メソッドが長い
・クラスが大きい
・引数が長い
を防ぐようにすればよいです。
そのためには、以下のポイントに気を付ける必要があります。

段落分けを行う

まずは、メソッドを処理の単位で段落分けしていきます。
例えば以下のプログラムは段落分けができていません。

段落分けできていないプログラム

List<String> result = Arrays.asList("A", "B", "C");
result = this.sort(result);
result = this.paging(result);
return result;

この処理では、
結果の取得
ソート
ページング
の3つの処理を行っています。
しかし、上記の書き方ではどこが区切りなのかわかりにくいです。

以下のようにまとまりごとに分けることで構造が見やすくなります。

段落分けされたプログラム

List<String> result = Arrays.asList("A", "B", "C");

result = this.sort(result);

result = this.paging(result);

return result;

説明変数を利用する

次にローカル変数を用途ごとに用意します。
現在の書き方だと、全ての結果を result に入れてしまっています。
このままだと result への変更がすべての個所に影響を与えてしまいます。

例えば、以下のようにした場合、ソートやページングの結果が崩れてしまいます。

resultへの意図せぬ変更

List<String> result = Arrays.asList("A", "B", "C");

result = this.sort(result);

result = this.paging(result);

result.add("Z");  // 意図せぬ変更

return result;

この程度の長さのプログラムであれば問題にはならないかもしれません。
しかし、構造複雑になってくるとどこの変更によって結果がおかしくなってしまったのかを追うのが難しくなります。
このように同じ変数を使い回すことで様々な個所から代入されてしまうことを破壊的代入と呼びます。

これを防ぐためには、それぞれの結果を格納する変数を分けてやることです。

それぞれの変数を用意する

List<String> rawResult = Arrays.asList("A", "B", "C");

List<String> sortedResult = this.sort(rawResult);

List<String> paigingResult = this.paging(sortedResult);

rawResult.add("Z");  // 結果に影響が出ない

return paigingResullt;

またそれぞれの変数を用意することによって、処理の意図が明確になります。
例えば、 sortedResult という名前からは、それがソートされた結果であることが容易に想像できます。
このように処理の意図や流れを明確にするための説明的な名称をつけられた変数のことを説明変数と呼びます。
説明変数を使用することで処理の意図が明確になり、また破壊的代入を防ぐこともできます。

独立した処理をメソッドに切り出す

独立した処理はメソッドに切り出すことで、変更の影響をそのメソッド内にとどめることができます。
例えば、以下のように変更することで、結果取得処理を切り出すことができます。

結果取得処理を切り出し

public List<String> getResult() {
    List<String> rawResult =this.getRawResult();

    List<String> sortedResult = this.sort(rawResult);

    List<String> paigingResult = this.paging(sortedResult);

    return paigingResullt;
}

// 切り出された結果取得処理
private Liist<String> getRawResult() {
    return Arrays.asList("A", "B", "C");
}

このように切り出すことで、例えば結果をDBから取ってくるように変更したい場合などの影響をメソッド内に閉じ込めることができます。
内部処理をどんなに変更しても<String>が返ってきさえすればよいからです。

DBから取得するように変更

private List<String> getRawResult() {
    List<String> rawResult = new ArrayList<>();
    rawResult = // DBから取得する処理
    return rawResult;
}

このように独立した処理をメソッドに切り出すことをメソッドの抽出と呼びます。

関心毎に特化したクラスを作成する

メソッド抽出を行っていく事で処理の独立性は高まっていきます。
しかし、それは同じクラス内での話になります。
例えば、ページングの処理を別のクラスで使用したいという場合、同じような処理をそのクラスに書く必要があります。
これは変更の容易性を損ないます。
または、ページングの処理をpublicにして外部から参照することでも可能です。
しかし、これはどの処理がどのクラスにあるのかがわかりにくくなり、プログラム全体の見通しが悪くなります。
ソート処理がそのクラスに存在するのは、たまたまそのクラスが最初に作られたからであり、そこにあるべき必然性は特にありません。

処理の再利用性を高めるためには、その処理に特化したクラスを作成するのが良いです。
ページングの処理の再利用性を高めたいのであれば、ページングクラスを作ります。

ページングクラス

public static class Paging {
    
    public static List<T> paging(int size, int page, List<T> target) {
        int pageFirst = (page - 1) * size;
        int pageLast = pageFirst + size;
        return target.subList(pageFirst, pageLast);
    }
}

クラスを独立させることで、いろいろなクラスからその処理を呼び出せるようになります。
クラスを独立させるためには、関心毎に特化させることが重要です。
関心事に特化させるためにはそのクラスの関心事を一言で言い表してみましょう。
一言で言い表せない場合、そのクラスは分割する余地があるということになります。

例えば、例のクラスでは以下の関心事が存在しています。
・結果の取得
・ソート
・ページング
これは3つの関心ごとを持っており、一言で言い表すことができません。
そのためクラス分割させる余地があったのです。

このように特定の関心毎に特化したクラスのことをドメインオブジェクトと呼びます。

Profile

管理人プロフィール

都内でITエンジニアをやってます。
変遷:中規模SES→独立系SIer→Webサービス内製開発
使用技術はその時々でバラバラですが、C#、AWSが長いです。
どちらかと言うとバックエンドより開発が多かったです。
顧客との折衝や要件定義、マネジメント(10名弱程度)の経験あり。
最近はJava+SpringBootがメイン。

Recommend