2010年2月6日土曜日

metric_fuを使用してのコード品質の向上

今回は、devver.netという会社が運営しているCaliperというRubyコードメトリックスを計測するツール群であるmetric_fuについてのブログ記事を訳してみました。

コードメトリックスはコードの品質をわかりやすい形で表現してくれるいいツールです。metric_fuで使われているSaikuroという循環的複雑度を計測するライブラリは同僚が開発したもので、実際、仕事でのコードに適用してリファクタリングとかを行っていました。




Metric_fuを使用してのコード品質の向上

大抵、人々は、コードメトリックスについて、興味はあるけど、それを使って何をすればいいのか分からない」と考えています。コードメトリックスは有益ですが、プロジェクトのコード品質を向上する時に使うことができる時には、さらに価値があるものとなります。metric_fuは、たくさんの素晴らしいメトリック情報を提供します。それらの情報はとても役に立ちます。しかしながら、どの部分がすぐに使用することができるのかが分からなければ、役に立つというよりは、ただ興味深いだけのものになってしまいます。

コードメトリックスを見る時に1つ気にとめておきたいことは、たった1つだけのメトリックはあまり興味あるものではないかもしれないということです。メトリックの動向をしばらくのあいだ見ていれば、もっと意味のある情報を提供することでしょう。この動向を表示することはCaliperでのゴールの一つです。メトリックスはプロジェクトを見守るあなたの友達のようであり、どのようにコードが進歩するかをみる2つ目でをもつようなことであり、そして、プロジェクトが制御不能になる前に問題部分を警告することができます。しばらくの間、コードにかかわっていると全てを記憶していることがとても難しくなります(私にはできないことがわかっています)。コードベースのサイズが大きくなるにつれてコード内に形成される重複や複雑さが存在する場所の全てを記憶することはとても難しくなります。コードメトリックスがそれらが出現するたびに問題箇所を指摘することで、コードが制御不能にならないようにしたり、コードへの将来の追加を簡単にすることができます。

実際のプロジェクトで使用することで、どのようにメトリックが変更を後押して、コードを向上するかを紹介したいと思います。それには、私たちのdevver.netウェブサイトのソースをmetric_fuで計測して、とても目立つ問題点を修正することが一番だと思いました。しばらくの間、metric_fuをバックエンドのコードに使用していますが、我々のMerbコードにあメトリックスの計測を行っていませんでした。このことで、最終的にはCaliperへ組み込まれたとがった機能とともにある部分がすこし制御不能となりつつありました。



metric_fuを使用する際に最初に取り組みたかったことはコードをもう少しDRYにすることでした。チームと私はコード内に重複が思ったより少し多くあることに気づき始めました。コードの重複のためにFlayの結果を参考にして、4つのデータベースモデルが同じメソッドをいくつか共有していることを発見しました。

Flayでは重複を強調表示します。タイムスタンプの取り扱い方法について変更する予定でしたので、重複を取り除く良い場所のようでした。4つのモデルすべてに存在していたメソッドを以下に示します。3番目のメソッドの「update_time」は4つのモデルのうち2つのモデル内にに存在していました。

def self.pad_num(number, max_digits = 15)
   "%%0%di" % max_digits % number.to_i
 end

 def get_time
     Time.at(self.time.to_i)
 end

ほとんど全てのデータベーステーブルにおいて、SimpleDBのクエリーを使ってソートできるような形で時間を保存していました。時間をISO8601形式のUTCで保存したいと思っていました。ISO形式に変更する前にこれらのメソッドをヘルパーモジュールに移動して全てのデータベースモデルでincludeすることは簡単でした。

module TimeHelper

  module ClassMethods
    def pad_num(number, max_digits = 15)
      "%%0%di" % max_digits % number.to_i
    end
  end

  def get_time
      Time.at(self.time.to_i)
  end

  def update_time
    self.time = self.class.pad_num(Time.now.to_i)
  end

end

DBモジュールから重複を取り除くとともに2つのDBモジュールに存在していたupdate_timeのような他の時間関連のメソッドを含めることがとても簡単になりました。これによりDBでの時間に関するロジックが一つのファイルに全てまとめられましたので、時間の形式をUTF ISO8601へ変更することはとても簡単なこととなりました。これは明白なリファクタリングの簡単な例ですが、ヘルパーメソッドがよくクラス間で重複することになってしまうことが簡単に分かります。時間がたつと起こることがある重複を見つけ出すにはFlayはとても便利になります。

Flogは測定されたコードがいかに複雑かを示すスコアを提供します。スコアが高いほど複雑さが増していることを意味します。コードが複雑になるほど可読性が低下して、不具合が含まれる確率が高くなります。DBモジュールからいくつかの重複を取り除いた後、Flogのスコアから最悪のデータベースモデルがMetricsDataモデルであることを発見しました。単一のメソッドとしては149というとても悪く高いFlogのスコアが含まれていました。

