API の設計方針とツール

Trema の API をどのように決めているか、舞台裏を紹介します。Trema API の唯一の設計方針は「短く簡潔に書けるかどうか」ですが、具体的にはこんな方法で設計しています。

API

お題:トポロジ検出コントローラ

たとえばこんなコードがあったとします。

def features_reply dpid, features_reply
  features_reply.ports.each do | each |
    next if each.down? or each.local?
    @topology.add_port dpid, each
  end
end

これはトポロジ検出コントローラの features_reply ハンドラの実装です。Trema 本 14 章で解説している LLDP を使ったトポロジ検出の実装例で、だいたい次のように動きます。

  1. スイッチがコントローラに接続したら、FeaturesRequest をスイッチに投げてポート一覧をくれと言う
  2. ポート一覧の入った FeaturesReply メッセージをこのハンドラで拾う
  3.  返ってきたポート一覧 (features_reply.ports) から、アップしていてかつローカルポートでないポートだけを選ぶ (3 行目)
  4. トポロジ情報 DB にそのポートを加える (4 行目)

このコードに問題がないか、ツールを使って機械的に調べてみましょう。

コードの臭いはないか?

動くコードが書けたら、つぎはもちろんリファクタリングです。Trema 本 の Appendix B で紹介しているリファクタリングツール、reek にかけてみましょう。reek はいわゆる「コードの臭い」つまり、リファクタリングが必要なコードのまずい箇所を自動的に検知してくれる、とても便利なツールです。

$ reek topology-controller.rb
topology-controller.rb -- 1 warning:
TopologyController#features_reply refers to each more than self (FeatureEnvy)

FeatureEnvy (機能の嫉妬) 警告が出ました。これは簡単に言うと、features_reply はコントローラクラスのメソッドなのに Trema::Port の細かいメソッド (#down とか #local?) に依存しすぎだということです (Features Envy の詳しい説明はたとえばここ → http://c2.com/cgi/wiki?FeatureEnvy)。

def features_reply dpid, features_reply
  features_reply.ports.each do | each |
    next if each.down? or each.local? # ← 臭うコード
    @topology.add_port dpid, each
  end
end

こういう密結合してしまった部分は改造するときに邪魔でしょうがないので、かならず解消しましょう。たいていは、次のどちらかで直せます。

  1. メソッドを Trema::Port に移動する
  2. Trema::Port のメソッドを呼び出す回数を減らす

今回は 2 の方針で Trema API 自体を見直してみましょう。

Trema API をリファクタリング

仮に、元のコードを最も Ruby っぽく実装したらどうなるかエア実装してみます。「こんな風に書けたらいいな」というコードがこれです。

def features_reply dpid, features_reply
  features_reply.physical_ports.select( &:up? ).each do | each |
    @topology.add_port dpid, each
  end
end

変更のポイントは次の 2 つです。

  • 物理ポート一覧を取るために #physical_ports というイテレータを使うようにしました。これはローカルポートを除いた物理ポート一覧を返すもので、元のコードから #local? の判定をなくせます。
  • メソッドチェイン (.select &:up?) を使って物理ポートの中から :up? => true となる、つまりアップしている物理ポート一覧をイテレータで回せるようにしました。

もちろん、Trema::FeaturesReply#physical_ports メソッドはまだありませんが、reek にかけて FeaturesEnvy が解消できたか見てみましょう。

$ reek topology-controller.rb
topology-controller.rb -- 0 warnings:

はい、たしかに FeaturesEnvy は解消しました。ここまでで、Trema API に Trema::FeaturesReply#physical_ports メソッドを追加しようという指針が得られます (そして、すでに追加しました)。

まとめ

このように、Trema の API を決めるときにはユースケースドリブンで地道にリファクタリングしながら進めています。

  1. Trema API を追加したら、それを実際に使うアプリを書いてみる
  2. できあがったアプリのコードを reek で機械的にチェック
  3. 指摘された問題点を直す。必要であれば Trema API の方も直す。

API の善し悪しはユースケースがどれだけあるかにかかっています。というわけで、なにかおもしろいアプリケーションを書いた場合には気軽に Pull-Request を出したりチケットを切ってください。

Tagged , ,

Leave a Reply