Apr - 27th
怠惰な午後
Posted at 3:29 pm | Filed Under Apple, PHP, Ruby
HDDを飛ばして以来、ずっと放置していたRoRとCakePHPとsymfopnyとCodeIgniterとDrupalの開発環境を用意したら満足してしまった。典型的な積ん読症候群である。
Jan - 14th
Zed Shaw Puts The Smack Down On The Rails Community
Posted at 12:44 am | Filed Under PHP, Ruby
ちょっと前に話題になったRails界隈のもめ事からの引用。
Zed Shaw Puts The Smack Down On The Rails Community:
Most entertaining quote: “This is exactly what makes Rails a ghetto. A bunch of half-trained former PHP morons who never bother to sit down and really learn the computer science they were too good to study in college.”
さて、自分はここには当てはまらないと安心していいものかどうか。
Jan - 2nd
Rails2.0.2のヘルプ、中身拝見(4)
Posted at 2:09 am | Filed Under Ruby
備忘録というかRails2になってからドキュメントが改訂されているケースが少ないことへの腹いせに始めたものの、コードを全然書かないし成果が何もないので飽きてきたヘルプ拝見だが、乗りかけた船ということで続けてみる。
今度はgenerateでmailerを指定してみよう。
mailer
実際の例:
$ ruby script/generate mailer Notification signup forgot_password invoice
exists app/models/
create app/views/notification
exists test/unit/
create test/fixtures/notification
create app/models/notification.rb
create test/unit/notification_test.rb
create app/views/notification/signup.erb
create test/fixtures/notification/signup
create app/views/notification/forgot_password.erb
create test/fixtures/notification/forgot_password
create app/views/notification/invoice.erb
create test/fixtures/notification/invoice
Notificationという名前のmailerを指定すると、新規にmailerとそのビューのスタブを作成してくれる。といっても、コントローラには特に何も追加されず、modelとviewのみ何かが作成されているので、ブラウザですぐに変更を確認できるわけではない。
ここで作成されたモデルには、ちょっと不気味なルールが適用される。先ほど作成したlistというコントローラを下のように変更して、Notificationモデルを実装してみよう。
model以下に作成されたnotification.rbのsignupというメソッドのみ変更してみた。
$ cat app/models/notification.rb
class Notification < ActionMailer::Base
def signup(sent_at = Time.now)
@subject = 'Notification#signup'
@body = {}
@recipients = 'root@localhost'
@from = 'user@localhost'
@sent_on = sent_at
@headers = {}
end
def forgot_password(sent_at = Time.now)
@subject = 'Notification#forgot_password'
@body = {}
@recipients = ''
@from = ''
@sent_on = sent_at
@headers = {}
end
def invoice(sent_at = Time.now)
@subject = 'Notification#invoice'
@body = {}
@recipients = ''
@from = ''
@sent_on = sent_at
@headers = {}
end
end
signupというメソッドが呼び出されたら標題や本文、Fromや宛先が指定されたものになるように変更している。ご覧の通り、generate mailerで作成されたmodelはActionMailerクラスを継承している。続いてこちらをコントローラから呼び出してみる。以前作成したコントローラを利用する。
$ cat app/controllers/list_controller.rb
class ListController < ApplicationController
def index
Notification.deliver_signup()
end
def create
end
def edit
end
def delete
end
end
奇妙なことに、実装した覚えのない「deliver_signup」というメソッドが無造作に呼び出されている。また、Notificationクラスもいきなり呼び出してしまっているわけだが、model上のクラスはこうして呼び出すことが出来るのがRailsの規則のようだ。
この「deliver_signup」というメソッドは、正直ちょっと面食らってしまったのだが、動作としてはsignupメソッドのことを指している。その証拠に、これを実行するとログにはメールが配信されたと出力される。ようするに、ActionMailerを利用したメール送信では、deliver_付きのメソッドが呼び出されると、deliver_以下の名前のメソッドが実行され、メール送信に必要な情報はそちらで定義されたものが利用されることになっているということのようだ。
メール送信機能を実装する際のヘルパー、generate mailerの使い方はざっとこんな感じ。
Jan - 1st
Rails2.0.2のヘルプ、中身拝見(3)
Posted at 11:29 pm | Filed Under Ruby
さて、コントローラまで作成した。
$ rails test $ cd test $ ruby script/generate controller list index create edit delete
すると、testディレクトリに何やら作られている。
$ cd test $ tree . |-- fixtures |-- functional | `-- list_controller_test.rb |-- mocks | |-- development | `-- test |-- test_helper.rb `-- unit 7 directories, 2 files
list_controllerを作成すると、自動的にテスト用にtest/functional/list_controller_test.rbも作られたようだ。しかし中身はまだない。
$ cat test/functional/list_controller_test.rb
require File.dirname(__FILE__) + '/../test_helper'
class ListControllerTest < ActionController::TestCase
# Replace this with your real tests.
def test_truth
assert true
end
end
Railsのすごいところはテストの面倒をフレームワークがみる(みようとする)という点で、Railsをちゃんと覚えようと思ったのもこれがあるからだ。generateスクリプトには、integration_testなるものがあり、テスト用のスタブ作成を請け負ってくれる。
integration_test
でも、これはさっきみたテスト用のスクリプトと何がどう違うんだろうか。
$ ruby script/generate integration_test DemoTest $ cd test $ tree . |-- fixtures |-- functional | `-- list_controller_test.rb |-- integration | `-- demo_test_test.rb |-- mocks | |-- development | `-- test |-- test_helper.rb `-- unit 7 directories, 3 files
test/integrationというディレクトリと、demo_test_test.rbというファイルが作成されている。中身を拝見。
$ cat test/integration/demo_test_test.rb
require "#{File.dirname(__FILE__)}/../test_helper"
class DemoTestTest < ActionController::IntegrationTest
# fixtures :your, :models
# Replace this with your real tests.
def test_truth
assert true
end
end
全然中身はない。いったい何が違うのか調べてみると、どうやら先ほどコントローラを作成した際に勝手に追加されていたのは、そのコントローラを単体でテストする場合に使うやつで、integration_testはその名の通り複数のコントローラを使ってシナリオ通りのテストパターンを試すような用途で利用されるためのスタブだとのこと。
実際に動かすにはやはり何らかのアプリケーションを作成してからになる。というわけで、この機能についてはここまで。
Dec - 30th
37signalsのシステム構成
Posted at 1:41 am | Filed Under Ruby, Web Services
37signalsといえば、Getting Realにこんな一節がある:
まだ発生してもいない問題のために時間を費やすな
ユーザーが10万人に達するのに2年かかると予想されるのに、今日10万人のユーザーを想定する必要があるのだろうか?今は3人のプログラマーがいれば事足りるのに、8人を雇う必要があるだろうか?
今は2台のサーバーでまかなえているのに、12台を準備する意味があるのだろうか?
とにかく進めよう
起こってもいない問題の解決方法を考えるために、人は時間を費やしがちです。そんなことに意味があるか! 私達がBasecampを開始したときは、顧客に請求書を送る手はずすら整っていませんでした。しかし1ヶ月サイクルで請求を行うことになっていたので、(サービスが開始してから)30日間の猶予があることはわかっていました。もっと緊急な問題を解決するためにサービス開始までの時間を費やし、私達が請求書の問題に取り組み始めたのはサービス開始後でした。 請求書の発行はうまくいきました(それにこのおかげで、シンプルな解決策をとることができたわけです)。
そして、いまや同社のサービスはBasecampのアカウントが200万、ユーザのアップロードしたファイルが5.9テラバイトという規模になった。つまり、問題は発生したわけで、じゃあどんな対応をしたのかというのが上の記事なわけだ。そう考えるとなんだか興味深い。
Dec - 30th
Rails2.0.2のヘルプ、中身拝見(2)
Posted at 12:37 am | Filed Under Ruby
さて、Railsでアプリケーションのひな形を作成したら、まずはgenerateを試してみたくなるのが人情というものだ。
$ rails test --database=mysql $ cd test $ ruby ./script/generate --help
ここでもヘルプをいちいちみていくことにしよう。
おっと、その前に。ここではデータベースにMySQLを指定したけれど、別に本格的なものを作るわけではないのでデフォルトのSQLite3でも問題はなかった。そもそもMySQLをセットアップしないといけないので、むしろMySQLの方が面倒かもしれない。
MySQLを指定すると、conf/database.ymlにそれっぽい設定が作られる。
$ cat config/database.yml # On Mac OS X Leopard: # sudo env ARCHFLAGS="-arch i386" gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config # This sets the ARCHFLAGS environment variable to your native architecture # On Windows: # gem install mysql # Choose the win32 build. # Install MySQL and put its /bin directory on your path. # # And be sure to use new-style password hashing: # http://dev.mysql.com/doc/refman/5.0/en/old-client.html development: adapter: mysql encoding: utf8 database: test_development username: root password: socket: /tmp/mysql.sock # Warning: The database defined as 'test' will be erased and # re-generated from your development database when you run 'rake'. # Do not set this db to the same as development or production. test: adapter: mysql encoding: utf8 database: test_test username: root password: socket: /tmp/mysql.sock production: adapter: mysql encoding: utf8 database: test_production username: root password: socket: /tmp/mysql.sock
で、この設定で接続可能な状態にMySQLがセットアップされている必要がある。
さて、話を戻そう。
といっても、railsコマンドと基本的には全く同じなので、細かいオプションは飛ばして、利用できるGeneratorについて調べてみよう。Generatorというのは、実際にgenerateコマンドが作成するファイルの内容を定義しているもので、組み込みのもの以外も後から入手可能になっている。下のコマンドであれこれ探しまわることができる。
$ gem search -r generator
ずらずらとたくさん表示される。面白そうだが、ここではビルトインのGeneratorのみ確認する。
controller
使い方:$ ruby script/generator controller ControllerName [options]
新しいコントローラとビューのスタブを作成する。コントローラの名前はキャメルケース(CamelCase)やアンダースコアで繋げたものが使える。引数としてビューの名前を渡す。
Railsのコントローラは、本来のMVC構造でいうところのCはRails自体が処理しているので、いうなればコントローラから呼び出されるアクションのコントローラみたいなものになる。つまり、http://example.com/test/にアクセスすると、Rails側でこのリクエストを処理するアクションはtestであると判定して、testというコントローラに処理が引き渡される。testはこのアクションのコントローラとして振る舞う。こういうこと自体を引き受けている上位のコントローラはRailsが持っている、ということだ。通常、コントローラはapps/controllers以下に配置される。この場合、apps/controllers/test_controller.rbというファイルになる。
また、Railsにはモジュールという概念もあって、http://example.com/module_name/test/へのリクエストはapps/controllers/module_name/以下に配置されたtest_controller.rbというファイルがコントローラに指定される。アクションをジャンルで分けたり、なにかと整理が大変な大規模なシステムなんかで重宝するだろう。この場合は、引数をクォートで囲んで指定するようだ。
$ ruby script/generator controller 'module_name/test' list new delete
ちょっと動かしてみよう。とりあえず、listという名前のコントローラを作ってみる。何かの一覧だから、これに付随する機能としては、インデックスと新規作成、編集、削除があればいいとしよう。
$ ruby script/generate controller list index create edit delete
exists app/controllers/
exists app/helpers/
create app/views/list
exists test/functional/
create app/controllers/list_controller.rb
create test/functional/list_controller_test.rb
create app/helpers/list_helper.rb
create app/views/list/index.html.erb
create app/views/list/create.html.erb
create app/views/list/edit.html.erb
create app/views/list/delete.html.erb
確かにcontrollerとview以下にファイルが作成された。generateに渡した引数の最初はコントローラの作成を指示するもの、次は作成するコントローラの名前、以降は全部作成するviewの名前だ。これでちょっとブラウザから結果を見てみよう。ruby scritp/serverでウェブサーバを起動したら、http://localhost:3000/list/にアクセスしてみる。すると、画面には
こんなメッセージが表示されている。この画面表示を担当しているビューのファイル名が記されているのがわかる。この場合はapp/views/list/index.html.erbがビュー用のファイルだ。
List#indexというのは、コントローラクラスのList(正確にはListController)の中のメソッドとして定義されているindexが実行されているという意味になる。Rubyではクラス名の最初は大文字なので、コントローラの名前としてlistを指定してもクラス名はListContorllerになる。
先ほど実行したコマンドでは、同じくhttp://localhost:3000/list/create/やhttp://localhost:3000/list/edit/、http://localhost:3000/list/delete/にアクセスすると、それぞれ似たような画面が表示される。後でみるscaffoldほど細かい内容のスクリプトは作成されない。
長くなったのでここでいったん切ろう。
Dec - 29th
Rails2.0.2のヘルプ、中身拝見(1)
Posted at 1:02 pm | Filed Under Ruby
誰もが知っているRailsの、誰もが知っているので誰もいちいち書かないようなことを書いてみた。先日ちょっと借りてきた書籍があつかっているRailsのバージョンが古くていまいち役に立たなかった腹いせだ。
動作環境はMac OS X Leopard(10.5.1)。
まずはインストールと作業環境の設定。
$ sudo port install ruby $ sudo port install rb-rubygems $ sudo port install rb-sqlite3 $ sudo port install sqlite3 $ sudo gem install rails
*MySQLのサポートって入れてなかったっけ?記録なし。
$ cd $ mkdir -p Ruby/rails/demo $ cd Ruby/rails/demo
これでとりあえずの準備は完了。
そうそう。portsで入れたMySQLだと、起動スクリプトをいじってMySQLのソケットを/tmp/以下に作るように指示してあげないといけないのが面倒くさい。一度書いてしまえば上書きされることもないのだろうが、なんとなく後で面倒になりそうなので、portsからではなくwww.mysql.comから落として/usr/local/mysql以下にインストールした。
まずは使い方チェック。
$ rails --help
最近の若者はヘルプを読まないからいかん。
railsコマンドはRailsで作成するアプリケーションに必要なひな形をそろえてくれるもので、使い方は
rails アプリケーションの設置場所 オプション
になる。オプションは以下の通り:
-rまたは–ruby=path
railsコマンドで実行されるスクリプトが使うRubyインタプリタの場所をpathで指定することができる。何か特別な理由でもない限りは使わないだろうけれども、デフォルトのRubyのパスが気に入らないときに利用できる。ちなみに作成されたアプリケーションの方はenvを使ってRubyの場所を参照するので、まあなんだかなあという感じだ。これが便利になる局面を知らないだけかもしれないが。
-dまたは–database=name
作成するアプリケーションが利用するデータベースサーバを指定することができる。選択肢はMySQL、Oracle、PostgreSQL、SQLite2、SQLite3。ちなみに指定するときは小文字で書いた方がいいのかな。普通どっちにも対応しているか。まさかね。きっとね。
じゃあ試してみよう。
$ rails test --database=PostgreSQL
あれ?ヘルプが表示された。もしかして…
$ rails test --database=postgresql
あ、ちゃんと作成された。というわけで、データベース名は小文字で(mysql、oracle、postgresql、sqlite2、sqlite3)指定しよう。
-fまたは–freeze
なんだろう。直訳すると「Railsを/vendor/railsに凍結する」とあるのだが。ちなみに通常は/vendor以下にはpluginというディレクトリが作られるのだが、これを指定すると変わるのだろうか。
試してみよう。
$ rails test
これでカレントディレクトリにtestというディレクトリが作成される。
$ ls -lF ./test/vendor/ total 0 drwxr-xr-x 2 user user 68 12 29 11:42 plugins/
vendorディレクトリにはpluginがあるだけだ。今度は-fを指定してみる。
$ rails test -f $ ls -lF ./test/vendor/ total 0 drwxr-xr-x 2 user user 68 12 29 11:37 plugins/ drwxr-xr-x 8 user user 272 12 29 11:37 rails/
おお、確かにrailsというディレクトリが増えた。中身は
$ ls -lF ./test/vendor/rails/ total 0 drwxr-xr-x 9 user user 306 12 29 11:46 actionmailer/ drwxr-xr-x 10 user user 340 12 29 11:46 actionpack/ drwxr-xr-x 10 user user 340 12 29 11:46 activerecord/ drwxr-xr-x 7 user user 238 12 29 11:46 activeresource/ drwxr-xr-x 5 user user 170 12 29 11:46 activesupport/ drwxr-xr-x 16 user user 544 12 29 11:46 railties/
これらは、どうやらRuby on Railsのキモになっているファイル群のようだ。つまり、-fまたは–freezeオプションを指定すると、現在のRailsをそのまま凍結してアプリケーション配下に置いてくれるらしい。もしそうだとしたら、システムにインストールされたRails自体をアップグレードしても作成されたアプリケーションには影響が出ないということになる。なるほど。
-vまたは–version
これがないアプリケーションは礼儀作法がなっていない。Railsのバージョン番号を表示して終了する。
-hまたは–help
ヘルプを表示して終了する。
-pまたは–pretend
みんなと仲良しのふりをする。ではなく、実行時の挙動を表示して何かしらのエラー確認が出来る。実際には変更は行われない。
–force
にっちもさっちもいかないときに、フォースの力を借りて既存のファイルを上書きする。デフォルトでは既存ファイルは上書きしないのか。でも、それなら次のオプションがあるのが不可解だ。
-sまたは–skip
既存のファイルをスキップする、とある。でも、–forceで強制上書きなら、デフォルトの挙動はどっちなんだ?
$ rails test
まずは実験台を作成。
$ rails test -s
スキップで実行。同じファイルが作成されるはずだ。
$ rails test -s exists exists app/controllers 中略 identical Rakefile identical README skip app/controllers/application.rb identical app/helpers/application_helper.rb 中略 identical log/server.log identical log/production.log identical log/development.log identical log/test.log
ディレクトリについては既存のものはexistsとなって飛ばすようだ。これは当然として、他に2種類の実行結果(というのか)があることがわかる。identicalとskipで、skipはその名の通り既存ファイルをスキップしたということなのだろう。じゃあidenticalってなんだ?
今度は上書きモードで実験してみよう。
$ rails test -f exists exists app/controllers 中略 identical Rakefile identical README force app/controllers/application.rb identical app/helpers/application_helper.rb 中略 identical log/server.log identical log/production.log identical log/development.log identical log/test.log
さっきスキップされたのと同じファイルがforceで上書きされたことがわかる。identicalとされたファイルも変わらない。identicalの問題は後で調べるとして、じゃあデフォルトの動作はというと
$ rails test 中略 overwrite app/controllers/application.rb? (enter “h” for help) [Ynaqdh]
ああ、なるほど、対話モードになるのね。想像してなかった自分が間抜けでした。
でも、identilcalってなんだろう。同じファイルということ(コメントでご指摘いただきました。お恥ずかしい)だから、更新の必要なしと判断されたのだろうか。語義からすると、アプリケーション固有のファイルということだろうか。実用上、ログファイルを上書きしないというのはわかる。READMEとかもまあいい。じゃあ、Rakefileとかapplication_helper.rbは更新される必要がないファイルなのだろうか。中身を拝見。
$ cat ./test/Rakefile # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. require(File.join(File.dirname(__FILE__), 'config', 'boot')) require 'rake' require 'rake/testtask' require 'rake/rdoctask' require 'tasks/rails'
うーん、lib/tasks以下に何らかのファイルを置いて、それをrequireで呼び出して何かをさせるときに利用されるファイルらしい。Railsのアプリケーションを実際のサーバにディプロイする時なんかに使われるCapistranoと連携したりするのか。アプリケーション固有の設定情報だから、特に上書きしてしまう必要はないということなのだろう。
$ cat ./test/app/helpers/application_helper.rb # Methods added to this helper will be available to all templates in the application. module ApplicationHelper end
あれ、空っぽだ。アプリケーションのテンプレートから呼び出せるメソッドを記述できるとのことなので、これも上書きする必要はないだろうな。
なるほど。というわけで、Railsを使ったアプリケーション作成では通常いじらない、いわゆるフレームワーク側のファイルとして扱うapp/controllers/application.rbなどは–forceで上書きされるけれども、アプリケーション固有のヘルパー関数などが記述されているファイルは–skipを指定しなくてもちゃんと飛ばしてくれるということか。
-qまたは–quiet
ディレクトリの作成やら何やらの情報を出力しないモード。携帯電話をBluetoothモデムにしてパケット単位で課金されている状態で緊急の作業をしていて、lsに-lオプションを付けるだけで悲鳴を上げているような人には重宝する。
–tまたは–backtrace
エラー発生時にバックトレースを出力してくれる。試してみよう。
$ sudo mkdir test $ sudo chown root:wheel test
オプションなしで書き込み権限のないディレクトリに書き込みを試みる。
$ rails ./test/test create Permission denied - ./test/test
じゃあ同じことをバックトレース付きで。
$ rails ./test/test -t create Permission denied - ./test/test /opt/local/lib/ruby/1.8/fileutils.rb:243:in `mkdir' /opt/local/lib/ruby/1.8/fileutils.rb:243:in `fu_mkdir' /opt/local/lib/ruby/1.8/fileutils.rb:217:in `mkdir_p' /opt/local/lib/ruby/1.8/fileutils.rb:215:in `reverse_each' /opt/local/lib/ruby/1.8/fileutils.rb:215:in `mkdir_p' /opt/local/lib/ruby/1.8/fileutils.rb:201:in `each' /opt/local/lib/ruby/1.8/fileutils.rb:201:in `mkdir_p' /opt/local/lib/ruby/gems/1.8/gems/rails-2.0.2/bin/../lib/rails_generator/commands.rb:314:in `directory' /opt/local/lib/ruby/gems/1.8/gems/rails-2.0.2/bin/../lib/rails_generator/manifest.rb:47:in `send' /opt/local/lib/ruby/gems/1.8/gems/rails-2.0.2/bin/../lib/rails_generator/manifest.rb:47:in `send_actions' /opt/local/lib/ruby/gems/1.8/gems/rails-2.0.2/bin/../lib/rails_generator/manifest.rb:46:in `each' /opt/local/lib/ruby/gems/1.8/gems/rails-2.0.2/bin/../lib/rails_generator/manifest.rb:46:in `send_actions' /opt/local/lib/ruby/gems/1.8/gems/rails-2.0.2/bin/../lib/rails_generator/manifest.rb:31:in `replay' /opt/local/lib/ruby/gems/1.8/gems/rails-2.0.2/bin/../lib/rails_generator/commands.rb:42:in `invoke!' /opt/local/lib/ruby/gems/1.8/gems/rails-2.0.2/bin/../lib/rails_generator/scripts/../scripts.rb:31:in `run' /opt/local/lib/ruby/gems/1.8/gems/rails-2.0.2/bin/rails:17 /opt/local/bin/rails:16:in `load' /opt/local/bin/rails:16
こんな感じ。
-cまたは–svn
説明を読んでもわからない。Subversionを利用してファイルを修正する、とのこと。svnコマンドにpathが通っていること、と注意書きもある。
$ which svn /opt/local/bin/svn $ rails test -c svn: 警告: '.' は作業コピーではありません svn: 警告: '.' は作業コピーではありません 中略 svn: ファイル '.svn/entries' を開けません: No such file or directory 後略
いきなりエラーの連続だ。とりあえずファイルは作成された。これではいったい何のことやらさっぱりわからないが、svn addを実行しているらしいエラーメッセージなので、どうやら既にSubversionの作業コピーとなっている場所を前提にしているようだ。
$ rm -rf ./test
$ mkdir test
$ svn import ./test/ file:///Users/user/svn/rails/test -m 'rails test'
$ rm -rf test
$ svn co file:///Users/user/svn/rails/test
$ rails test -c
svn: 警告: '.' は作業コピーではありません
svn: 警告: '.' は作業コピーではありません
exists
create app/controllers
A test/app
後略
こんな感じで、railsコマンドで作成されるスケルトンをsvn addしてくれた。あとはコミットして作業を続けるだけ。なるほど。
とりあえず、以上、railsコマンドのヘルプの中身拝見でした。
Dec - 23rd
Ruby on Railsとsqlite3
Posted at 11:08 pm | Filed Under Ruby
ええと、Railsがデフォルトで利用するデータベースがMySQLからSQLiteになったということは、MacPortsでrb-sqlite3を入れておかないと、解説書の最初に載っているようなデモ用のなんでもないアプリケーションでいきなりドバドバとエラーが出てしまうってことだよな。特にRailsをアップグレードした人なんかは。
Sep - 9th
生産性
Posted at 11:47 am | Filed Under Ruby
孫引きなのだが、Rubyのまつもと氏の発言とされる以下の記事:
【XDev】「とりあえず作って,後から作り直せ」,Rubyのまつもと氏が語るエンタープライズ開発:ITpro:
「エンタープライズRubyに関しては,実績もあるし,生産性が高いことも認められ始めてきた。楽天が社内向けシステムをRubyで開発したところ,JavaやPHPと比べて,生産性が1.6倍から3倍に上がった」
これ、どうやって生産性を計測しているのだろうか。
もっともありがちなのは、JavaやPHPでだいたい同規模のソフトウェア開発をやってみた時の実際の工数と比較すると、Rubyで作った場合の方がこれくらい早く仕上がった、という計測の仕方だろう。いや、実際にそうやったんだろう、と下司の勘ぐりをしているわけではない。よくあるパターンだとこうだよね、という話。
でも、プログラミングの世界では、生産性の個人差が大きいので、1.6倍や3倍といった程度の違いなら、個人のスキルで十分に吸収されてしまう誤差でしかなかったりする。
また、もっとベタな状況を想像してみよう。10人の開発者を抱えるごく普通のソフトウェア会社があるとする。主にPHPを使ってウェブアプリケーションを作っている。普段は数人のチームに分かれて開発をしている。よく出来る人にそうでもない人を混ぜているので、どのチームも生産性はいつもだいたい同じくらいだ。そこへ、新任のプロジェクト管理者が登場し、プログラミング言語による生産性の違いを計測するために、それぞれのチームにPHP、Java、Rubyをそれぞれ使わせて、同じ内容の社内向けアプリケーションを開発させてみた。
あまりの贅沢さにトム・デマルコの本に出てくるトムキンスが聞いたら呆然とするかもしれない。まあ、仮定の話なので細かいところはいいとしよう。
だが、この実験プロジェクトを遂行するにあたっては、様々な問題が噴出した。最初の問題は、では誰がJavaやRubyのプロジェクトを担当するか、だ。普段の業務で使っている言語なら、どのメンバも短くても半年、職歴の長いメンバなら5年以上の実務経験がある。そこへ、急に別の言語の仕事がまわされるかもしれなくなったのだ。どちらかを選べといわれれば、平均的な開発者であれば、普段使っている言語の方を選びたがるだろう。コーディングを担当する者にとって、目標とする期日までに業務を完了させることは、決してないがしろにできないミッションでもある。キャリアの浅い言語のプロジェクトは敬遠するはずだ。誰だって、期日前に受けるプレッシャーは避けたいものだ。それが直接人事考課に響くなら、当然のことだろう。
そんな状況でJavaやRubyでの開発を受けるような人間は、以前Javaの現場にいたか、あるいはRubyを独学していて、それなりの勘所を押さえているに違いない。
もうお分かりかと思うが、独学で実務では使っていない言語を習得しようとするような意欲的なプログラマに、生産性の低い人間はほとんどいない。だから、必然的に普段から他人の何倍もの生産性を発揮しているかもしれない。
JavaとPHP、Rubyのチーム編成を考えると、RubyなんかさっぱりわからないメンバをRubyチームに入れたり、JavaとJavaScriptの違いもよくわかっていないようなメンバをJavaチームに組み入れたりするのは、いくらなんでも問題だろう。データとして使い物にならない上に、下手をすると普段の業務で発揮していた力を出せないことを苦にメンバに退職されてしまうかもしれない。誰だって自尊心は必要なのだ。というわけで、だいたいのケースでは、意欲的なプログラマがRubyチームに入ったりする。
そうなると、まつもと氏が語っているような、Rubyで生産性向上があったというのは実際にはちょっと違っていて、言語など関係なく、元々生産性の高い人を集めたチームが、他の言語で開発していたチーム(つまり、優秀な人を除いた、まあ平均的技能のチーム)より高い生産性を発揮したという、ごく当たり前の結果がなんだか視点によって違うもののように見えてしまっているだけだということも十分にあり得る。少なくとも、自分の会社でこんなベンチマークをやろうとしたら、きっとこんな風な事態になると思う。
もちろん、個人的にはRubyはとにかくガシガシ書けるし、テスト可能性の高いコードを書こうと思ったらあれこれと便利なところがたくさんあって、個人的にはとてもいい言語だと思う。しかし、生産性の計測という点では、もうちょっと詳しく書いてほしかったな。
追記:
まず、記事の総論にはぜんぜん反対じゃない、というのははっきりしておきたい。もちろん、誰も正解がわからないソフトウェア開発で「速く作って、後から直す」という主張が出てくる背景も理解できる。ただし、これはRuby on Rails絡みのよくある話にもいえることだが、ソフトウェアを直すというのはとんでもないコストがかかることなので、いくら素早く開発してリリースすることが出来たとしても、この「後から直す」を軽視するととんでもない事態になる、というのは強調しないといけない。このやり方では初期開発費用は抑えられるけれども、ソフトウェアが軌道に乗るまでの費用が全て減るわけでは決してない。開発費と保守運用費のトレードオフになることを、開発者だけじゃなくて経営側、営業側も理解しないと、リリース後の下らないゴタゴタでひどい苦労をすることになる。つまり、単純にいえば、今までのやり方では直すことを前提としていないために見積もられなかった改修費用を、最初から折り込んでおきましょう、という話なのだ。変化に対応出来る体制というのは、こういうお金の絡むところの体制と、関連スタッフの意識の体制でもある。
Jul - 4th
ホーア式・初級
Posted at 10:50 pm | Filed Under PHP, Ruby, Tuit, Work
仕事メモ。今日のミーティングはむちゃくちゃ面白かった。
=========================================
関数を2種類に分類しよう:
(1)query(accessorなど呼称多数)
(2)command(actionなど呼称多数)
*例外はある。
constructor(初期化したり…)、utility(掟破りでもあると便利なもの)など
■queryとは
SQLのSELECT文のように、実行されてもスタックやオブジェクトの状態に何ら影響を与えない関数
■commandとは
実行されることで状態に何らかの影響がある(副作用がある)
逆にいえば、関数をこの分類に従って書くことで享受出来るメリットがある。
■メリット
明確なやり方でユニットテストを計画、実施することができる。
■ホーア式:pre {command} post
pre状態でcommandを実行、例外が出なければpost状態になると真、という式。
■コードで考える
関数、といってもクラスのメソッドで考えることしか思いつかなかったので、とりあえず簡単なクラスで考えてみた。
===========================================
#!/usr/bin/env ruby
class TestClass
#query型の関数
def testQuery
@testValue
end
#command型の関数
def testCommand
@testValue = "これはテストです。"
end
end
#このクラスをテストする
test = TestClass.new
#pre状態(nilになる)
puts test.testQuery
#command実行
test.testCommand
#post状態(これはテストです。になる)
puts test.testQuery
===========================================
この場合、ホーア式は
test.testQuery === nil;←これがpre
{test.testCommand;}←command
test.testQuery == 'これはテストです。';←大方の予想通りこれがpost
となる。
preとpostを定義して、command実行後にそれぞれの結果を参照すればcommandに対するユニットテストになる。
問題があるとすれば:
commandで例外が発生した場合を考慮する必要がある。
commandで無限ループになった場合、post状態にならないのでホーア式が真偽判定できない。
*できないのだから偽、という考え方もある
ユニットテストでエラー出力レベルを上げる、無限ループはエラーとする、実行時間を仕様として決めて時間がかかりすぎるようであればエラーとする、などのルールで対応可能。
keep looking »

