Ruby 1.8 系と Ruby 1.9 系の間にある大きな溝について。

この記事は私的な見解ですので参考程度の読み物としてお楽しみください。

Ruby 1.8 系(おそらくは Ruby 1.9.1 も含む)と Ruby 1.9 系には思った以上に大きな溝があります。

Ruby 1.8 の環境で動いてきたものだから Ruby 1.9 でも簡単に動くよねーと思ったらそうでもなかったり、逆に Ruby 1.9 のものを Ruby 1.8 で動かそうとすると苦労するかもしれ ません。

このようなバージョンアップとバージョンダウンの必要性は、以下のような場合にありえると思います。

  • バージョンアップの可能性について。
    • Ruby 1.8 は過去の遺産になりつつあります。Ruby の公式サイトにある1.8.7の今後につきましてを読んでください。
    • 上記の影響を受けて徐々にバージョンアップの機会が増えていくと思います。
  • バージョンダウンの可能性について。
    • レンタルサーバなど借用環境でプログラムを動作させている場合、サーバの移行が発生した際にバージョンダウンの可能性があります。
    • 僕は2つのレンタルサーバを借用していますが、1つは Ruby 1.9 系でもう1つは

Ruby 1.8 系です。

    • Ruby 1.9 系を利用させてもらっているレンタルサーバは個人が運営しているフリーの環境で、リクエストに応じて最新を入れてくれました。
    • Ruby 1.8 系を利用させてもらっているレンタルサーバは大手レンタルサーバです。

Ruby 1.8 系と Ruby 1.9 系の溝は大きいので、大手レンタルサーバであればあるほどバージョンを上げるのが難しくなるのではないかと考えています。

いきなりバージョンを上げられたら動作しないプログラムが数多く出るのではないかと個人的に思います。

バージョンアップする機会があるとすれば

  • 事前にバージョンアップの告知があって、ある日を境に切り替える。
  • Ruby 1.8 系と Ruby 1.9 系を並存させてシェバングの切り替えにより Ruby 1.8 系と Ruby 1.9 系を選択できるようにする。

というパターンでしょうか。実は既にバージョンアップの告知がされていて、僕がその告知をちゃんと読んでないだけの可能性はありますが(笑)

では、本題の Ruby 1.8 系と Ruby 1.9 系の大きな溝についてです。


Ruby 1.8 ⇒ Ruby 1.9 にある溝

もっとも大きな溝は多言語化(M17N)の影響だと思います。

このブログでは何度かこの話題を出していますが、Ruby 1.8 系と Ruby 1.9 系では文字列の扱いが大きくことなります。

Ruby 1.8 系では単なるバイト配列として文字列を認識します。文字コードを変換する方法はライブラリとして用意されているものの、文字コードという概念は乏しいです。

しかし、それは逆に見ると単なるバイト配列として見ているだけに文字コードに関して慣用とも言えます。

多言語化(M17N)を理解するまで Ruby 1.9 でまともなプログラムを動作させることがとにかく僕は難しかったという認識があります。

文字コードが許容できない文字列を誤って入力してしまうととにかく例外で止まるのです。

文字コードは厳密に解釈する必要があります。

複数の入力を扱う際は、とにかく文字コードを揃えるよう気を配る必要があります。

その配慮が欠けているととたんにプログラムの動作は不安定になり、いつ例外で止まるかわかったものじゃありません。

文字コードを配慮することは当たり前だなんて言わないでください。

Ruby 1.8 には無かった概念が Ruby 1.9 で導入されたのです。それは新たに文字コード設定用のメソッドが追加されたことを意味し、それを適切に呼んでいなければプログラムが動作しないことを意味します。

具体的に言うと String#encode や String#encoding などですが、Ruby 1.8 にはそのメソッドは無かったわけですから改修の対応が必ずといって言いほど必要になると僕は考えます。

でなければプログラムは動きません。止まります。

後はライブラリの違いや、細かなメソッドの動作があります。

僕の知っている限りを以下に列挙します。

  • CSV 解析ライブラリ
  • ユニット用テストライブラリ
  • String のメソッド一般(多言語化(M17N)の影響を受けて処理単位がバイト単位ではなく文字単位に変わってます)
  • Time.parse メソッドに日時を示さない文字列を渡したときの挙動(例外が出て欲しいところででてくれず Time.now が返ってきたりします)

特に CSV 解析ライブラリは全く変わっている(別のライブラリに置き換わった)ので強く影響を受けるでしょう。バージョンダウンする場合、置き換わったライブラリ(FasterCSV)を入れれば動きそうな気がします(試してませんが...)。

Ruby 1.9 からユニット用テストライブラリの rubyunit がなくなりました。

unit/test もしくは minitest への置き換えが必要です。ユニットテストの考え方はおそらく大きく変わってないと思うので書き換えは容易だと思いますが、rubyunit を使っていた場合は改修は必至です。 バージョンダウンした場合、unit/test は Ruby 1.8 系にもあった記憶ですし、minitest は別個にインストール可能です。


Ruby 1.9 ⇒ Ruby 1.8 にある溝

一番の大きな問題は Ruby 1.9 系にある便利なメソッドが Ruby 1.8 系にはなくて動かないというものでしょう。

やはり大きいと思うのは Ruby 1.9 系で導入された多言語化(M17N)の影響を受けて、Ruby 1.9 系では文字コードの変更が確実かつ優秀でとても楽に行えたのができなく なることです。 これは String#encode メソッドの恩恵ですが、このメソッドが Ruby 1.8 系には残念ながらありません。

文字列を多く処理するプログラムではその影響はとても大きなものとなると思われます。

また、Ruby 1.9 系ではこっそり(?)と便利なメソッドが増えていたりします。

この前、Ruby 1.9 系にあって Ruby 1.8 系に無かったのが Hash の assoc と rassoc メソッドの2つです。他にもあるかもしれません。


ではどう対処するか?

問題だ問題だーとだけ言っていては仕方ないですよね。対処方法を考えましょう!

Ruby は非常におもしろい言語です。

まつもとひろゆき氏が講演で言っていました。Ruby はとても柔軟な言語だと。柔らかいと。確かにそうだと僕も思います。

まつもとひろゆき氏は、この能力はメタプログラミングにあると確か言っていました。

Ruby は定義済みのクラスに対して

  • 後からメソッドを追加したり
  • 後からメソッドの上書きをしたり

ということが簡単にできるん言語なんです(※ 確か JavaScript にもそんな能力があったような気がします)。

また、Ruby は動作環境のバージョンを動的に判断できます。

これらを組み合わせればバージョンによって不足しているメソッドを自分で追加したり、上書きして統一した動作に変更したりということが簡単にできます。実にすばらしい!!!

例えば多言語化(M17N)に対応するために String クラスに encode メソッドを追加する方法を以下の記事で紹介しています。

また、最近ぶつかった Ruby 1.8 系には Hash の assoc と rassoc がなかったという問題には以下のようなプログラムを追加して対応しました。

if RUBY_VERSION < '1.9.0' then
  class Hash

    def assoc(key)
      each do |k, v|
        if k == key then
          return [k, v]
        end
      end

      return nil
    end

    def rassoc(value)
      each do |k, v|
        if v == value then
          return [k, v]
        end
      end

      return nil
    end

  end
end

こうするだけで既存の Hash に対して状況に応じてメソッドを追加し、バージョンを気にせず動かすことができちゃうんです。

Ruby 本当に柔軟ですね。

さよなら。さよなら。さよなら!