パーフェクトRubyを読んだ

パーフェクトRuby (PERFECT SERIES 6)

パーフェクトRuby (PERFECT SERIES 6)

  • 作者: Rubyサポーターズ,すがわらまさのり,寺田玄太郎,三村益隆,近藤宇智朗,橋立友宏,関口亮一
  • 出版社/メーカー: 技術評論社
  • 発売日: 2013/08/10
  • メディア: 大型本
  • この商品を含むブログ (22件) を見る

正月くらいに買って積んであった本。この本の最初に書いてある対象読者の1つに、"過去にRubyは触っていたが、しばらく触っておらず、最近のRubyを学び直したい人"とあるのだけど、そういうつもりで読んだ。Ruby2.0時代の言語仕様が丁寧に説明されているし、BundlerとかのRuby界でよく使われるツールの解説などもあって便利度が高い。最近の本でこのくらい網羅的な本はあまり無いと思うので、たまにRubyを使うだけの人も会社とか家の本棚に置いておくと、リファレンスとして便利に使えそうだと思った。

この本は全部で5つのPartにわかれていて、以下のようになってる。

  • Part1 Ruby ~overview
  • Part2 Rubyの言語仕様
  • Part3 メタプログラミング
  • Part4 標準添付ライブラリ
  • Part5 実践プログラミング

特におもしろかったのは、Part3のメタプログラミングとPart5の実践プログラミングの部分。

Part3のメタプログラミングでは、動的なプログラミングやリフレクションといった話題がでてくるが、Rubyのクラスオブジェクトについての章が自分としては一番有用だった。Rubyのオブジェクト志向シンプルで良いと思うのだけど、オブジェクト、得意クラス、クラスと継承、モジュールとmixinの関係がときどき頻繁にわからなってしまう。この本では、段階的に図を使って説明してくれるので、どうなっていたかこんがらがっても、その部分を読めばすぐに理解できる感じになってる。

Part5の実践プログラミングでは、実際の開発に必要なツールの紹介がコンパクトで丁寧にされている。普段Rubyになじみのない人も、Rubyの開発に必要なツールのひと通りの使い方やツールが取り扱っている概念をすぐに勉強できる。例えば、うちの会社ではCapistranoを使ってるのだけど、設定を変更するにはまずCapistranoのwikiをじっくり読み込んで、どういう概念があるかなどを理解する必要があった。この章ではCapistranoの取り扱ってるtaskやroleといった概念をひとおとおり教えてくれるので、今後はまずパーフェクトRubyを読んでからドキュメントにあたるとスムーズにCapistranoを理解できそう。他にもBundlerやYARD、RackやSinatraなど、Ruby界ではよく使われているツールについての説明がそろっている。

ほぼ最新のRubyの言語仕様とその周辺ツール群をざっくり理解するのにこの本を読むのは効率が良さそう。主な言語としてRubyを利用していなくても、Rubyで書かれたツールを利用するという人も多いだろうから、こういう本が手元にあると便利だと思う。

Scalaのfor comprehension

Scala の for 文には、いろんな機能がある(参考: Scalacheat - Scala Documentation)。for comprehension というやつで、for文を書くと実際には対象オブジェクトのメソッド呼び出しに変換される。

val nestedList = List(List(1,2,3), List(4,5,6))

for {
  list  <- nestedList
  num   <- list
} println(num)

たとえばこのようにコードを書くと、実際には以下の様にforeachを使ったコードが実行されているらしい。

val nestedList = List(List(1,2,3), List(4,5,6))

nestedList.foreach({ list =>
  list.foreach({ num =>
    println(num)
  })
})

for文が値を返すようにyieldをつかうと、flatMapとmapの組み合わせで実行される。

val nestedList = List(List(1,2,3), List(4,5,6))
val nums = for {
  list  <- nestedList
  num   <- list
} yield num

// 以下のように展開される
val nums = nestedList.flatMap({ list =>
  list.map({ num =>
    num
  })
})

