【リーダブルコード】初心者エンジニアが読み解く「分/時間カウンタ」を設計・実装する際のヒント - 第15章

こんばんは。マル太です！

前回は、リーダブルコードの第14章「テストと読みやすさ」についてまとめました。

今回は、第15章「「分/時間カウンタ」を設計・実装する」について、まとめていきたいと思います。

# どんな「カウンタ」を作るの？
この章では、過去1分間、1時間のWebサーバーへのリクエスト数をカウントする「分/時間カウンタ」を作成します。

「分/時間カウンタ」とは、以下のようなものです。

・直近1分間と1時間の転送バイト数を把握したい
・高パフォーマンスなアプリケーションにも耐えうる設計であること

# 問題を整理する
まず、「分/時間カウンタ」を作成するにあたって、どんな問題があるかを整理します。

**1.パフォーマンス**

高頻度でリクエスト数をカウントする必要があるため、処理速度が重要になります。

**2.正確性**

過去1分間、1時間のカウント数を正確に集計する必要があります。

**3.保守性**

コードが複雑になると、後々の修正や機能追加が難しくなります。
<br>

# 解決策を検討する
問題を整理した上で、解決策を検討していきます。

試案1：素朴な解決策
```ruby:Python
class Counter:
    def __init__(self):
        self.counts = []

    def add(self, value):
        self.counts.append((time.time(), value))

    def get_count(self, seconds):
        now = time.time()
        total = 0
        for t, v in self.counts:
            if now - t <= seconds:
                total += v
        return total
```

このコードは、リクエストが来るたびにエントリを追加し、`get_count()`が呼ばれるたびに、エントリを先頭から走査してカウント数を計算します。

この方法だと、エントリ数が増えるほど計算量が増えてしまい、パフォーマンスが悪いです。
<br>

**試案2：ベルトコンベヤー設計**
試案1のパフォーマンス問題を解決するアイデアとして、「ベルトコンベヤー設計」があります。

ベルトコンベヤー設計では、リングバッファと呼ばれる特別なデータ構造を使用し、固定長の配列とインデックスにて、データを順番に格納していく仕組みです。

データが追加されるたびに、インデックスが更新され、古いデータが新しいデータで上書きされます。

```ruby:Python
class Counter:
    def __init__(self, size):
        self.counts = [0] * size
        self.times = [0] * size
        self.size = size

    def add(self, value):
        index = int(time.time()) % self.size
        self.counts[index] = value
        self.times[index] = time.time()

    def get_count(self, seconds):
        now = time.time()
        total = 0
        for i in range(self.size):
            if now - self.times[i] <= seconds:
                total += self.counts[i]
        return total
```

この設計にすることで、`get_count()`の計算量を大幅に削減できます。

しかし、この設計にも問題点があります。

それは1時間よりも長い間隔の集計に対応できないことです。
<br>

**試案3：時間バケツの設計**
試案2での課題を解決するために、「時間バケツ設計」が紹介されています。

これは、固定長の時間間隔（バケツ）で集計を行う設計です。

この設計では、1分間、1時間といった集計間隔をあとから追加できます。

```ruby:Python
class BucketCounter:
    def __init__(self, bucket_size):
        self.buckets = []
        self.bucket_size = bucket_size

    def add(self, value):
        now = time.time()
        self.buckets.append((now, value))
        self._remove_old_buckets(now)

    def get_count(self, seconds):
        now = time.time()
        total = 0
        for t, v in self.buckets:
            if now - t <= seconds:
                total += v
        return total

    def _remove_old_buckets(self, now):
        self.buckets = [(t, v) for t, v in self.buckets if now - t <= self.bucket_size]
```

※時間バケツ設計の解説
ここでは、バケツと呼ばれる固定長の時間間隔で区切られたデータ構造でカウントを保持します。
各バケツには、開始時刻とその時間帯のカウント数が格納されます。
<br>

**上記時間バケツ設計の良い点**

**・柔軟性**
時間バケツ設計では、集計間隔を後から自由に変更できます。
例えば、1分間、1時間だけでなく、30秒間や12時間といった間隔でも集計できます。

**・パフォーマンス**
試案1のように毎回全てのデータを走査するのではなく、バケツ単位で集計を行うため、データ量が増えてもパフォーマンス劣化が少ないです。

**・保守性**
バケツの管理や集計処理がクラスとしてまとまっているため、コードの見通しが良く、保守がしやすいです。
<br>
<br>

以上の点から、試案1から試案3までの内、最終的に
**「時間バケツ設計」**
が一番実装するのに相応しいという結論になりました。


# まとめ
今回は、過去1分間、1時間のWebサーバーへのリクエスト数をカウントする「分/時間カウンタ」を作成しました。

本章では、最終的に「時間バケツ設計」を採用しましたが、
重要なことは、「問題を正しく認識し、様々な解決策を試行錯誤すること」です。

また、それぞれの解決策にはメリット・デメリットがあるため、「要件に合った解決策を選択すること」も重要です。

このプロセスを辿ることで、より良いコードを作成できることを学びました。
<br>

# 最後に
最後までお読みいただきありがとうございました。

「リーダブルコード」解説ブログは、今回で最終回となります。

このブログでのアウトプットを通して、「リーダブルコード」への理解を深めることができたかなと思います。

この経験を活かして、今後も機会があれば、様々な技術情報の発信をしていきたいと考えています。