拡張性のあるプログラミング

<!-- 拡張性のあるプログラミング -->

## はじめに

自分の書いたコードを他の人に見てもらう時、プログラムの拡張性について言われることがあります。

今回はそれらを具体的にまとめ、今後の参考にしようと思います。

## プログラムの拡張性とは？

拡張性のあるプログラムとは、要件にある程度の変更があっても、プログラムの修正が少なくて済むようなプログラムのことを指すこととします。

### データ項目が増減しても問題ない

例えば以下のようなコードを見てみましょう。ある配列の中身の総和を取って出力するRubyのコードです。

```ruby
array = [1, 2, 3, 4, 5]
sum = 0
for i in 0..4 do
  sum += array[i]
end
puts sum
```

このコードの特徴は、配列の要素数が5であることが前提となっている点です。

このようなことをしていると、データを変更して配列の長さが変わったときに、プログラムの修正が必要になります。長くなった時は最後まで要素が取得できず総和がおかしくなるだけで済みますが、短くなった場合は配列の範囲外を参照することになりエラーが発生します。

そのため、例えば以下のように、配列の長さを実行時に取得するようにしておくと、配列の長さが変わっても問題なくなります。

```ruby
array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
sum = 0
for i in 0..array.length - 1 do
  sum += array[i]
end
puts sum
```

そもそも、このような要件に対しては組み込み関数を用いるべきです。

```ruby
array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
sum = array.sum
puts sum
```

どちらにせよ、大きさが変化する恐れのあるデータに対しては、その大きさに依存したコードを書くことは危険です。

### ハードコーディングが回避されている

先ほども書きましたが、変数などに直接値を代入しておくことを「ハードコーディング」といいます。

ハード、つまり「固い」コードは、臨機応変さがないわけです。

もっと例を見てみましょう。

電卓を作っているとします。単純に足し算メソッドを作りましょう。ユーザの入力を2つ受け取り、その和を表示するメソッドを考えます。今回はjavaで書いてみました。

```java
public class Calculator {
    public static void main(String[] args) {
        int a = 1, b = 2;
        int result = a + b;
        System.out.println("Result: " + result);
    }
}
```

見てわかる通り、計算対象がハードコーディングされています。こうなると、計算内容を変更するためには、コードを修正する必要があります。これだとユーザに配布などできるわけがありません。実行のたびにユーザに内容を書き換えてもらうなんて論外です。

というわけで、ユーザからの入力を受け取るようにしてみましょう。

```java
import java.util.Scanner;

public class Calculator {
    public static void main(String[] args) {
        int a, b;
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter first number: ");
        a = scanner.nextInt();
        System.out.print("Enter second number: ");
        b = scanner.nextInt();
        int result = a + b;
        System.out.println("Result: " + result);
    }
}
```

これで、計算対象を簡単に変更することができるようになりました。

さらに、この「足し算」を2つではなく3つ、4つと増やしていくこともできるようにしてみましょう。

具体的な手段として、次のような流れを考えてみます。

1. ユーザからの入力を配列に格納していく
2. 配列の要素を全て足し算する
3. 結果を表示する

```java
import java.util.Scanner;

public class Calculator {
    public static void main(String[] args) {
        int n;
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter number of elements: ");
        n = scanner.nextInt();
        int[] array = new int[n];
        for (int i = 0; i < n; i++) {
            System.out.print("Enter element " + (i + 1) + ": ");
            array[i] = scanner.nextInt();
        }
        int result = 0;
        for (int i = 0; i < array.length; i++) {
            result += array[i];
        }
        System.out.println("Result: " + result);
    }
}
```

前もって入力される数字の数を聞いておき、その分だけ配列の領域を確保して、その後入力を順次受け取る、といった方法です。

最後に配列の総和を求めます。この時for文を使うわけですが、配列の長さを``array.length``で取得しているので、どんな配列が来ても大丈夫です。

### オブジェクト指向でプログラミングする

上の計算プログラムを再掲します。

```java
import java.util.Scanner;

public class Calculator {
    public static void main(String[] args) {
        int n;
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter number of elements: ");
        n = scanner.nextInt();
        int[] array = new int[n];
        for (int i = 0; i < n; i++) {
            System.out.print("Enter element " + (i + 1) + ": ");
            array[i] = scanner.nextInt();
        }
        int result = 0;
        for (int i = 0; i < n; i++) {
            result += array[i];
        }
        System.out.println("Result: " + result);
    }
}
```

このプログラムには以下のような機能が詰まっています。

- 入力を受け取って配列に格納する
- 配列の中身の総和を求める
- 結果を表示する

これらはどれも汎用性がある操作です。つまり、もう何回か再利用したい場面が出て来る可能性が高いです。

この時、もう一度これらのコードを書き直すのは面倒であり、もしそんなことをやったとしても、変更を多くの場所でする必要があったり、コードが長くなってわかりづらくなってしまいます。

そのため、これらの操作をメソッドとして定義してしまいましょう。こうすることで、新たな機能を追加したいとなったときに、既存の機能を使いまわすことができるようになります。

```java
import java.util.Scanner;

class Calculator {
    public static int[] inputNumbers() {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter number of elements: ");
        int n = scanner.nextInt();
        int[] numbers = new int[n];
        for (int i = 0; i < n; i++) {
            System.out.print("Enter element " + (i + 1) + ": ");
            numbers[i] = scanner.nextInt();
        }
        scanner.close();
        return numbers;
    }
    public static int calculateSum(int numbers[]) {
        int sum = 0;
        for (int number : numbers) {
            sum += number;
        }
        return sum;
    }
    public static void displayResult(int result) {
        System.out.println("Result: " + result);
    }
    public static void main(String[] args) {
        int[] numbers = inputNumbers();
        int sum = calculateSum(numbers);
        displayResult(sum);
    }
}
```

先ほど示したプログラムを、メソッドを使って書き直したものです。メソッドとして、列挙した機能を独立させることができました。

これによって、例えば引き算を実装するとき、入力の受け取りや結果の出力はそのまま使い回すことができます。

## メリット

プログラムの拡張性があることの主なメリットは、「仕様変更に柔軟に対応できる」ということです。

初めからプログラムに拡張性を持たせておくと、後から機能を追加するときなどに、プログラムの修正が少なくて済みます。

これは自分の仕事が増えることを防ぐだけでなく、他の人にも迷惑をかけないことにもつながります。

## おわりに

わかりづらくならない限りはこのようなプログラミングを心がけるといいのかな、と思いました。

今後も何か新発見したことがあったら、ここに追記していこうと思います。

## 参考文献

[オブジェクト指向プログラミングとは #Python - Qiita](https://qiita.com/Arata0608/items/4ae829ef632735e0ac26)