2014年6月25日水曜日

プログラミングにおける美的センスの必要性

これはよく言われる話だし、おそらくこの文章をお読みの方も散々目にした、あるいは耳にした話だと思いますけれども、やっぱりプログラミングには美的センスって必要だと思うんですよ。

もちろん、画家や音楽家のようなアートのセンスを持てと言っているわけでは全然ないんですよ。ぱっと見た目で整っているかどうかって話です。例えば本棚に本を入れる際に、大きさの順に並べて同じ大きさの本同士をまとめたら、きれいに見えますよね。その中でもさらに似たような本をまとめるとか、ハードカバーは右に置くとか、規則性をもたせれば更にきれいに見えます。そういった話であって、決して困難な話ではありません。

以前も書いていますが、今の時代は単に動けばいいというプログラミングはもはや許されません。プログラムのソースコードは人の手から手へと非常に速いスピードで引き継がれます。ですから「他人に読みやすいプログラム」を書くことも優れたソフトウェアエンジニアの必要条件の1つだと言えるでしょう。

そして当然のことながら、他人に読みやすいプログラムは例外なく美しいです。ロジックがよく整理されると自然と記述の順序も規則性を持ってきますし、変数やメソッド名といった命名規則も整ってきますから、見た目も当然美しくなるのです。

僕は「自分のソースコードは芸術作品だ」と言えるくらいになるよういつも心がけています。時間の制約の中で、高い品質ど美しさをどれだけ追求できるか、日々挑戦し続けています。これもプログラミングの醍醐味だと思うんですよね。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年6月24日火曜日

UI設計とは想像力である

良いUIを設計するのには何が必要なのか、と問われたら、僕は「想像力」と答えます。実際にそのシステムを利用するユーザは誰で、どんな目的で、いつ、どういうタイミングで使うのか。場合によってはそのユーザの置かれている立場や、年齢、性別、性格、あるいはその時の気分まで想定することによって、いかにリアルに使っている時の状況を思いうがべることができるか。そこの勝負だと思うんですよね。

さらに、ボタンや入力欄などの要素はどう配置たら使いやすいのか、実際に自分がそのシステム使っていると想像する時のリアルさが研ぎ澄まされているほど、使いやすさに考慮したデザインが出来るでしょう。

確かに経験があれば、ある程度かこに制作したUIとの類似点から、とりあえず失敗しない程度のUIを作ることは出来るかもしれません。しかし、あらゆるプロジェクトは同じように思えても1つ1つ違うものです。そして、そのUIに触れるユーザもまた、その類のシステムに触れるのは初めてかもしれないのです。であれば、例えば自分が初めてそのUIを触れているようなつもりになって、まっさらな気持ちで見なおしてみることで、どうすればもっと改善できるかが見えてくるじゃないか、と思っています。だからこそ想像力が必要だと思うんですよ。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年6月21日土曜日

文章記述力とプログラミング能力の関係

昨日、プログラムを読むことに読解力が関係すると書きました。ではプログラムを書く方はどうなんでしょうか。僕はプログラムを書く方についても、少なからず文章を書く力(特に論理的文章を書く力)が影響すると思っています。

これは、時代の変化という影響もあると重んですよね。例えば、昔はマシンのスペックが今よりもずっと貧弱であったため、プログラム自体の容量を小さくする必要があり、ソースコードそのものを短くすることに結構意味がありました。

ところが今はメモリ容量は劇的に多くなり、プログラム自体の容量なんか、普通に記述する分にはまるで気にしなくてもいいと言っても構わないくらいになりました。つまり、無理に短く記述するよりも、ロジックに無駄さえなければ、少々ソースコード自体が増えても分かりやすい書き方を選択する意味のほうが大きくなったと言えると思います。メモリ消費という意味ではソースコード自体よりも、ロジックの実行時に消費する用が重大であって、その意味でもロジックを整理してわかりやすく書くことで、無駄なロジックを紛れ込ませて余計にメモリ消費するリスクも減らせます。

というのは、昔はプログラムというのはある程度の時間を掛けて書き、一度書き上げてシステムに組み込んだら、その後はあまりいじらないことも多かったわけです。だから完成後のソースコードが読みやすいかどうかというのはあまり意味を持たなかったかもしれません。

ところが、今はプログラムを素早く仕上げ、さらに頻繁に書き換える、しかも担当者も違うという状況も珍しくありません。となると、自分以外の別の担当者が読んで分かりやすいコードを書くことには大きな意味があります。

ロジックを分かりやすく整理し、他人に読みやすいということが重要、となると、これはまさに論理的文章の記述力がプログラミングにもリンクしてくる状況になったと言ってもよいと思うのです。実際、世界的に有名な開発者は文章も上手かったりしますし。

