More Related Content
Similar to 可読性について リーダブルコード Part5(優れたテストコード2)
Similar to 可読性について リーダブルコード Part5(優れたテストコード2) (20)
可読性について リーダブルコード Part5(優れたテストコード2)
Editor's Notes
- Part4では、テストコードに的を絞った内容になります。ちなみに原文ではC++で記述されていたのでそのまま抜粋しています。そしてリーダブルコード発行当初に比べると、機能が豊富なテスティングフレームワークが多数あるので、時代にそぐわない箇所も多々あるかと思いますが、本書の意図を理解して行くという意味で記載通りの内容を学んで行きたいと思います。
- 続いて、第二章は「時間のカウンタ」を設計・実装する、です。同じく問題点を解決しながら、機能を追加していきます。ただし、一番大切なことは、リーダブルコードで一貫して言われている、原則を使ってコードを読みやすくすることです。
- 仮にウェブサーバの直近の1分間と直近の1時間の転送バイト数を把握したいと言う課題があったとします。課題としては一般的ですが、これを効率的に解決するのは難しく、まずはクラスのインターフェースを定義するところから始めてみます。
- こちらはC++で書かれたクラスのインターフェースの最初の状態です。このクラスを実装する前に名前とコメントに着目してください。変更の余地があると思いますので名前を改善していきます。
- Count()は何をするのかが分からないので、ほかの名前を考えていきます。候補としては4つあります。 - Increment() : 値が増加する一方だと思われてしまうのでNGです。 - Observe() : 問題ないが少しあいまいなのでNGです。 - Record() : 名詞と動詞の問題があるのでダメ。 - Add() : 「この数値を追加する」と「データのリストに追加する」というどちらの意味もあり、今回はこのメソッドの名前をAdd(int num_bytes)とする。
- より正確で詳細な言葉を使って、誤解のないない明確なものにします。外部の視点を得るために、同僚に素直な第一印象を聞くのが良い手段です。
- では、実装する準備が整ったので課題に対してのコードを記述していきます。まずは簡単な方法で、タイムスタンプのついた「イベント」のlistを保持する形にしてみます。必要に応じて直近のイベントをカウントでき、正しくカウントできますが読みにくい点があります。それはforループが少しうるさい点と、メソッドがほとんど同じ点です。
- CounSince()の仮引数の名前をsec_agoという相対値ではなく、cutoffという絶対値に設定している。 イテレータの名前をiからritに変えている。 forループからrit->time <= cutoffという条件を抽出して、新しくif文を作っている。
- これからも大きくなっていく。このクラスはメモリを無限に使用してしまうため、MinuteHourCounterは、1時間よりも古い不要のイベントを自動的に削除すべきだ。MinuteCount()とHourCount()が遅すぎる。それぞれのメソッドの処理時間はO(n)である。Add()が呼び出された時点で対応する値をminute_countとhour_countとで別々に保持すべきだ。
- 先程の問題を両方解決する設計が必要になります。ベルトコンベヤー設計とは、一方の端に新しいデータが到着したら合計に加算すして、データが古すぎたらもう一方の端から「落下」させて合計から減算する設計になります。ベルトコンベヤー設計には2種類あります。1つめは図左のように、2つのlistを管理して、1つは直近1分間のイベントに、もう1つは直近1時間のイベントに使い、新しいイベントが到着したら、両方にコピーを追加します。こちらは単純ですが、非効率です。一方2つめの方法は、2つのlistを管理するには変わりませんが、1番目のlistにイベントが到着してから、2番目のlistに流れ込むようになっています。
- イベントは、minute_eventからhour_eventへ移動する。そのあとで、minute_countとhour_countを更新する。汚い仕事はShiftOldEvents()に押し付ける。
- この設計には柔軟性が無い。例えば、直近24時間のカウントを保存したいとすると、多くのコードの修正が必要になる。 メモリの使用量が多い。こうトラフィックのサーバが1秒間に100回もAdd()を呼び出すと、直近1時間のデータを全て保持しているので、約5MBのメモリが必要となる。 Add()が呼び出される頻度に関係なく、MinuteHourCounterの使用するメモリは一定の方がよい。
- 60秒で60個のバケツを用意して、直近1分間のイベントは、1秒ごとに60個のバケツに入れる。 直近1時間のイベントは、1分ごとに60個のバケツに入れる。 これにより、メモリ使用量を固定化できて予測可能になるということである。
- まず、単一責任の原則に従って、複数のクラスで異なる部分を処理するために、期間(直近1時間など)のカウントを追跡するクラスを作ります。これは、1種類の期間した扱えないクラスを汎用化したものです。インターフェイスは図左になります。実装コードは右側になります。
- TrailingBucketCounter クラスの実装だけだ。問題をさらに分割するため に、もう一度ヘルパークラスを作ろう。 これから ConveyorQueue というデータ構造を作る。潜在的なカウントと合計を扱う データ構造だ。TrailingBucketCounter クラスは、時間経過に伴って ConveyorQueue を移動させるタスクに集中できる。図右がTrailingBucketCounterを実装したコードになります。
- 右側の画像コードで完成になります。これで高速でメモリ効率もよく、簡単に再利用できる柔軟性の高いコードになりました。3つの解決策を比較してみると、時間バケツのコード行数は、他の 2 つのコード行 数よりもずっと多い。でも、パフォーマンスは高いし、設計に柔軟性がある。それ に、クラスに分割しているので読みやすくなりました。
- 今回リーダブルコードを題材に、読みやすいコードについてLTを行って来ましたが、今回で終了になります。リーダブルコードの最後に、自然に読みやすいコードを書けるようになるための方法が書かれていたので、紹介させて頂きます。1つ目は当たり前ですが実践することです。実践することで気づくことがたくさんあります。2つめは他の人に読んでもらう、レビューしてもらうです。読みやすいとは「他の人の読む時間を短くする」と言うことなので、実際のFBをもらい精度を高めていきます。最後の3つめは続けることです。基本的にリーダブルコードで書かれていることは基本的なことばかりです。読みやすいコードを当たり前にして続けていきましょう。