前回までのあらすじ
- ひとりTDDBCをやります
- お題はt_wadaさんのTDDBCお題スライド P.5
- Githubのこのリポジトリで進めます
- Vol.1
仕様変更その1
キャッシュのサイズを後から変更したいそうです。
cache = LRUCache.new(5)
cache.resize(2)
こんな感じでresizeメソッドに変更したいサイズを渡して
キャッシュのサイズを変更できるようにしたいと思います。
レビュー
テスト
RSpec.configureのprofile_examplesを無効にする
今回使っているRSpecのバージョンは3.1なのですが、
rspec --initで生成されたspec_helper.rbの
=begin =endをアンコメントアウトして実行すると
[tddbc_lru_cache (feature/resize)]$ bundle exec rspec
Run options: include {:focus=>true}
All examples were filtered out; ignoring {:focus=>true}
...中略...
Finished in 0.00388 seconds (files took 0.15989 seconds to load)
10 examples, 0 failures
Top 10 slowest examples (0.00244 seconds, 62.9% of total time):
  LRUCache サイズを固定 :bが消える
    0.00115 seconds ./spec/lru_cache_spec.rb:34
 ...中略...
  LRUCache サイズを固定 何も消えない
    0.00008 seconds ./spec/lru_cache_spec.rb:4
Top 3 slowest example groups:
 ...中略...
    0.00015 seconds average (0.00046 seconds / 3 examples) ./spec/lru_cache_spec.rb:58
Randomized with seed 32109
こんな感じでテストをプロファイリングしてくれます。
テストスイートが大きくなり、最適化が必要になった時には便利ですが
今はまだ必要ないので、spec_helper.rbの
# config.profile_examples = 10
この部分をコメントアウトしてプロファイリングを無効にしました。
仕様変更部分のテスト
RSpec.describe LRUCache, 'サイズを5から2に変更' do
  let(:cache) { LRUCache.new(5) }
  before do
    cache[:a] = 'alpha'
    cache[:b] = 'bravo'
    cache[:c] = 'charlie'
    cache[:d] = 'delta'
    cache[:e] = 'echo'
  end
  it ':a,:b,:cが消える' do
    cache.resize(2)
    expect(cache.to_hash).to match(
      d: 'delta',
      e: 'echo',
    )
  end
  it ':a,:b,:dが消える' do
    cache[:a]
    cache[:c]
    cache[:e]
    cache.resize(2)
    expect(cache.to_hash).to match(
      c: 'charlie',
      e: 'echo'
    )
  end
  it ':a,:b,:d,:cが消える' do
    cache[:a]
    cache[:c]
    cache[:e]
    cache.resize(2)
    cache[:f] = 'foxtrot'
    expect(cache.to_hash).to match(
      e: 'echo',
      f: 'foxtrot'
    )
  end
end
- 何もせずにリサイズ
- リサイズ前にデータを取り出してからリサイズ
- リサイズ後にデータを入れる
この3つのテストケースで、リサイズしても正しくデータが消えているか(残っているか)をチェックします。
プロダクションコード
   def initialize(capacity)
     @capacity = capacity
     @container = {}
-    @stored_key_order = []
+    @contained_key_order = []
   end
@stored_key_order という名前は、データを入れた時のキーの順序という意図でしたが、
実際の役割は
次にキャッシュサイズを超えた時に消されるキーの優先順位
になっているので、それにふさわしい名前に変えたほうがよさそうです。
前回、こう書きましたが実装を進めるうちに@stored_key_orderは
役割が元に戻り@contianed_key_orderに変わりました。
def resize(new_capacity)
  delta = @capacity - new_capacity
  @capacity = new_capacity
  return if delta < 0
  shape_container(reduce_contained_keys(delta))
end
今回の仕様変更で追加したresizeメソッドはこんな感じになりました。
- 
@capacityを新しいサイズにする
- 新しいサイズの方が小さければ@containerをそのサイズに合わせる
@contained_key_orderふたたび
[] 、[]= 、resize 、reduce_contained_keysの各メソッドで
- 要素の削除
- 要素の追加
- 要素の入れ替え
- 配列のサイズ変更
を直接Arrayのメソッドを使ってやっているのが気になります。