たのしいRuby 第22章 アクセスログの解析2 Hatena-Log.rb 0.02

昨日作ったHatena-Log.rbを書き直してみました。

バグ修正

パイプを通しても大丈夫!

% ruby hatena-log.rb 001-2007-04.csv | head
2859    77.4%   Windows
400     10.8%   Linux
255     6.9%    Mac
88      2.4%    mobile
27      0.7%    BSD
...(略

原因は・・・ファイルを閉じてなかった・・・・。

内部的な事

仕様変更
  • OS比率を前面に押し出す感じに。
例外処理を追加
  • ファイルが開けない時でもエラーメッセージがでるように。
CSVライブラリの真似をしてcreateメソッドを作った
yieldを使ってみた
  • 出力テキストをクラス外から指定し、面倒な内部処理をクラスにお任せする事が出来るようになった!

Rubyの特徴を全面的に出してみました。

Hatena-Log.rb 0.02

#!/usr/bin/ruby

# Hatena-Log.rb 0.0.2
# Usage  : ruby hatena-log.rb [000-0000-00.csv]"

class Log
  attr_reader :ua
  def initialize(time, ip, from, ua, lang, display, color, to)
    @time = time
    @ip = ip
    @from = from
    @ua = ua
    @lang = lang
    @display = display
    @color = color
    @to = to
  end
end

class HatenaLog
  def initialize
    @logs = Array.new
  end

  # HatenaLog::create(csv_file) -> HatenaLog
  def HatenaLog::create(csv_file)
    require "csv"
    logs = HatenaLog.new

    CSV::Reader.create(csv_file).each do |line|
      logs << Log.new(*line)
    end
    return logs
  end

  def <<(log)
    @logs << log
  end
  def length
    @logs.length
  end

  # dump_os -> os_list
  # dump_os {|os, count| block} -> os_list
  def dump_os
    os_table = Hash.new(0)

    @logs.each do |log|
      case log.ua
      when /Windows|Win32|gooRSSreader/i
        os = "Windows"
      when /Mac/i
        os = "Mac"
      when /Linux/i
        os = "Linux"
      when /FreeBSD|NetBSD|OpenBSD/i
        os = "BSD"
      when /SunOS/i
        os = "Unix"
      when /DoCoMo|KDDI|Vodafone|WILLCOM/i
        os = "mobile"
      when /w3m/i
        os = "w3m"
      when /BOT/i
        os = "BOT"
      else
        os = log.ua
      end
      os_table[os] += 1
    end

    os_list = os_table.sort do |a, b|
      b[1] <=> a[1]
    end

    if block_given?
      os_list.each do |os, count|
        yield(os, count)
      end
    end
    return os_list
  end
end

# main
unless ARGV[0]
  STDERR.puts "Usage: ruby log.rb [000-0000-00.csv]"
  exit 1
end

begin
  File.open(ARGV[0]) do |file|
    logs = HatenaLog::create(file)

    length = logs.length
    logs.dump_os do |os, count|
      ratio = count.to_f * 100 / length
      printf("%s\t%.1f%%\t%s\n", count, ratio, os)
    end
  end
rescue => ex
  STDERR.puts ex.message
  exit 1
end

main部分が減りました。

OS判別は随時追加していく予定です。

ひらメソッドを読んでみた。

最近、人気の高いコードリーディング手法であるひらメソッドを読んでみました。

ひらメソッドの重要ポイントは、

  • ボトムアップに関数を読んでいく
  • 安心して忘れることができる環境
  • 記憶しなければならない箇所を絞り込むことができる

ってところですね。

今までの僕のコードリーディング手法を振り返ると・・・

lsの場合

  • 「lsのコードを読んだことのない奴はプログラマじゃない!!」と、青木さんが言ってたので、GNU lsソースコードを入手。
  • GNU lsが読みにくかったので、FreeBSDのlsのソースコードを読んでみることに。
  • lsのusage()から読んだ。
  • (void) fprintf("Usage: ls ...");という謎のコードを発見。
  • manを読んで、fprintfの戻り値を確認した。

確信した

この手法は間違っていない!!

lsのusage()からボトムアップで進んでいけば、カーネル全体が把握出来るのではないか!と思った。

ついでに、

Rubyの場合

  • RHGを見て面白そうだと思った。
  • Rubyのソースを入手。
  • Rubyのクラス構造体を発見。(全体像を見渡せた)
  • RHGを見たら超重どころであった!!
  • ++演算子の実装に疑問を持った。
  • yylex()を読んでみた。
  • ++演算子が定義されていない事を発見。
  • 構文解析を理解するために、電卓作ってみた。

確信した

この手法も間違っていない!!

Rubyのyylex()からボトムアップで進んでいけば、カーネル全体が把握出来るのではないか!!と思った。

トップダウンで進み、ボトムアップで読む

ボトムまで辿り着きました。後はアップするだけ。

でも、

アプリからやると、zshも読まないといけなくなるな・・・(汗

ってことは、Konsoleも読む必要がある。

すると、xtermも読む必要がある。

そういえば、KDEを読まなければいけなくて。

そうなると、Xを読む必要もある。

そうだ、libcも。

gccも。

おぉぉぉい。カーネルに辿り着くまでに何年かかるんだ!!!!!!!!!!!!!!

やる。

やるっきゃない!

ひらメソッド大作戦。

  • lsのusage()を起点に。
  • Rubyのyylex()を起点に。
  • カーネルのブートを起点に(挫折ポイント)

カーネルを読む!

  • 僕が死ぬまでかけて!!
  • 超まったりペースで(重要)