以下のコードはコンパイルエラーになる。このエラーがなんで起こるのかすぐにわからなかった。

val option = Option(List(1,2,3))

val nums = for {
  list <- option
  num  <- list
} yield num

// [error]  found   : List[Int]
// [error]  required: Option[?]
// [error]     num  <- list
// [error]          ^

このコードは以下のflatMap/mapの組み合わせに変換される。

val nums = option.flatMap({ list =>
  list.map({ num =>
    num
  })
})

Option型のflatMapは flatMap[B](f: (A) ⇒ Option[B]): Option[B] という型になっていて、flatMapに渡した無名関数はOption型の値を返さないといけない。一方、List型のmapはList[Int]を返しているために、ここではエラーになる。

概念的にはOptionの中身を変換するという操作のはずだから、いきなりListに変わったりするのはおかしいという感じな気がする。

ちなみに、yieldがない場合にはforeachに変換されるので上記のエラーは発生しない

Scala勉強

良いとされている本をひと通り読んだ後、ためしにとScala書いてみてるけど、わからないことが多い。とりあえず、まだやりはじめなので、何もわからないのはしかたない。Scalaの世界観に慣れてなんとかなりたい。

まず、言語の表現力が高くて、あることをするのにもいろいろ書き方があるけど、場面ごとのベストな書き方とかがわからない。普段書いてるPerlでは、TMTOWTDIというスローガンからわかるように、いろんな書き方が選べる。Perlの場合は、いろんなコードを書いたり読んだりするうちに、だいたいいい塩梅がわかってきたので、Scalaでもそのうちなんとかなると期待してる。

ドキュメントの引き方もまだよく知らなくて、ベストな方法がわかってない。標準ライブラリのAPIのドキュメントに関しては、Scala API Docs | The Scala Programming Language がすごい充実しているし、Dashを使って検索もできて不便がない。Documentation | The Scala Programming Languageにいろんな情報源がありそうだから、ここから思ってる情報にたどり着けるように訓練すればなんとかなるようになってくるのかもしれない。

標準でないライブラリについてはどうすればいいか良くわかってない。例えば、Dispatchというライブラリのドキュメントは丁寧な説明がなされている一方、scaladocはどこかに置いてあるのかとがわかってない。一般的なやり方があるのかもしれない。IDEを使えばだいたい解決するといった世界観なのかもしれない。

来週会社のScalaに詳しいひとにいろいろ知見を共有されたいのでよろしくおねがいします。

Ruby/Rails勉強会@関西 59th に参加した

@cuzicさんにお誘いいただいて、Ruby/Rails勉強会@関西 59th #rubykansai - Ruby関西 | Doorkeeperに参加した。大学のころ(2006年とかそのへん)に京都で開催されていたころは、頻繁に参加していたのだけど、その後、大阪で開催されるようになったりしてあまり言ってなかった。数年ぶりの参加。

発表はどれも良かったけれど、特に今回からRuby関西の代表になられた@cuzicさんの所信表明となる発表がおもしろかった。(勉強会の代表になってみるなど)人のためになることをすることで、自分もやりたいことができるようにできるようにるというはなしや、「自分の能力は身の回りにいる人5人の平均になる」というどなたかの名言(メモしてない)を引用されて、自分よりおもしろいことをしている人たちがいるところ(=勉強会)に行こうという話などがあって、なんとなく納得感があった。

Ruby/Rails勉強会@関西ではおなじみのRuby初級者向けレッスンにも参加して、Stringクラスについていろいろ調べた。演習問題では、なんかちょっと前に流行った、"単語の先頭と末尾一文字以外の順番を入れ替えても意外と意味が理解できる文章"を生成するという問題を解いた。めっちゃ雑な感じに書いたコードは以下の様な感じ。シャッフルせずにそのまま出力する文字を取り扱うのにごちゃごちゃやったという感じ。

cambridge = <<EOF.chomp
こんにちは みなさん おげんき ですか ? わたしは げんき です 。
EOF