プログラムは単に動けばよいという時代はもはや終わっているというのは、すでに周知の事実でみんな分かっていることでしょう。読み手のことを考えるコーディングが求められていると思います。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年6月20日金曜日

他人のソースコードを読む際に必要な"読解力"

昨日の投稿で「コメントをしっかり書くべき」だと書きましたけれども、現実問題としてコメントが圧倒的に少ないソースコードを読まなきゃいけない場合もあるわけでしてね。その時には仕方ないんでとにかく意味を頭の中で補完しながら読むしか無いわけです。

ところで、「他人の書いたものを、書いた人間の意図や目的を推測しながら読む」というのは、まさに「読解力」だと思ったりもするわけです。これは普通の文章でもプログラムのソースコードでも共通しています。

もちろん単純に自然言語の読解力とプログラミング言語の読解力を比べることはできませんけれども、繋がっているとは思うんですよね。

エンジニアの世界だと技術的な要素ばかりが注目されがちですし、まあ事実優先順位としては技術的な要素が上で当たりまえではあるんですけど、実は「読解力」も欠かせないんじゃないかと思っています。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年6月19日木曜日

ソースコードのコメントはしっかり書くべき

プログラミングにおいて、"ソースコードに記述するコメント"に対するスタンスとして、

「コメントは極力書かないようにする。なぜなら、ソースコードを見てそのまま意味が分かるのが良いプログラムであるからだ。」

という意見があります。僕もまだプログラミングを覚えたての頃はそうかもしれないと思い、自分がコメントの少ないソースコードを読むのに苦労しているのは、単純に経験不足のせいだと思っていました。

しかし、何年も実際に仕事でプログラミングの経験を積んだ現在、はっきりと断言できます。コメントを書くべき場合というのは存在します。「ソースコードを見てそのまま意味が分かるのが良いプログラムである」という言葉を単純に信じてはいけません。そして、コメントを書かなくて困る場合はあっても、書きすぎて困ることはありません。だから僕はソースコードのコメントはしっかり書くべきだと思っています。

たとえソースコードの意味が完全に理解できたとしても、外部から渡される引数の意味や取りうる値など、ソースコードには書かれないものの動作を理解するために不可欠な情報がありますからね。

また、ソースコードをどのような意図で書いたのか、など、ソースコードを読む開発者にとって知りたい情報もあります。

もちろん書きすぎはよくありませんが、オブジェクト指向プログラミングならば、全てのメソッドについて目的と各引数・返り値の説明くらいは例外なく全部コメントを書くべき、というのが僕の意見です。

そんなのソースコード見りゃ分かるって?嘘ですね。確実に嘘です。

だったら、他人の書いたソースコードを読んでいる時を思い出してみればいいですよ。全くコメントがない状態だと、たった1行からなるメソッドですら、そのメソッドの意図や引数の取りうる値やどんな返り値になるのかを推測するのが大変な場合なんて腐るほどあるでしょ。この業界でエンジニアやってれば経験があるはずですよ。

結局コメントを書くのに反対する意見って単純にコメント書くのが面倒なだけって気がするんですよね。いいから書こうよ、って思います。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年6月18日水曜日

自動テストのためのid属性とclass属性の使い分け

HTMLのid属性とclass属性の使い分けについては、「id属性はHTML文書内でユニーク(重複しない)であり1つの要素に1つのみ指定、class属性は重複可能で1つの要素に複数使用できる」という特性はあるものの、以前は使い分けは結構曖昧でした。

が、最近になって以下のように使い分けるようになりました。

  • id属性 → 自動化テストの要素特定のために使う。
  • class属性 → CSSによるスタイルの指定に使う。

理由を説明します。

id属性は単一指定だがclass属性は複数指定可能という特性から、自動テストという目的にはid属性のほうが向いています。複数指定だと、意図しない要素が指定されてしまったりする可能性もあり、テスト記述に余計な気を使うことになります。よって、勘違いやミスによるバグの発生はid属性のほうが少なくなると考えられます。

逆にスタイルの指定という観点からすれば、複数指定で重ねがけできるclass属性は都合がよく、CSSに使うのはclass属性でしょう。クラスの名前を工夫すればid属性は使わなくても済みますから、テストコードとデザインを分離することが可能です。

ちなみに動作速度はid属性指定のほうが速いらしいですが、どちらもブラウザの体感速度としては無視できるくらいに速いので、ユーザビリティの観点からは実質的に差はないです。しかし、自動テストでは幾度と無く表示を高速に繰り返すので、id属性指定の効果がある・・・かもしれません。「かもしれません」と書いたのは、実際にまだそこまで大量のテストを書いて試していないからです。まあ、動作速度としてはそのくらい差はわずかでしかないです。