問題のメソッドはextract_data_from_yamlでした。そして、少しのリファクタリングの後、extrac_data_from_yamlを149のスコアから最大のスコアがextract_flog_data!(33.6)であるいくつかの小さなメソッドにすることは簡単でした。そのメソッドは仕事をしすぎており、頻繁に変更されていました。メソッドは6つのメトリックツールからデータを取得してそれらのデータのサマリーを作成していました。

メソッドは乱雑に広がった42行のコードから、より整理されてより小さな10行のメソッドと下記のような一連のヘルパーメソッドになりました。

def self.extract_data_from_yaml(yml_metrics_data)
  metrics_data = Hash.new {|hash, key| hash[key] = {}}
  extract_flog_data!(metrics_data, yml_metrics_data)
  extract_flay_data!(metrics_data, yml_metrics_data)
  extract_reek_data!(metrics_data, yml_metrics_data)
  extract_roodi_data!(metrics_data, yml_metrics_data)
  extract_saikuro_data!(metrics_data, yml_metrics_data)
  extract_churn_data!(metrics_data, yml_metrics_data)
  metrics_data
end

def self.extract_flog_data!(metrics_data, yml_metrics_data)
  metrics_data[:flog][:description] = 'measures code complexity'
  metrics_data[:flog]["average method score"] = Devver::Maybe(yml_metrics_data)[:flog][:average].value(N_A)
  metrics_data[:flog]["total score"]   = Devver::Maybe(yml_metrics_data)[:flog][:total].value(N_A)
  metrics_data[:flog]["worst file"] = Devver::Maybe(yml_metrics_data)[:flog][:pages].first[:path].fmap {|x| Pathname.new(x)}.value(N_A)
end

Churnはリファクタリングが必要でありそうなファイルを提示します。ファイルがたくさん変更されているなら、大抵は、コードがたくさんのことをしすぎているという意味になります。そして、もし小さなコンポーネントへと分割したらもっと安定して信頼のあるものとなるでしょう。変更の結果を見てみるとサイトでの異なるスタイルを対応するために他のレイアウトが必要となるような場合があるようです。他に気づいたこととしてTestStatsとCaliperコントローラがわりと頻度が高く変更されています。Caliperコントローラはかなり大きく成長しています。それは、ユーザーへ対する機能と管理機能の2つの責務をこなしているためで、それらは分割するべきです。TestStatsはサイズが大きくなっている管理用コントローラコードで、もっと独立したケースへ分割するべきです。



Churnは注力が必要でありそうな部分について提示をしてくれました。他のメトリックスを見ることによって、Caliperコントローラに注意をする必要があることがはっきりとしました。

CaliperコントローラのFlog、Reek、そして、Roodiのスコアは以下のようでした:

RoodiReekは両方ともコード内の設計と可読性の問題について指摘します。Reekは、Caliperコントローラでの「コードの臭い」のスクリーンショットにおいて、どのように手に負えなくなっているかが示しているはずです。コードの臭いがブラウザページ全体に埋め尽くされています!Roodiでは同様にCaliperコントローラについて多くの苦情がありました。Flogでもそのファイルがだんだん複雑になってきていることを示していました。RoodiとReekの不平をいくつか取り除いて、高いFlogスコアのメソッドを分割した後、コードは簡単に読むここができるようになり一目で理解できるようになりました。実際、コントローラのReekの不満をほとんど半分に減らしました。

拙速にハックされて制御不能になりつつあった1つのコントローラをリファクタリングすることで目まいがするような203行から138行になりました。メトリックスによって長いメソッドをリファクタ(52行から最大23行の3つのメソッドに)したり、不明瞭な変数名を変更(sをstatへ、pをprojectへ)したり、ヘルパーメソッドをコントローラからヘルパークラスへ移動したりしました。メトリックスを計測しなくても、これら全てのリファクタリングと良いコード設計をすることはできますが、小さく始めるときに悪いコード臭いを見落としがちです。メトリックスはあるコード片が制御不能になりつつあり、大抵、不具合率が高くなりがちであることについて初期警告を与えることができます。この小さなファイルで行ったことでも、循環的複雑度、LOC、コード複製、そして、より重要な可読性について、大きな進歩を遂げました。

明らかにコードメトリックスは便利で、開発サイクルの一部として注意を払うことであなたのプロジェクトを向上させることができると思っています。プロジェクトでこれらのメトリックスを試すことができるようにmetric_fuについて記述しました。metric_fuは素晴らしいと思っており、Rubyツールでの興味は、プロジェクトのメトリックスを本当に一番簡単に測定できるCaliperを作成することと原因の一つでした。現在、metric_fuをホストするようなことと考えることができますが、もっと進化させてメトリックスで何をするべきかを明瞭にすることを望んでいます。

最後ですが、これは私が作成に手助けした製品の宣伝になってしまいますが、コードメトリックは皆さんの開発を手助けするいいツールになることができると思っているので、あなたのレポジトリを送って、CaliperがホストしているRubyメトリックスを試して下さい。我々はメトリックスがすぐに使用可能ですべてのRuby開発者にとって便利にしようと試みています。ですので、Caliperをよくする方法についてどんな考えでも聞くことができればと思うので、連絡を下さい。

0 件のコメント: