Chienomi

素朴なプログラミングのススメ

開発::style

プログラミングは楽しい。

プログラミングについて言いたいことや、言わねばならないことはたくさんある。 大抵、プログラミングについて教えたり説明するような局面では、非常に神経を使って過不足ないような表現を追い求めることになる。

だが、そんなプレッシャーから解き放たれたならば、私が言えるのは「プログラミングは楽しい」という一言だけだ。

どんなプログラミングでも楽しいか…と言われるとそうでもない。 実用されないプログラミングはあまり楽しくないし、単純作業のようなプログラミングも楽しいとは思わない。

だが、プログラミングそのものは楽しいものだ。 特に、それがお気に入りの言語――Ruby――でやるものであるならばなおさら。

思わぬ苦戦をした「コメント機能の実装」

コメント機能の実装に関しては検索機能と合わせてだいたい3時間ほどかけた。 コード量がトータル200行程度であることを考えるとペースはかなり遅い。

ペースの遅さはデバッグに苦労したところが大きい。 ウェブアプリケーションであるため、普段と比べるとデバッグの効率が悪いのと、入念にテストしたためテストにも時間がかかった。

そして稼働したのが1月4日のことだったが、早速1月5日にしてスパムが舞い込み始めた。

実は、MemorandaとChienomiでコメントの仕様は全く異なる。 というのは、Memorandaはモデレーションする前提であるためフィルタが全く入っていない。 一方、Chienomiはそのまま公開されるので、インジェクション系の攻撃を受けないようにしていたり、連投に対応していたり、スパムを防ぐ仕組みがあったりする。 そもそもMemorandaはPureBuilderでの生成によってコメントが含まれる仕様であり、対してChienomiは独立したページになっている。こうしたアプリケーション的な性質の違いもあり、共有コンポーネントはほぼ持たない(全く持たないわけではない)設計になっている。

結果として、翌日に「Memorandaのほうから」スパムが届くようになった。まぁ、これは当然に予想された帰結ではあるのだが、モデレーションのための機能との連携もあったこと、そして「思ったよりもいっぱいきたこと」からMemorandaでもスパム対応をすることになった。

そこでごく簡易なスパムフィルタを実装したのだが、必然的にChienomiに含まれている機能を内包することとなり、特にChienomiでは大きな設計変更を行うことになった。 つまり、全く違う動作をするふたつのアプリケーションから処理的には共通したものをまとめ、その上で違う流れになっているものを無理やり同じ流れに落とし込む必要があった。

そもそも、投稿専用のMemorandaのコードとの大きな違いとして、Chienomiはコードはこんな風になっている。

if cgi["comment"] && !cgi["comment"].empty?
  return Writer.new(cgi)
else
  return Reader.new(cgi)
end

そしてこうだ。

comment = Comment.check
comment.run

で、スパムフィルタは当然writer用の機能なのだが、readerでも使える機能があったりする。 ここらへんをうまいこと使えるようにするための調整をするなど、順序としては

  • Memorandaに合わせてライブラリを書く
  • ライブラリを使うようにMemorandaを変更する
  • ライブラリを使うようにChienomiを変更する

という手順ではあるのだけど、Chienomi側を変更する段階でライブラリもMemorandaも大きく変更された。 ばかりか、実は現時点でもあまり綺麗に統合されていなかったため、翌日にさらに1時間ほどかけて手を加える有様だ。

ややこしい原因は、両者が似たような投稿インターフェイスを持っているにも関わらず、データ的に互換性がなかったことだ。そのためちょっとしたミスによるエラーを発生しまくり、すり合わせに時間がかかった。 最終的にはさらなる機能変更を加え、Chienomi側にあったパフォーマンスチューニングも外して取り回し優先にしてまとめた。

「動く」という感動

スパムコメントが来てからのチューニング作業はあまり楽しくなかったが、なんといってもプログラムが動作する、というのは結構感動する。

もちろん、私は今まで数知れないぐらい「動くプログラム」を書いてきているのだが、それでもプログラムが動くというのはいつでも感動モノだ。

特にwebアプリケーションはそうだ。 実は私は本格的なプログラミングとしてはwebアプリケーション(Perl/CGIスクリプト)が原点である。それ以前のBASICとかFORTRANとかその他で書いていたプログラムは、正直あまり今につながっていない。

当時の勉強法は「ソースコードを見る」だった。 教科書的なものはなかったので、ソースコードを見ることが唯一の学びだったのだ。

そして、ソースコードを見ると割と同じようなアプローチ、同じような構成であることが多かった。 誰が始祖なのかはわからないが、模倣から普及していったからなのかもしれないし、何かしらの教科書があったのかもしれない。 だが、いずれにせよ「常識的な書き方・構成」というのがその時代にあった。

最初は私もそれに倣った書き方をしていたし、だいたい500行くらいが目安でもあった。

だが、色んなことを思うわけだ。 「それいる?」とか、「こうしたほうがよくない?」とか。 これは、疑問であり、どちらかといえば先人がそのようにしているのだからそうするだけの何らかの理由があるはずだと思うのだが、とくになぜDB_Fileモジュールを使わないのか…というのは大きな疑問だった。