上記の書き方に従えば、一つの要素に同じ値のid属性とclass属性を付ける場合も多いです。でもこれで自動テストとスタイリングの分離ができると考えれば、そんなに大した話ではないと思ってます。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年6月17日火曜日

Ruby/LDAP 経由で LDAPS 接続

Rubyから手軽にLDAP接続するのに使えるRuby/LDAPライブラリですが、LDAPS接続をする方法について、あまり実際の例がないように思います。今回、実際にやってみたので、ポイントを書いておこうと思います。

ポイントは3つ。
  • LDAP::Connの代わりにLDAP::SSLConnを使うこと。
  • 接続先ポート番号を明示的に指定すること。
  • LDAPクライアント用設定(/etc/ldap/ldap.conf)で証明書検証設定を正しく設定すること。
以下、やってみた時の経緯です。
今までSSLなしのLDAP接続はできていました。単にLDAP::Conn を LDAP::SSLConn に置き換えればいいと思っていたんですが、エラーになってしまいます。例をirbで示してみますと、こんな感じです。

irb(main):001:0> require 'ldap'
=> true

irb(main):003:0> conn = LDAP::SSLConn.new('localhost')
=> #<LDAP::SSLConn:0x000000021130d0>

irb(main):004:0> conn.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
=> #<LDAP::SSLConn:0x000000021130d0>