puts cambridge.scan(/(\p{^Word}*)(\p{Word}*)(\p{^Word}*)/).map { |before, word, after|
  before +
  (word.size > 2 ? word[0] + word[1..-2].chars.shuffle.join + word[-1] : word) +
  after
}.join

# result
# こにちんは みさなん おげんき ですか ? わしたは げんき です 。


懇親会では、京都にいると全然わからない大阪方面のWebエンジニアの情勢なんかを伺えたり、あずにゃんがスクラム開発やActiveRecordについて教えてくれる本を見せてもらえたりして有益な時間が過ごせた。今年のRuby/Rails勉強会@関西は2ヶ月に1回くらいの開催を目指されるらしいので、そのうち会社のひと誘ったりして行きたい。

予習

Ruby/Rails勉強会@関西 59th #rubykansai - Ruby関西 | Doorkeeper にめっちゃひさびさにいくので予習してた。Ruby普段はちょっと設定ファイル書くくらいで、ほとんどかかない。

自分的な最どうなってたっけポイントであるメソッド呼び出しのルールについて予習した。なるほど〜

# method search order
class Parent
  def hi
    p :parent
  end
end

module IncludeModule
  def hi
    super
    p :include_module
  end
end

module PrependModule
  def hi
    super
    p :prepend_module
  end
end

class Child < Parent
  include IncludeModule
  prepend PrependModule

  def hi
    super
    p :child
  end
end

obj = Child.new

def obj.hi
  super
  p :singleton
end

obj.hi

# $ ruby m.rb
# :parent
# :include_module
# :child
# :prepand_module
# :singleton

neocomplecacheを使ってPerlをかいてるときに :: をdelimiterにしない

neocomplecacheを使ってPerlをかいてるときに、package名の補完候補が少ししかでなくて、::まで書き進めると補完候補が増えるという状態だったので、微妙にこまってた。

以下の様な感じで、Guita::Handler::Auth とか Guita::Handler::Pickとか補完したいのだけど、::から先を省略してくれる機能が働いていて、一気に補完できない。

f:id:hakobe932:20140121213034p:plain

Rubyのクラスやモジュールではこういう風にならないので、なんか設定を変えたら思うようにできそうと思って調べたら、g:neocomplcache_delimiter_patternsという変数でこのへんの振る舞いを変えれそうだった。

if !exists('g:neocomplcache_delimiter_patterns')
    let g:neocomplcache_delimiter_patterns = {}
endif
let g:neocomplcache_delimiter_patterns['perl'] = []

こういうふうにしたら思うような振る舞いになって便利になった。

f:id:hakobe932:20140121213043p:plain

これでペアプロ中にtypoしまくることで、ひとでさんに怒られることがなくなってうれしい。

テストをどこまで書くか

CROSS2014で以下の2つのセッションにお招きいただきお話をさせていただいた。ありがとうございました。

セッション中はもちろん、その後の懇親会でもいろいろな方におはなしを伺えてかなり勉強になった。テストやコードレビューなどの開発フローについてのお話が多かったので、そのへんの話で考えたことを少し書いていこうと思う。