当時はセパレータが<>である疑似CSVのようなファイルを使うことが多かったのだが、バグやセキュリティホールの温床となっており、また機能制限にもなっていた。

また、多くはPerl5を前提としていながらPerl4時代のコードになっており、jcode.plを要求するのが当たり前であった。jcode.plに関してはもしかしたら責められないかもしれないが。なぜならば、Encodeライブラリが導入されたのは2002年7月リリースの5.8であり、それ以降がファーストリリースというCGIスクリプトは割と少なかったからだ。

いずれにせよ、私にはもっとモダンでスマートな設計があるのではないか? という感情が強くなっていた。だが、それを形にするには実に8年もの歳月を必要とした。 もし私がRubyに出会っていなかったならば、私は今でも従順なプログラミングを続けていたかもしれない。 (もしくは、プログラミングそのものを辞めていたかもしれない)

Rubyは書いていて楽しいというのもとても大きなポイントだが、それ以上に公式に提供されるものそのものが非常に強力な言語である。個人的には「あまりにも強力」になったのはRuby 2.0だと思うのだが、その前の1.9/1.8.7も大きかった。しかしそれ以前に、Ruby 1.8であっても相当強力だったのだ。

例えばMarshalクラスである。 これはオブジェクトを永続化するための機能を持つビルトインライブラリで、これを使えばRubyのデータがそのまま保存できる。つまり、怪しげで潜在的な問題がありそうなログファイルを作らなくてよくなった。

File.open("log.rbm", "r+") do |f|
  past = Marshal.load(f)
  f.seek(0)
  f.truncate(0)
  past.push(log)
  Marshal.dump(log, f)
end

もっとダイレクトに使えるPStoreというものもあった。 これは、Marshalを使ってRubyオブジェクトを透過的にファイルに保存できる。

store = PStore.new("log.rbm")
store.transaction do
  store[:log].push log
end

また、このようなシリアライズライブラリはRuby専用のMarshalだけでなくYAMLライブラリも用意されていた。 網羅的に書かれた「プログラミングRuby」にすら掲載がないが、PStore::YAMLというクラスも存在する。

さらに標準ライブラリに豊富なDBに対応したDBMライブラリがあり、Marshalと組み合わせることで本格的なデータベースを利用することができた。 既にRDBMSが台頭していたのだが、ここでようやく本格的なKVSの価値が増してきた、というのもおもしろいところだ。

ちなみに、PerlにもData::Dumperというのがあったのだけれど、これはPerlコードを吐くため私にはあんまり良いものだとは思えなかった。

こうした試行錯誤と発見、そして新しく登場したものに対する「これ超便利じゃん!」という感動を積み重ねて今がある。

これはあくまで頭の中にある考えであり、発明のアイディアである。そうして自分が考えた「こうすればうまくいくんじゃね!?」が本当にうまくいくと嬉しいのだ。 それがどれほど経験に裏打ちされ、確信を持ってうまくいくと思えるものであってもだ。

素朴なプログラミングのススメ

実際、今は「動くプログラム」を書く機会が少ない。 ここで言う「動くプログラム」は単に動作するというだけでなく、期待した機能を果たし、思わぬ問題を発生させない、という意味である。

noddyなプログラムは通常そのような要件を満たさない。なぜならば、問題を発生させないように利用するほうがずっと話が早いからだ。 それを公開するならば要件を満たすようになるだろうが、それは「動く」分量に対して「問題を発生させない」分量が多すぎて、いまいち達成感が低い。

そう考えると、「素朴なプログラム」は動作することに対して「おぉ、すげ」となる要素が大きい、そしてそれが「問題を発生させない」ことにまで及ぶと良いのではないかと思えてくる。

動くプログラムを書きづらい利用のひとつは、「生半可なwebアプリケーションを稼働させることは非常にリスクが高い」にある。 以前だったらゆるゆるなプログラムでも全然許されたのだが。

だから、「素朴なプログラム」は初心者にとって用意に到達できる目標という意味ではなく、どちらかといえば「書ける人」の話である。

書ける人にとっては普段プログラムを書く上で様々な要素を組み込んだ構成を前提と持っているし、そもそも日々のライフワークの中でフルスクラッチで書き上げるという機会がなくなってくる。そのような人はだいたい、日々LinuxやFirefoxやk8sのコードを書いたりしているわけで、書ける人がフルスクラッチで書くというのは選択的にそのようなライフスタイルを選ばない限りなかなか巡ってこない。

しかし、たまにはこんな小さくて素朴なプログラムを書いてみるのも良いのではないだろうか。 そういうプログラムならよりトリッキーな工夫を盛り込むこともしやすい。ビギナーに近い人でも達成できるようなプログラムにエクストリームなコーディングテクノロジーを詰め込むのも楽しい。

もし「もう長い間フルスクラッチで書いていないな…」なんて人がいたら、私は「久しぶりにやってみたらどう?」と勧めたい。 その何年間かの蓄積で、簡単なプログラムにも見所のあるコードを詰め込めるはずだ。 そして、それは原始的に楽しいのだ。