irb(main):006:0> conn.bind(‘cn=hoge,dc=foo,dc=localhost’,’pass')
LDAP::ResultError: Can't contact LDAP server
from (irb):6:in `bind'
from (irb):6
from /usr/bin/irb:12:in `<main>'

よく調べてみると、ポート番号があやしいことに気づきました。公式ドキュメントによると、デフォルト値が定数LDAP_PORTになっているんですが、LDAP::LDAP_PORTはSSLではない接続用のポート番号(389)が入った定数なのです。

実はLDAP::LDAPS_PORTの方がSSL接続用のポート番号(636)が入った定数なんですね。なぜデフォルト値がLDAPS_PORTじゃなくてLDAP_PORTなのかはよく分からないのですが、もしかして、LDAP::SSLConnはLDAP::Connのサブクラスだからかもしれません。※参考→http://ruby-ldap.sourceforge.net/rdoc/classes/LDAP/SSLConn.html

ということで、今度はポート番号にLDAP::LDAPS_PORTを指定して再試行してみます。

irb(main):008:0> conn = LDAP::SSLConn.new('localhost', LDAP::LDAPS_PORT)
=> #<LDAP::SSLConn:0x0000000211c180>

irb(main):009:0> conn.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
=> #<LDAP::SSLConn:0x0000000211c180>

irb(main):010:0> conn.bind('cn=admin,dc=test,dc=localhost','admin')
LDAP::ResultError: Can't contact LDAP server
from (irb):10:in `bind'
from (irb):10
from /usr/bin/irb:12:in `<main>’

しかし、まだ同じエラーです。slapd側のログを見ると、接続自体は確立されているようです。このようなログが残っています。

conn=1013 fd=13 TLS established tls_ssf=128 ssf=128

このログはポート番号修正前には記録されなかったので一歩前進です。LDAPのSSL接続について更に検索すると、こんな情報が見つかりました。→http://christian.hofstaedtler.name/blog/2009/05/ruby-ldap-ssl.html

要するに/etc/ldap/ldap.confにクライアント設定が原因かもしれないということ。試しに、/etc/ldap/ldap.conf の TLS_CACERT をサーバ側の cn=config で設定している
CA証明書と同じ証明書を指定するようにしたところ、接続に成功しました。

なるほど。サーバ証明書をCA証明書で検証していたわけですね。しかしそもそも、証明書検証が必要なければ省略する方法もあります。※参考→https://wiki.debian.org/LDAP/OpenLDAPSetup#Configuring_LDAPS

/etc/ldap/ldap.conf で TLS_CACERT をコメントアウトし、TLS_REQCERT を never に設定すればいいですね。今回はこちらを採用します。

ということで、irbでもう一度試してみます。

irb(main):001:0> require 'ldap'
=> true

irb(main):002:0> conn = LDAP::SSLConn.new('localhost',LDAP::LDAPS_PORT)
=> #<LDAP::SSLConn:0x0000000166b9e8>

irb(main):003:0> conn.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
=> #<LDAP::SSLConn:0x0000000166b9e8>

irb(main):004:0> conn.bind('cn=admin,dc=test,dc=localhost','admin')
=> #<LDAP::SSLConn:0x0000000166b9e8>

これで接続に成功することを確認できました。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年6月14日土曜日

slapdをSSL対応で起動させる(Debian)

LDAPサーバをSSL対応(LDAPS)で起動させる際には、サーバ証明書、CA証明書、秘密鍵の3つを設定します。cn=configの属性にこんな感じで設定することになります。

dn: cn=config
olcTLSCACertificateFile: /etc/ssl/certs/ssl-cert-snakeoil.pem
olcTLSCertificateFile: /etc/ssl/certs/ssl-cert-snakeoil.pem
olcTLSCertificateKeyFile: /etc/ssl/private/ssl-cert-snakeoil.key

ちなみにsnakeoilというのはサンプル用証明書によく使われる名前です。

それから、/etc/default/slapd の SLAPD_SERVICES に ldapsプロトコルを含めます。下の例は、SSL対応のLDAPS通信とソケット通信のみ受け付けるよう設定する例です。

SLAPD_SERVICES="ldaps:/// ldapi:///"

これで起動すればOK・・・と言いたいところが、すんなりとはうまくいくと限りません。

main: TLS init def ctx failed: -1

というエラーが発生することがあります。このエラーは、証明書や秘密鍵の読み込みに失敗すると発生します。調べてみると、Debianでは、デフォルトのままだと秘密鍵の読み取り権限がないようです。参考 → https://wiki.debian.org/LDAP/OpenLDAPSetup#Configuring_LDAPS

秘密鍵は所有者のグループを ssl-cert にし、グループに読み取り権限を付与することが推奨されています。slapdはopenldapというユーザ名で実行されますので、openldapユーザをssl-certグループに所属させればOKです。

# usermod -a -G ssl-cert openldap

これで今度こそOKです。

# service slapd start

とすればSSL対応のLDAPS通信を待ち受ける状態で起動するはずです。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年6月13日金曜日

RSpecの"-fs"オプションが無効になったことへの対策

Padrinoのプロジェクトにおいてrake 経由でRSpecのテストを実行しようとして、今まで起きていなかったエラーが突然起きました。

`find_formatter': Formatter 's' unknown - maybe you meant 'documentation' or 'progress'?. (ArgumentError)

スタックトレースを見てもソースコードの原因箇所が的確に示されていなかったので少々戸惑いましたが、結局原因はspec/spec.rakeファイルにありました。そこではspecコマンドのオプションとして"-fs —color"が指定されていたんですが、実はこの"-fs"が原因でした。"-fs"は"--format specdoc"の短縮形式で、以前はこのオプションは有効だったが無効になったみたいです。

現在はフォーマットとして指定できるのは"documentation"か"progress"になっていますが、詳細な情報を出力したいので"documentation"を指定することにします。この指定にあわせてspec/spec.rakeファイルを修正しようと思います。

Padrinoが自動生成する修正前のspec/spec.rakeファイルはこんな感じです。

begin
  require 'rspec/core/rake_task'

spec_tasks = Dir['spec/*/'].inject([]) do |result, d|
 result << File.basename(d) unless Dir["#{d}*"].empty?
 result
end

  spec_tasks.each do |folder|
    RSpec::Core::RakeTask.new("spec:#{folder}") do |t|
      t.pattern = "./spec/#{folder}/**/*_spec.rb"
      t.rspec_opts = %w(-fs --color)
    end
  end

  desc "Run complete application spec suite"
  task 'spec' => spec_tasks.map { |f| "spec:#{f}" }
rescue LoadError
  puts "RSpec is not part of this bundle, skip specs."
end

これを以下のように変更します。

begin
  require 'rspec/core/rake_task'

  spec_tasks = Dir['spec/*/'].inject([]) do |result, d|
    result << File.basename(d) unless Dir["#{d}*"].empty?
    result
  end

  spec_tasks.each do |folder|
    desc "Run spec suite under #{folder} directory"
    RSpec::Core::RakeTask.new("spec:#{folder}") do |t|
      t.pattern = "./spec/#{folder}/**/*_spec.rb"
      t.rspec_opts = %w(--format documentation --color)
      t.verbose = false
    end
  end

  desc "Run complete application spec suite"
  RSpec::Core::RakeTask.new("spec") do |t|
    t.pattern = "./spec/**/*_spec.rb"
    t.rspec_opts = %w(--format documentation --color)
    t.verbose = false
  end
rescue LoadError
  puts "RSpec is not part of this bundle, skip specs."
end

変更点としては、-fsオプションをやめて--format documentationにしたこと、
t.verbose = falseを追加したこと、"spec"タスクを"spec:フォルダ"の集合ではなく
新たに全specファイルを含むタスクとして定義したことです。

t.verbose = falseを指定するのは、長ったらしい実行コマンドの出力を省いて出力を見やすくするためです。また、"spec"タスクが"spec:フォルダ"の集合だと、何件成功して何件失敗したか、という結果がフォルダの実行ごとに表示されるので見づらくなるので、対象を全てにした1つのテストスイートにまとめなおしています。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年6月12日木曜日

ソフトウェア開発への日本社会の評価が低すぎる

書籍「ソフトを他人に作らせる日本、自分で作る米国」を読んで考えたことシリーズ3日目。米国と日本の違いの全般的な方向性として、ソフトウェア開発そのものに対して価値を認めている度合いが、日本はまだまだ少ないように思います。

日本において、形のないもの・直接目で見ることができないものに対する評価の低さは、何もIT業界に限った話ではないと思います。他の業界でも「ソフトウェア的なもの」に対しては、物質として形のあるものに比べて、実際に掛かる労力や手間やコストに対して評価が低すぎると感じることは多々あります。率直に言って、ソフトウェア開発者の社会的地位は、本来であればもっとずっと高くてもおかしくないと思っています。

これは推測に過ぎませんが、まず、ソフトウェア開発が本当に重要な役割を担うようになったのは最近のことで、ただでさえ意識の変化がゆっくりな日本人はついてこれていない可能性があること。そして日本は一度戦争でインフラをガッツリ失った後に急激に再構築していますので、ソフトウェア的なものよりもハードウェア的なものに対する評価が高くなりやすい時代があったこと。などの理由が重なって現在のような状態になっているのかもしれません。

今後、ソフトウェア開発はもっともっと社会の中で重要な役割、根幹を為す要素となっていくはずです。その時には日本人の意識も変わってくると思っています。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年6月11日水曜日

「クラウド」「ビッグデータ」という言葉の罪

昨日に引き続き、書籍「ソフトを他人に作らせる日本、自分で作る米国」より、気になった部分に関して。第3章『「言葉のインフレ」は恐ろしい』の中で、キーワードとして「Web2.0」「クラウド」「ビッグデータ」が出てきます。ITの世界に少しでも関わりを持つ人なら必ず聴いたことのある言葉だと思いますが、どれも明確な定義のないまま言葉だけが独り歩きしているという点では共通しています。

僕がIT業界に入った時には「Web2.0」は既に死語になりつつあった気がしますが、「クラウド」は流行り始めた頃でした。当時からこの「クラウド」という言葉が氾濫し、なんでもかんでも「クラウド」とつけりゃいいと思ってんじゃないかと疑いたくなるほど節操なしに使われるのを、物凄い違和感とともに見てました。

昔からあったものと結局中身は同じだという本書の指摘は僕だけでなく多くのIT関係者(特に技術者・エンジニア)には共感できるものだと思います。正直、言葉は悪いですが、クラウドクラウドと浮かれている連中はバカなんじゃないかと呆れている、というのが本音のところでしょう。「クラウド」という言葉をグーグルの社員は軽蔑している、という話が出てくるのですが、全く驚くような話ではなく、「やっぱりそうか」と納得したというのが素直な印象です。

最近ではまた性懲りもなく「ビッグデータ」という新たな言葉が氾濫してますが、結構うんざりしてます。残念なことは、そういう曖昧な言葉を喧伝する連中が業界内部にいて、商売目的に使ってるってことです。もっと露骨な表現をするなら、ITの知識が無い顧客をうまいこと騙して、さも全く新しい製品であると見せかけてカネを搾り取りカモにする都合のいい道具として、「クラウド」とか「ビッグデータ」とかいう曖昧な言葉を使っている、と言っても過言ではないと思います。僕の目にはほとんど詐欺に見えます。

それでも一度広まってしまった言葉は使わざるを得ない、と本書でも述べられてますが、だったら最初に広めるのが悪いわけでしてね。結局IT業界が曖昧な言葉を使えば稼げるという経験に味をしめているように思えて仕方ありません。

もっと技術に対して、あるいは顧客に対して誠実であるべきだと思うのは僕だけではないんじゃないでしょうか。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年6月10日火曜日

内製化はエンジニアにとって良い流れだ

書籍「ソフトを他人に作らせる日本、自分で作る米国」を読みました。タイトルに惹かれて買った本ですが、面白かったです。読みながら思ったことは、やっぱり日本の場合、ソフトウェア開発に対しての評価が低いっていう、認めたくないが厳然たる事実があるってことなんですよね。

ソフトウェア開発を外注にホイホイ投げてしまうのだって、ソフトウェア開発がビジネスでクリティカルな役割を担っているならばありえない選択肢ですし。いや、もし本当にソフトウェア開発がクリティカル「ではない」領域ならば構わないです。しかしやはり、本来は内製すべきソフトウェア開発を安易に外注に投げている場合が多すぎる、と僕の感覚では思います。

プロジェクトのマネジメント手法やソフトウェア開発手法を、ただ輸入して真似してみたところで上手くいかないのも、そもそも内製を前提としている手法なのに、外注に無理やり当てはめているから、という側面もあるでしょう。アジャイルなんて特にそうであって、あれはどう見ても内製を前提としてるようにしか思えないです。

とまあ、なんかネガティブな感じに書いてしまいましたが、近年の内製化の流れは、このへんのソフトウェア開発軽視の価値観が改善されてきたことの1つの表れかもしれないと思ったりします。

ちなみに僕はソフトウェア開発の経験として内製と外注の両方の経験がありますけれども、明らかに内製の方がいい環境だと思いました。内製と外注では、工数・スケジュール・要件・技術仕様・そしてエンジニアへの報酬に至るまで、考え方が全然違ってしまいます。例えば外注を請け負う立場の場合、考え方は「受注・納品」であり、この場合エンジニアへの報酬は単なるコストでしかありません。一方内製の場合には考え方は「投資・回収」です。するとエンジニアへの報酬は投資の一部となります。この辺の考え方の違いについては別の機会にまた書きたいですね。

ともあれ、実感としてはソフトウェア開発の内製はエンジニアにとっても良い環境を生みやすいので、近年の内製化の流れはいい流れだと思っております。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年6月7日土曜日

SCSSが便利すぎる

WebアプリのUIを組んでいて、今回初めてSCSSを使ってみているわけですが、便利さにびっくりしてます。以前からその便利さはネット上で色々語られているので知識としては知っていましたが、やっぱり自分で実際に使うと実感できますね。

今回SCSSを書くこと自体初めてでしたが、ものの30分くらいで全く問題なく作業を進められるようになりました。記法としてはSASS記法ではなくSCSS記法です。ネットを見ているとHAMLを採用したSASS記法の方が記述量が少なくすっきりしていて、そちらが好みの人もいるらしいですが、僕はCSSの書き方が流用できるSCSS記法を支持します。

なお、LESSという選択肢もありますが、Rubyを採用したプロジェクトなのでSCSSと相性が良さそうという理由でSCSSを選択しました。Padrinoフレームワークは、.scssファイルを自動的に.cssファイルに変換してくれます。

うーん、一度実際に体験してしまうと、もはや生のCSSなんて書きたくない(笑)

ちなみにSCSSの書き方詳細についてはネット上に良質な解説サイトが沢山あるのでそちらをご参照ください。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年6月6日金曜日

Capybaraで入力欄・ボタン・リンクをCSSセレクタによって指定する

CapybaraはWebアプリケーションをテストする際に便利なライブラリです。ところで、フォームに入力するメソッドとしてはfill_inがありますが、要素の指定にCSSセレクタが使えません。click_button, click_linkも同様にCSSセレクタが使えません。ではXPathは使えるかというと、XPathも使えません。指定できるのは単なる文字列で、例えばfill_inの場合、その文字列がid属性またはname属性または対応するlabel要素の文字列に合致するものを探すことになっています。他のfindなどの要素指定メソッドではCSSセレクタを使うので、他のメソッドとの統一性がなくて分かりにくいし、細かい指定もできないので、CSSセレクタを使いたいところです。

単純な解決策としては、findメソッドで要素を絞り込んでsetやclickすればいいわけですが、それだけだともとの機能と同一にはになりません。というのは、fill_in, click_button, click_linkの特長として、それぞれのタイプに合致する要素だけを探してくれるのです。例えばfill_inならば入力可能フィールドのみを検索対象としてくれます。

これは、Capybaraのこのソースコードを見るとよくわかります。

例えばfill_inの場合、"fillable_field"という条件で探してsetしています。これと同じことをできればいいわけです。条件の詳細はXPathライブラリのこのソースコードを見るとわかります。

これによるとfill_inに使われる"fillable_field"の条件は、

  • input要素あるいはtextarea要素であり、
  • type属性が次のいずれでも「ない」:'submit', 'image', 'radio', 'checkbox', 'hidden', ‘file'

click_buttonに使われる"button"の条件は、

  • input要素かつ、type属性が次のいずれかである:'submit', 'reset', 'image', ‘button'
  • または、button要素である

click_linkに使われる"link"の条件は、

  • a要素かつ、href属性が存在する

となっています。これらの条件を満たすように、CSSセレクタに条件を付加してXPathに変換するメソッドを作ってみました。

# CSSセレクタをXPathに変換し、さらに入力可能フィールドの条件を付加する
# @param [String] css CSSセレクタ
# @return [String] 変換後のXPath
def convert_css_to_fillable_xpath(css)
  base_xpath = %(#{Nokogiri::CSS.xpath_for(css)[0]})
  # init_xpath = %(#{base_xpath}[../input or ../textarea])
  # new_xpath = ['submit', 'image', 'radio', 'checkbox', 'hidden', 'file'].inject(init_xpath) do |tmp_xpath, attr|
  #   %(#{tmp_xpath}[not(@type='#{attr}')])
  # end
  # new_xpath
  # 高速化のためベタ書き
  "#{base_xpath}[../input or ../textarea][not(@type='submit')][not(@type='image')][not(@type='radio')][not(@type='checkbox')][not(@type='hidden')][not(@type='file')]"
end

# CSSセレクタをXPathに変換し、さらにボタンの条件を付加する
# @param [String] css CSSセレクタ
# @return [String] 変換後のXPath
def convert_css_to_button_xpath(css)
  base_xpath = %(#{Nokogiri::CSS.xpath_for(css)[0]})
  # xpath_input = %(#{base_xpath}[../input][@type='#{['submit', 'reset', 'image', 'button'].join("' or @type='")}'])
  # xpath_button = %(#{base_xpath}[../button])
  # "(#{xpath_input} | #{xpath_button})”
  # 高速化のためベタ書き
  "(#{base_xpath}[../input][@type='submit' or @type='reset' or @type='image' or @type='button'] | #{base_xpath}[../button])"
end

# CSSセレクタをXPathに変換し、さらにリンクの条件を付加する
# @param [String] css CSSセレクタ
# @return [String] 変換後のXPath
def convert_css_to_link_xpath(css)
  base_xpath = %(#{Nokogiri::CSS.xpath_for(css)[0]})
  "#{base_xpath}[../a][@href]"
end

少々解説します。Nokogiri::CSS.xpath_for は、複数条件CSSに対応するため、返り値はXPath文字列を要素に持つ配列になります。入力欄を選ぶという状況的に、1つのみになると考えられるので最初の要素を選択すればよいから、Nokogiri::CSS.xpath_for(css)[0]としています。

変換したXPathの後ろに例えば [../input or ../textarea] といった要素指定を付加しています。これは、要素自身がinput要素あるいはtextarea要素であることを後から無理やり指定するためです。スマートではない形式ですが、CSSセレクタを変換して条件を付加するという状況なので仕方ありません。

最初はメソッド中のコメントアウトしてある部分のようにかなり動的にXPathを生成していましたが、テスト実行を少しでも早くするために、文字列をベタ書きしておくことにしました。

さて、使い方としてはCSSセレクタを渡してXPathに変換し、その条件でノードを取得して動作を行えばよいです。Cucumberによる例を挙げると、こんな感じでステップ定義に使えます。何やら余計なものが色々付いているように見えますが、その辺の詳細は以前に書いたこの記事を参照。

もし(/^".*?\((.*?)\)" 欄に "(.*?)" と入力(?:し(、)|した)(\(非同期\))?( debug)?$/) do |t_css, t_with, t_eos, t_async, debug|
  show_debug_info if debug
  xpath = convert_css_to_fillable_xpath(t_css)
  page.find(:xpath, xpath, :visible => true, :wait => t_async ? async_time : 0).set(t_with)
  puts '' if t_eos.blank?
end

もし(/^".*?\((.*?)\)" ボタンを押下(?:し(、)|した)(\(非同期\))?( debug)?$/) do |t_css, t_eos, t_async, debug|
  show_debug_info if debug
  xpath = convert_css_to_button_xpath(t_css)
  page.find(:xpath, xpath, :visible => true, :wait => t_async ? async_time : 0).click
  puts '' if t_eos.blank?
end

もし(/^".*?\((.*?)\)" リンクを押下(?:し(、)|した)(\(非同期\))?( debug)?$/) do |t_css, t_eos, t_async, debug|
  show_debug_info if debug
  xpath = convert_css_to_link_xpath(t_css)
  page.find(:xpath, xpath, :visible => true, :wait => t_async ? async_time : 0).click
  puts '' if t_eos.blank?
end

Gerkinによるfeature定義としては、例えば

もし "アカウント名(#account_name)" 欄に “foo" と入力し、
かつ "パスワード(#password)" 欄に “bar" と入力し、
かつ "ログイン(#login-button)" ボタンを押下した
ならば・・・(以下略)

のようになります。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年6月5日木曜日

Sinatra・PadrinoでセッションIDを変更する

Webアプリケーションでは例えばログインやログアウトしたタイミングなど、セッションハイジャックのリスクを下げるためにセッションIDを変更したほうがよい場面があります。

PHPですと、言語に組み込みでsession_regenerate_idという関数が用意されています。さすがWebに特化した言語ですね。

で、Rubyでは言語そのものにセッションIDを変更する方法はありません。各フレームワーク依存ですね。ということで、軽量フレームワークとしてよく使われるSinatraやその派生のPadrinoにおいてセッションIDを変更する方法を調べてみました。

ます、セッション内容を破棄して構わないのであれば、

session.destroy

と単にdestoryメソッドを呼んでセッションを破棄するだけです。自動的に新しいセッションIDでセッションが再生成されます。

セッション内容を引き継ぎたい場合は、事前にセッション内容を取得しておき、
新しいセッションの内容としてまるごと置き換えます。

old_session_hash = session.to_hash
session.destroy
session.replace(old_session_hash)

Padrinoの場合、以下の様な感じでヘルパーメソッドにしておくとよいでしょう。メソッド名はphpのマネです。ヘルパーを定義している部分に適当に追加すればいいと思います。

# セッションIDを再生成する
# @return [void]
def session_regenerate_id
  old_session_hash = session.to_hash
  session.destroy
  session.replace(old_session_hash)
end

上記 session_regenerate_id を呼べば、セッションIDが変更されてセッション内容が引き継がれます。

※2014/06/12追記
RackミドルウェアでCSRF対策をしている場合には、セッションIDの変更とともに、セッション内に保持しているCSRFトークンも変更したいと思うでしょう。そういう時は、引き継ぐセッション情報からCSRFトークンの情報を削っておくと、自動的にCSRFトークンを再生成してくれます。ということで修正したコードを載せておきます。

# セッションIDを再生成する
# @return [void]
def session_regenerate_id
  old_session_hash = session.to_hash
  old_session_hash.delete('csrf')
  session.destroy
  session.replace(old_session_hash)
end

上記の例はCSRFトークンをセッションに格納する際のキーが"csrf"である場合です。このキーは状況に応じて変更してください。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年6月4日水曜日

Capybaraでテスト実行中にセッションIDを取得する

テストにCapybaraを使ってるんですが、セッションIDが変化するかどうかをテストしたいと思いまして。ところが、セッションの中身を見る場合は rack_session_access っていうライブラリが使えるんですが、rack_session_accessではセッションID自体を取得する方法は提供してくれません。

Rack::Session::Cookieを使っている場合にはセッションの中身にセッションIDが入っているので、rack_session_access でセッションIDを取得できますが、Rack::Session::Cookie以外によるセッション機能を使っている場合はrack_session_accessではセッションIDが取得できません。

何とかできないものかと思案し、発想を転換しました。Rack::Session::Cookie以外によるセッション機能を使っている場合、ブラウザのCookieにはセッションIDそのものが入っているわけです。つまり、Cookie経由でセッションIDが取得できるはずです。

ということで調べると、Capybaraのドライバを呼び出して、ドライバ内部のCookieを取得するメソッドを呼び出せばいいみたいです。なお、ドライバ内部のCookieを取得するメソッドの呼び出し方法は、ドライバごとに異なります。次の例は、ドライバがPoltergeist の場合の例です。

@session_id =  page.driver.browser.cookies['rack.session'].value


などとすれば取得できます。

なお、セッションIDが変化するかどうかをテストしたい場合はセッションIDを保存しておく必要がありますが、単純にインスタンス変数に入れておけば、後のステップでそのままインスタンス変数として呼び出すことができます。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年6月3日火曜日

RSpecで外部コマンド実行結果を判定する

RSpecによる自動テスト中に、外部コマンドを実行して成功したかどうかを判定したいとします。そういう時は、実行ステータスを取得でき、なおかつ外部コマンドの終了を待ち合わせるような外部コマンドの実行方法を採用すればいいでしょう。

Rubyには外部コマンドの実行方法がいくつか存在しますが、Open3.capture2あたりが適しているように思います。コマンドの標準出力への出力結果はテストで使ったりするため取得したいことが多く、なおかつ標準エラー出力はそのままコンソール上に表示することでテスト中に外部コマンドでエラーが起きた際に原因が分かりやすくなります。

コマンドの実行が成功したかどうかを終了ステータスで判定すれば、エラーが起きた際に分かりやすくなるでしょう。コード例を挙げておきます。

stdout, status = Open3.capture2 %(some_command), :stdin_data => stdin_string
expect(status.exitstatus).to eq(0)

上記コードでは終了ステータスが0、つまりコマンドが成功することを期待しています。コマンドが異常終了して終了ステータスに0以外が返ると、テストは失敗となります。

なお、僕の場合実際には上記コードをRSpecから直接実行するのではなく、Cucumberのステップ定義に組み込んで使っています。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。