とりあえずテストの話から。元気があったらコードレビューのはなしとかも別のエントリで書きたい。 => コードレビューの話は id:shiba_yu36 さんの記事がわかりやすい コードレビューを円滑に行いたい (#cross2014 のお話) - $shibayu36->blog;

テストをどこまで書くか

naoyaさんのセッションでテストをどこまで書くかについての議論があった。結論として「コストと効果を見極めていい具合に書こう!」という感じになったのだけども、いい具合というところが一番むずかしい。

たぶん、漠然とテストをどこまで書くか書かないかという話はできなくて、ソフトウェアごとに保証したい品質やその程度に合わせて、テストをどこまでやるかをいうことを丁寧に考えないといけない。その考えた結果がいい具合ということになるのだと思う。

前提

naoyaさんのセッションでも言われていたけれど、テストの話をするときには、まず前提を話さないとコミュニケーションが失敗がち。自分が所属しているはてなでは、いくつかサービスを数年にわたって運用している。そういったサービスを実現しているソフトウェアには以下の様な特徴があることが多い。

  • ソフトウェアの寿命が数年単位と長い
  • ソフトウェアの根本的な設計や機能が何度も変化する
  • ソフトウェアの開発メンバーが変わることがある

数年にわたって動作するソフトウェアが、価値を生み出し続けるためには、継続的に変更が必要だし、時にはそれが大きな変更になることもある。開発メンバーが変わることもあるので、変更者が必ずしも変更するソフトウェアのことを熟知しているわけではない。

テストを書く理由

上にかいた前提のもとでは、多くの場合、以下の様な理由でテストを書いていると感じる。

  • 1.ソフトウェアの変更可能性を維持するため
  • 2.ソフトウェアの機能性/信頼性を保証するため
  • 3.開発効率を高めるため

ソフトウェアの変更可能性を高めるのにテストは有用だ。回帰的なユニットテストを書いておくと、別の変更によって何かがこわれてもすぐに気づくことができる。テスト可能なコードを書くことで良い設計になったり、テストがコードの意図を残したドキュメントになることでソフトウェアの理解性が高まる効果もある。

ソフトウェアの機能性や信頼性の保証にもテストが利用できる。ソフトウェアがちゃんと機能を満たし、異常事態に対応できるかを確かめるのにテストが必要である。

開発効率を高めるためのにもテストは便利だと感じる。ソフトウェアに何か機能を追加すると、テストがあろうがなかろうが、様々な入力で動作することをたしかめないといけない。実装が進めるたびにブラウザを起動して、フォームになにか入力して、結果を確認するみたいなちまちましたことを繰り返し行うのは効率が悪い。

数年の間動くサービスを作っているという前提では、1のソフトウェアの変更可能性を維持するのがすごく大事で、コストをかけてもテストを書く一番の理由になっていると思う。2や3については必要に応じてコストをかける。

テストをどこまで書くか

なんのためにテストを書いているかを考えながらテストを書くと、ある程度どこまで書けばよいか基準を考えることができると思う。はてなでの例に照らし合わせて考えてみる。

変更可能性の維持を目的にテストを書いているのならば、代表的な正常系と異常系でテストが通過していることを確認したりして、対象のコードのすべての命令が一度は実行されている程度のテスト( C0 程度 )があれば良いと思ってる。呼び出しているメソッドに大きな変更(メソッドのシグニチャが変わるとか)があったときにテストが落ちるようになっていれば、他の場所の変更でコードがこわれたことが検知できる。

機能性や信頼性が重要な部分(課金系の機能とか)でそれらの品質を保証したいテストを書きたいのであれば、一歩踏み込んで、あり得るすべて入力や実行経路での動作を確認するテストが必要になると思う。厳密な保証が不要な部分であれば、滅多にない入力の組み合わせなどについてはテストをしないこともできる。

開発効率をあげるためのテストのみを書いているのであれば、開発中のソフトウェアの動作の確認ができさえすれば内容はなんでも良いかもしれない。

naoyaさんのセッションで、テストをどの程度までやるかの基準としては、テストはC0を満たす必要があるのではないかと発言をしたのは、ソフトウェアの変更可能性を維持するためには、最低限それくらいのテストが必要だという文脈だった。もちろん他の目的でテストをかいているとC0ではやりすぎかもしれないし、C0では足りなすぎるということがあると思う。

つまりどうしたら

ソフトウェアの品質を高めるためにテストを書く、というだけではおおざっぱすぎて、結局何を書けばいいかわからなくなってしまう。そうではなく、まず、目的をもってテストを書くことが大事だと思う。開発しているソフトウェアに合わせてどういった品質をどの程度まで保証したいかということを、ちょっとでも意識できると、テストをどこまで書けばよいかという基準が見えてきて、テストを異常に書きすぎたり、書かなすぎたりして意味のないテストになるということは減るのではないかと思う。