2014年10月22日水曜日

vimdiffで単語単位の差分表示: diffchar.vimが超便利

今日もプログラミングや文書作成にvimを巧みに操り続ける全国のvimmerの皆様におかれましては、vimを利用した差分表示であるvimdiffは欠かせないツールであることと思います。

当方も今までvimdiffについては色を見やすくしたり差分計算アルゴリズムを賢くしたりと、カスタマイズによって使い勝手を向上させてきましたが、唯一実現できていなかったのが、「単語単位の差分表示」なのであります。

しかしついに、vimdiffで単語単位の差分を表示できるようにするvimプラグイン「diffchar.vim」を先日見つけました。最初の公開が今年5月という新しいプラグインです。
diffchar.vim - Highlight the exact differences, based on characters and words : vim online

ということで、早速インストール。このプラグインはGithubにもミラーされており、すでにNeoBundleをお使いであれば導入はとっても簡単で次の一行を.vimrcに追加して ":NeoBundleInstall" するだけです。それ以外の場合については省略。

NeoBundle 'vim-scripts/diffchar.vim'

実際サンプルで試してみましょう。まず、vimdiffで差分を表示させると、次のようになりますね。


一つの行に複数箇所の変更が含まれる場合も、最初の変更から最後の変更までを含むような大きな1つの変更として認識されてしまっています。

しかしここで<F7>キーを押すと!


うおおおおぉ!すばらしい。単語単位の差分を表示できました。一行に複数の変更があってもちゃんと認識して表示。しかも、追加部分と変更部分で色分けもされています。

ちなみに、再度<F7>キーを押すと元に戻ります。なお、<F7>キーは文書全体について単語単位の差分表示を切り替えますが、<F8>キーを押すと現在の行のみ単語単位の差分表示を切り替えることができます。

これだけでも便利ですが、欲を言えば、vimdiffコマンド実行時やgit diftoolにvimdiffを使っている時に、いちいち<F7>を押すのは面倒。そんな時には、

" vimdiffで起動した際自動的に単語単位の差分(diffchar.vim)を有効にする
if &diff
  augroup enable_diffchar
    autocmd!
    autocmd VimEnter * execute "%SDChar"
  augroup END
endif

と.vimrcに書いておけばOK。vimdiffコマンドで起動した時には自動的に単語単位の差分が有効になります。(この場合、単語単位の差分をOFFにしたければ<F7>キーを押す、という使い方になります。)

これでvimdiffで差分をチェックする作業が一層はかどりますね。

※補足:autocmdのイベントはFilterWritePost、BufReadPost 、BufWinEnterなども試しましたが、FilterWritePostでは効果がなく、BufReadPostやBufWinEnterだとどうやらdiff表示途中に実行されてしまうらしく余計な箇所まで単語単位で差分表示しようとしてしまい表示が崩れます。VimEnterを指定したら期待した動作になりました。

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

2014年10月21日火曜日

OS X Yosemite で固定IP(手入力)時にVPNが繋がらない

MacをOS X Yosemiteにアップデートしたら、今まで正常に動作していたVPNが繋がらなくなりました。

ところが、iMacの方は繋がらなくなったものの、MacBookAirでは繋がります。VPN設定は同じ。なのになぜかiMacの方だけダメ。ファイアウォールを切ってみたり再起動してみたりしても繋がりません。

色々情報を探してたら、ネットでこんな情報を発見。
http://arstechnica.com/civis/viewtopic.php?f=19&t=1258743

要するに、YosemiteのL2TPクライアントにバグがあり、固定IPの場合接続できなくなる、というもの。上記リンク先ではWi-fiの時に再現を確認したとの情報ですが、Ethernet(ケーブル)接続においても同様かもしれないと思ったら・・・やはりそうでした。IPの設定が「手入力」になっていると、L2TPを利用したVPNに接続できません。MacBookAirだけ繋がっていたのは、IPアドレスがDHCP利用に設定されていたからです。

iMacで「手入力」にしていたのはIPアドレスを固定するためですが、これだとVPNに繋がらないし、どうしようか・・・と思っていたら、Macには

「DHCPサーバを使用(アドレスは手入力)」

という設定があるんですね。これに設定すると、IPアドレスのみて入力で固定することができ、デフォルトゲートウェイあネットマスクなどの他の設定はDHCPで取得することになります。この設定を行うことで、VPNに接続することができました。

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

2014年8月20日水曜日

プログラミングにおける「コメント」を軽視してないか

「他人が書いたプログラムの解読」は、ソフトウェアエンジニアにとってもっとも厄介な仕事の一つでありながら、必ず求められる作業です。

他人が書いたプログラムとの格闘戦は、ソフトウェアエンジニアの方なら経験がおありだと思います。もしかしたらそれが日常かもしれません。

逆にプログラムを白紙の状態から書いていく機会というのは、新規のソフトウェア製品や新規サービスの開発に初期メンバーとして参画するといったものがあります。しかしソフトウェアエンジニアの仕事としては、それよりも既存の製品や既存のサービスに機能を追加したり不具合を修正するなど、すでに存在するプログラムに手を加える機会も多いです。むしろこちらの方が多いかもしれません。だから、すでに存在する他人が書いたプログラムを解読する必要が出てくるわけですね。

そもそも、なぜ他人が書いたプログラムの解読が難しいのかといえば、プログラムに書かれる情報はあくまでプログラム上の処理の流れを記述してあるのであって、その処理の目的や背景、その処理を行う理由といった、いわば"プログラムの文脈"に関する情報は含まないからです。

だから、プログラムの処理自体が完全に理解できたとしても、それがどういう目的で何を達成するための処理なのか、全体の中で果たす役割は何なのかさっぱり分からない、という事態もありえます。

そこで大きな役割を果たすのが、ソースコードに挿入するコメントであると考えます。コメントが書いてあることによって、"プログラムの文脈"に関する情報を得ることができれば、プログラムへの理解は飛躍的に速くなります。

一方、プログラミングにおいて、コメントは極力少なくすべきであるという主張もあります。「関数やメソッドを十分小さい単位に分離し、適切に名前をつければ、その関数やメソッドが果たす役割は名前によって十分表現できる。構造化を適切に行えば、コメントを書く必要はない。コメントは最後の手段だ」という考え方ですね。

しかし、それでもコメントを書かない理由にはならないと思います。構造化と命名の工夫によってプログラムが理解しやすくなるのは確かです。しかし、それはプログラムの処理自体が分かりやすくなるだけであって、その処理の目的や背景、その処理を行う理由といった"プログラムの文脈"に関する情報が含まれないことには変わりありません。

つまり、ソースコードをコメントに書いておかなければ、プログラムを理解するのに極めて困難を要する場合が多々あるのだから、ソースコードには適切にコメントを書くべきであって、コメントを極力少なくする方針には賛同できない、ということです。

そして、どこにどのようなコメントを入れるか、というところに、ソフトウェアエンジニアとしてのセンスが現れると思っています。

実際、絶妙な場所に適切なコメントが入っているソースコードは、プログラムの解読が大変行いやすく、メンテナンスも容易で、大変ありがたいプログラムです。多少プログラム自体が上手く書けていなくても、そのソースコードをもとに作業する人間としてはコメントが適切に入っている方が速く理解できたりするのです。

エンジニアの美学としては、コメントが無くて、プログラムのみのソースコードを扱うほうがカッコいいのかもしれませんが。そこは格好よりも実利を尊重すべきだと思うんですよね。

というわけで、ソースコードに適切なコメントを書くことを決して軽視してはいけないし、コメントは最後の手段なんかではない、と言いたいのです。

「なるほど」と思ったら
↓このリンクをクリックを。



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

2014年8月8日金曜日

gitコミットとRedmineチケットは1対1にする

ソフトウェア開発のプロジェクトでgit(あるいはSubversion)を使っていない方が珍しいと思います。と同時に、極めてよく使われるツールがRedmineなどのプロジェクト管理ツールですね。

昔はバグトラッキングシステム(BTS)と呼ばれていましたが、バグの管理はもちろんのこと、プロジェクトのタスク管理につかうシステムとしても非常に優れており、広く使われるようになったことから最近では "プロジェクト管理ソフトウェア" などと呼ばれているようです。BTSに変わる決まった呼び方があるのかどうかはよく知りません。

ともあれ、git と redmine を連携させて使っているプロジェクトも多いことでしょう。Redmine 上のプロジェクトには git のリポジトリを関連付けることができ、gitのコミットメッセージにチケット番号を特定の書式で書くと、自動的にRedmine上のチケットとgitのコミットが関連付けされます。チケットの表示画面にgitコミットの概要とリンクが表示され、またコミットの表示画面ではチケットへのリンクが表示されます。これは後からソースコードの変更を確認したいときに、タスクの記録と一緒に追うことができて大変便利です。

ところで、1つのチケットに複数のコミットを関連付けることもできます。また逆に1つのコミットに複数のチケットを関連付けることもできます。ただし、僕の意見としては、極力チケットとコミットを1対1に対応させると、後から過去の変更を調べる際にすごく楽になります。(当然完全には無理でしょうから、出来る限りということです。)

例えば、1つのコミットに複数のチケットに関する変更を入れてしまうと、それらの変更がそれぞれどのチケットに関するものか判別するのは、変更した本人でない限り結構手間がかかるものです。あるいは、1つのチケットの解決に要した変更が複数のコミットにまたがっていると、これまたどんな変更がそのチケットに関連してなされたのかの結論を確認するのに骨が折れます。

だから、チケットとコミットを1対1に対応させるのがよいのです。もちろん、自分の手元でコミットを1回きりで済ませるのは大変だと思いますので、例えばローカル専用ブランチを作ってそこにコミットし、1つのチケットの分がまとまったら提出用ブランチにmerge --squash でまとめるといった方法をとればよいと思います。

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

2014年8月7日木曜日

リファクタリングは早めに行うべし

前回「既存のコードはなるべくいじらない」と書いてますが、それは他人が書いたコードとか、すでにテストを通過したコード、公開されて十分に使われているコードなどについてのことです。

自分で書いたコードで、リリース用のテストが始まる前の段階のものについては積極的に修正を行っていくべきでしょう。もしタイミングを逃してそのままリリースされてしまうと、今度は下手にいじれなくなってしまいますからね。

特にリファクタリングでコードの質を向上させておくことは後々のためになります。とはいえなかなか時間をとれないとは思いますが、例えば変数・関数・メソッドの名前の付け方だけでも見なおしておくことは役立ちます。

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

2014年8月6日水曜日

既存の他人のコードは書き直したくても我慢する

以前から続いているプロジェクトに自分が新しく入った時とか、オープンソースで公開されているソースコードに追加・修正を加える時か、他人の書いた既存のコードを見ていると、ついつい全て書き直したい衝動に駆られたりします。

しかし、既存のコードを書き直すのは得策ではないです。以前そのコードは製品リリースの段階でテストされており、機能が正常に動作することは確認済みだったりするからです。下手にいじることはむしろ余計なバグを産むことになりかねません。

だから可能な限りもとのコードを尊重するのです。いじらなくてすむ部分はいじらない。シンプルです。

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

2014年8月5日火曜日

既存のプログラムの修正時には「郷に従え」

他人が作ったプログラムに手を加える時とか、オープンソースで公開されているプログラムを利用する際に一部改変する時とか、既存のプログラムを修正する機会はIT系エンジニアには日常的な仕事の一部であると思います。

新規でソースコードをイチから書いていける場合と違い、既存のソースコードに追加・修正を加える際に意識すべきことは、「郷に入っては郷に従え」ということです。

同じ動作をするプログラムの書き方は無数にあります。単に望み通りの動作をさせたいだけだったら、既存のソースコードをほとんど無視して書くことも出来てしまいます。

でも、それをやってはいけません。既に書かれたコードにおいては、プログラム全体への影響が考慮されているものがほとんどであり、他の部分との整合性がとれています。だから、何かの機能を追加したり修正する場合には、既存のコードを出来る限り利用すべきです。これは無駄なバグの発生を防ぐ事にもなります。

他にも、変数の命名規則やインデントの体裁なども含めて、既存のコードに沿って追加や修正を行うべきです。そうすることでコードが読みやすくなり、バグも発見しやすくなります。

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

2014年8月2日土曜日

ソースコードのコメントは「省力化」のために入れる

プログラミングにおけるソースコードに書くコメントについて、他のエンジニアと話す機会が最近あって、その方は「コメントは最後の手段」と言っていました。

要するにその方によれば、例えば関数やメソッドの名前・引数の名前などでコード自体が機能を説明するようにすべきであり、コメントに説明を書くのは最後の手段であると。

確かに、コード自体が機能を説明していることは重要です。関数やメソッドの名前・引数の名前は読めば何を意図しているのか分かるようにすべきであり、そのためなら多少名前が長くなっても構わないと思います。一つの関数やメソッドが長くなり過ぎたり条件分岐や繰り返しといった制御構造のネストが深くなり過ぎないように、適切に分割していくべきです。

それは確かなのです。しかし、どうしても「ソースコード自体が機能を説明する」ことには限度があります。その目的や意図や、動作の詳細を説明しきれない場合は結構多いのです。

もう1つ、ソースコード自体を細かい関数やメソッドに分割した場合、もし意図がわからなければ、結局次々と呼び出し元の関数やメソッドを追っていかなければいけません。これも実際に結構な労力です。

本当に何をしているか理解するには、やはり最後はソースコードを追っかけるしか無くて、関数やメソッドの名前・引数の名前だけで何をしているか説明できるというのは無理があると思っています。それは自分自身が書いたソースコードならばともかく、他人の書いたソースコードになれば、もはや不可能です。

ソースコードを追っかける際に一番難しいのは、やはりどういう意図と目的を持って書かれているかということで、そこはコメントに書いてあるのとないのとでは理解のスピードに雲泥の差が生じます。

Web上にあるプログラミング言語のリファレンスマニュアルを思い出してみると、関数やメソッドの引数の想定する型や説明、及び返り値のとりうる型や説明、そして関数が行う処理の説明が意図と目的が分かるように書いてあります。だから、例えばRubyやPHPの関数やメソッドを、その元となるC言語のソースコードを全く知らなくても使えます。ちょっと強引な例ですが、十分に詳細な説明があれば、その先のソースコードは追っかけなくてもよくすることができる、ということです。

もちろん、プログラミング言語のリファレンスマニュアル並みの詳細度を全てのプログラミングのソースコードのコメントに求めていたら、ソースコードを書く側が余りにも大変になってしまいますが、コメントがきちんと書いてあることによって、ソースコードの理解は格段に速く楽になることは間違いありません。結局ソースコードを追っかけることになったにしても理解度とスピードが全然違うでしょう。

書いたら捨てる使い捨てのコードならばともかく、後々のメンテナンスを自分や他人が行うことになるコードなのであれば、コメントを上手く入れることが非常に重要であり、特にライブラリ的に多数の箇所から参照される関数やメソッドの先頭には、引数の想定する型や説明、及び返り値のとりうる型や説明、そして処理の意図と目的が分かるようにコメント入れるべきである、というのが僕の意見です。それは後々の「省力化」になるのです。

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

2014年8月1日金曜日

プログラミングにも「作曲」と「編曲」がある

プログラミングというのは、最初にロジックを考える部分と、実際のコードにしてからより良く動作するように改善を加えるリファクタリングの部分があります。これはいわば、音楽でいうところの「作曲」と「編曲」みたいなもんじゃないかと思います。

音楽が編曲によって化けることがあるように、プログラミングもロジックがイマイチでも、よくリファクタリングされ磨き上げたコードはバグもなく十分良い動作をしたりします。

音楽の場合は作曲者と編曲者が別々であることはよくありますが、プログラミングの場合はロジックを考える人とリファクタリングを行う人が分かれておらず、ほとんどの場合で同一人物が行います。なので、うまく頭を切り替えて、両方を意識しながら作業することが大切です。

ロジックを最初に思いついた方法で実装しただけで終わりにしまう人が多いですが、そこからのアレンジも大切なんですよね。

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

2014年7月31日木曜日

動的型付けを「複数の型を持てる」と考える

PHPやRubyなど、動的型付けをもつ言語を僕も普段から使っています。ただ、動的型付けに対しても、僕は明確に変数の型を意識します。

そもそも、動的型付けに対して、「型を意識しなくてよくなる」と考えるのは間違っていると思っています。そうではなく、「一つの変数が複数の型を持てる」と考えるべきかと。

例えばPHPだと、ある関数の返り値は場合によって整数かもしれないし文字列かもしれません。失敗時にはfalseだったりします。その関数の返り値を変数に代入すると、この変数は整数かもしれないし文字列かもしれないし真偽値であるかもしれません。しかしこの特徴によって、ソースコード記述の柔軟性を上げることが出来るのですね。

ただ、やはり変数を取り扱う上で、変数の型を意識しないと思わぬバグを産んだりします。だから、「型を意識しなくてよくなる」のではなく、「一つの変数が複数の型を持てる」と考えるべきなのです。

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

2014年7月30日水曜日

開発における "あと一歩" の追求

例えば、UIの配置やプログラミングのパターンを、あと1つだけ多く試してみるとか。手間としては、それほど多くなるわけではないでしょうが、これをやるかやらないかの違いは大きいと思います。

時間との兼ね合いもありますけれども、特にソフトウェアにおいてはちょっとした違いが積み重って全体の印象を決めています。むしろ地味な違いほど効たりします。

だから、ソフトウェア開発において、"あと一歩" の追求は時間に切羽詰まっていない限りは重視したいと思うのです。

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

2014年7月29日火曜日

UI設計では素朴な感覚を大切にする

ソフトウェア開発におけるUI設計は、経験や理論も影響してくるものではありますが、それより大切だと思っていることは「素朴な感覚」です。

まっさらな気持ちで、初めてそのソフトウェアを触った人のような気持ちになって操作し、何を感じるか。そこで「使いやすい」と感じられるか。何か足りないと思うことはあるか。多すぎると思うことはあるか。変えたほうが良いと思うことがあるか。などなど・・・。そこでかすかに感じるわずかな違和感に気づくことが何よりも重要です。

確かにプログラミングには経験や理論もファクターとして大いに絡んできます。しかし、そのプログラミングを通して作り上げ、最終的にユーザとの接点になるUIにおいては、何も知らない人間の感覚になる必要があります。

こういった感覚の切り替えもソフトウェア開発の面白いところですね。

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

2014年7月25日金曜日

git diffって多用するよね

git diffは非常によく使うコマンドの1つで、僕の仕事のクオリティを支えてくれているツールでもあります。また、difftoolにvimdiffを用いたgit difftoolも同じくらいよく使います。

僕はかなりこまめにgit diffを使います。プログラミングをしていると、ちょっと書いたらgit diffで差分を見て、動作確認し、またちょっと書く、みたいな感じです。差分を確認せずに一気に書くことはほとんどないです。

あとコミット前にはインデックスとHEADの差分をgit diffとgit difftoolの両方で確認します。ここで原則として差分については全て余すところなく確認します。

あと、ローカルブランチのコミットをgit merge --squashでまとめて提出用ブランチにした際も、提出用ブランチの直前との差分を確認しますが、この際はざっと目を通す感じですね。

こんな感じです。実に利用シーンは多いです。ちなみに差分アルゴリズムはhistogramがおすすめです。difftoolにvimdiffを用いる場合に、差分作成アルゴリズムを変更する方法については過去の記事を参照。

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

2014年7月24日木曜日

言われたとおりにやるエンジニアは二流

依頼者から言われた通りのものを作る。と言えば一見問題ないように感じますが、ただ言われたとおりにやるだけでは、それはソフトウェア開発エンジニアとして二流だと思ってます。

仕事の依頼というものは、それ自体が依頼者の本当に欲しい物を示しているのではありません。依頼者が、欲しい物を伝えようとして表現した結果が仕事の依頼なのです。そこには依頼者が表現する際にどうしてもズレや抜け漏れが生じていると考えなければいけません。

だから、依頼を受けたエンジニアとしては、依頼通りのものを作る事を目指すのではなく、依頼を通じて依頼者が本当に欲しがっているものを推理し、依頼をチューンナップしてあげる必要があります。

上記の話は、ソフトウェア開発においてウォーターフォール型開発だと要件定義の段階で実際によく言われる話です。しかし、ウォーターフォール型開発のように、ステップが明確に分かれていない開発が多くなっています。アジャイルや、それに類似した開発手法では、要件定義・設計・実装は明確に分かれておらず、担当者も同じだったりします。僕が関わっているプロジェクトもそうですね。

そうなると、より担当者の判断が求められるようになってきます。ウォーターフォール型開発だったら、要件定義の段階で全ての仕様について依頼者の同意を得てから次のステップへ進むという方法でしょうが、アジャイルや類似した開発手法の場合そうは行きません。そもそも仕様はどんどん変更されるのが前提ですから、担当者自身が判断して実装して実際に動く状態まで作って依頼者に見せてしまえばいいし、それしかないのです。

今後ますます、言われたとおりにやるエンジニアよりも、言われたことを上手に解釈して意図を汲み取り、実装まで行って動く形にして、依頼者を満足させられるエンジニアが評価されていくでしょう。

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

2014年7月23日水曜日

プログラミングは "新規" と "追加・修正" で全く異なる

最近思うんですが、プログラミングっていうのは、それが「新規」つまり「そのプロジェクトに関しての過去のコードが全く存在しない状態からコーディングする」のと、「追加・修正」つまり「そのプロジェクトに関して既にコードが存在し、それに対して追加あるいは修正を加える」のとでは、全く異なったものになりますね。もう少し詳細に言うならば、「追加・修正」の中でも、たとえプロジェクトに関しての過去のコードがあったとしても、実装しようとしている機能が他の機能から比較的独立しており、かつ、そこに関する部分のコードがまだ存在しない場合、「新規」に近いとみなすことができます。

さて、「新規」の場合、ソースコードの配置(どのファイルにどういった内容をコーディングするのか)、論理構成(どんなクラス・メソッドを作成してどのような役割を分担させるか、あるいはクラス・メソッドが既に用意されているフレームワークの選定)など、自分で骨組みを考えていきます。また、細かい話で言えば、クラス・メソッド・変数などの命名規則、空白や括弧のコーディングスタイルなども自分で決めていくわけです。

一方、「追加・修正」の場合は、上記で書いたようなことは(中途半端な状態で引き継いだのでない限り)元から全て存在しています。ですから、ソースコードの配置を勝手にできません。例えば画面描画のためのソースコードを集めて置いてあるフォルダに、いきなりDB接続用のソースコードを置くなんて暴挙は言語道断です。あるいは、すでにあるクラスと同じ役割のクラスを勝手に作ったら自分でさえ混乱しかねません。変数の命名規則を無視すると他のコードとの連携でミスを誘発し、バグの温床になります。要するに、既に存在するコードの規則に従うことが原則です。

従って、同じプログラミングとはいえ、「新規」と「追加・修正」では頭を切り替えなければいけません。

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

2014年7月19日土曜日

書籍「パーフェクトRuby」読了

書籍「パーフェクトRuby」(Rubyサポーターズ著、技術評論社)を読み終わりました。技術書として読み応えのある一冊であったと思います。

Rubyは基本的な文法をマスターすることはもちろんですが、次に組み込みクラスとモジュールの理解が重要です。組み込みクラスとモジュールをしっかり理解しているかどうかは、Rubyを使いこなす分かれ目にもなります。ここが網羅的に分かりやすく書いてあったのは大変勉強になりました。

加えて「パーフェクトRuby」を読むまではあまり知らなかったメタプログラミング系の知識が得られたのも大きかったと思います。Rubyにおけるクラスやオブジェクトの成り立ちや、動的なオブジェクトの操作についての理解は、現場でのOJT的な勉強だけだとなかなか身に付けづらいものがあり、こういった書籍での体系化された知識に触れることが理解に大いに役立ちます。

さらに、BundlerやYARDなどのよく使われる標準外のツールの説明や、実際のアプリケーション開発をコマンドラインアプリケーションとWebアプリケーションを具体例として解説してあるのもいいポイントでしたね。Webアプリケーションの方は、RackやSinatraといった業界標準のツールにも触れていて、僕も実際仕事で使っているものですが、知識を補完することが出来ました。

それにしても技術評論社のパーフェクトシリーズは良書が多いと思います。「パーフェクトPHP」「パーフェクトJavascript」に続き「パーフェクトRuby」もリファレンス本として手元においておく一冊にしようと思います。

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

2014年7月18日金曜日

SQLを久々に直接書いてWeb開発者として思ったこと

久々にSQLを直接書きました。とうのは、最近はORマッパーを使うことが多くて、直接SQLを書くことがめっきりなくなっていたからです。しかし今回は既存のコードに手を加える作業をするという状況で、既存のコードがORマッパーなど使用していないコードだったものですから、SQLを直接書くことになりました。

別にDBの専門家ではないので、パフォーマンスのチューニング方法などはあまり詳しくありません。もちろん速度は考慮して書きますが、それは基本的な部分であって、高度なチューニング(例えばDBMSの種類ごとの最適化やストレージエンジンごとの違いの考慮など)はできません。

とは言うものの、そこまで高度なチューニングが求められる場面は、Webアプリケーションにおいて少ないですし、今後も少なくなっていくように思います。基本的な原則(例えば、不要なレコードは取得しない=WHERE句でなるべく絞り込む、など)に則っていればよいと思います。以前はSQLの方で最適化しなければならなかったようなパターンも、DBMS自身の進化によって高速化している例もありますし。

Web開発者としては、そもそもSQLをチューニングしなければならないようなDB構成を避けるという方向性の方がよいような気もします(既存のシステムだとそうも行きませんが、そこはDBの専門家の出番でしょう)。

ということで、Web開発者の自分としては、SQLについては今後も基本的な原則は学んでいこうと思いますが、DBMSの種類ごとの最適化やストレージエンジンごとの違いの考慮といった事項は、DBの専門家にまかせておこうと思っています。

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

2014年7月17日木曜日

システム構築に必要な「統一性のセンス」

エンジニアとしてITシステムの構築を生業とするにあたっては様々な能力を必要とされますが、中でも重要な能力だと思っているのが「統一性のセンス」ですね。

これは、直接ユーザの目に触れるユーザインターフェース(UI)についても言えることですし、直接ユーザの目に触れないプログラミングやデータベース構造といったものについても当てはまることです。

例えばUIについては、ボタンの配置の方向や順番や色、アイコンの形、語彙表現など様々な要素について統一性を持たせることが、使いやすさに大きく影響します。

プログラミングについても、変数名・クラス名・メソッド名などの命名規則、論理的構造のパターンなどに統一性を持たせることで、バグの低減やメンテナンス性の向上を図ることができます。

システム構築には美的センスが求められる、と言う場合、その多くは「統一性のセンス」を意味しているのです。なぜなら、統一性のあるUIやソースコードは美しいものだからです。芸術作品では「混沌のセンス」も美的センスとしてアリなのでしょうが、システム構築にはありえないですからね。

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

2014年7月16日水曜日

他人のソースコードを読む際には推理能力も要る

昨日、プログラミングの際には考え方と目的も記録を残せと書いたんですが、自分はそうするよう気をつけていても、実際に仕事で扱う既存のソースコードにおいて、考え方と目的をコメントや公開サイトや付属ドキュメント等に書いていてくれているのかというと、まあ残念ながら書いてくれていない場合も多いわけですよ。

そういう時は仕方ありませんから、まるで推理小説の名探偵よろしく推理するしかありません。例えば変数やメソッドの名前も人によって微妙に用法が異なったりするので、ソースコード全体の傾向から、その名前が意味する所を推し量ったりとか。静的にソースコードを解析するだけでは分からない時は、デバッグツールを使って実際の動きを見ながら、コードの意味を分析したり、わざとエラーを起こさせてみたり。Javascirptの時なんかはよくやる手段です。

その意味では推理能力も必要ですね。つくづくプログラミングってのは思考を駆使すると思うんですよ。だから楽しいんですけどね。

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

2014年7月15日火曜日

プログラムを書いたら考え方と目的も記録を残すこと

プログラミングをする過程で、他人の書いたソースコードを読んでいて本当に強く思うんですけど、その実装方法に至る経緯や考え方や目的が分からないと非常に解読に苦労します。

単なる処理の動きだけならソースコードを読めば分かります。でも、例えばどういう時に使うクラスやメソッドなのかとか、以前別の方法で実装したけれど何らかのトラブルがあって直したとか、そういう情報がないときちんと理解出来ないんですよね。

だからソースコードに考え方と目的を、コメントとして残しておくか、あるいはRedmineなどのタスク管理システムを使っているならチケットに書いておくとか、何らかの方法で記録しておくべきだと思います。場合により使い分けるといいと思いますね。

これは将来そのソースコードを読む他人のためでもあり、そして後から修正をすることになった時の自分のためでもあります。忘れるものですからね。

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

2014年7月13日日曜日

gitのpush前にmerge --squashとpullとrebaseを

gitでどうブランチを切ってコミット・プッシュするかの運用方法については様々な意見というか流派(?)があるように思います。僕自身も複数人数のチームで開発を実際に行う中で、こうしたらいいんじゃないかって思っている方法を書いてみようと思います。

まず開発のタスク管理にはredmineを使っている、という背景があります。gitの共有リポジトリはredmineと連携させていて、共有リポジトリにプッシュしたコミットのメッセージにチケット番号を記載しておくと、自動的にredmineのチケットからリポジトリビューアにリンクされるようになっています。そのような背景から、チケットごとにコミットを1つにまとめています。

さらに、共有リポジトリへプッシュする際は、自分の名前が付けられたブランチにプッシュすることにしています。(masterへのマージはmasterブランチ管理者以外は行わない。)例えば僕はkasaiという名前のブランチを作成して、そこへ自分が担当するチケットを解決するコミットを行い、それを共有リポジトリへプッシュします。

masterブランチ管理者は、共有リポジトリへプッシュされた他の担当者のブランチをローカルリポジトリへプルしてmasterへのマージを行い、改めて共有リポジトリへプッシュします。(つまり、共有リポジトリのmasterへのプッシュを行えるのはmasterブランチ管理者だけです。)

この辺までは結構よくある運用方法でしょうね。ここまで全体的な流れの話を書きましたが、次に、各担当者が個人的に行う運用方法についてです。

僕の場合、ローカルリポジトリにはローカル用ブランチを作って作業しています。ローカル用ブランチは自分のためだけのブランチで、一度行った変更を後のコミットで元に戻したり、試行錯誤の汚い履歴が混在していて、そのまま共有リポジトリにプッシュするなんて考えられない代物です。ちなみに僕はローカル用ブランチには local/yy.mm.dd という名前を付けています(yy.mm.ddは作成時の日付、1日に複数作成した場合はyy.mm.dd-2などとする)。

とういことで、ローカル用ブランチで試行錯誤して、完成したら提出用ブランチ(前述した例ではkasai)にまとめて取り込むという方法を採っています。その際にはmerge --squashを使います。

なお、提出用ブランチにmerge --squashされた後のローカル用ブランチは消してもいいのですが、バックアップの意味もあるし、コードが動かなくなった際に履歴が残っていると原因究明にも役立ったり、元に戻したりできるので残しておくことにしています。

では、提出用ブランチをこのままpushすればいいかというと、ちょっと考えるべき問題があります。pushする前に、共有リポジトリのmasterに、自分が元にした時点から変更がいくつも加わっている場合、コミット内容がmasterと競合しないにもかかわらず、仕様変更によりコードが動かなくなってしまう可能性があります。例えば、他人の担当範囲で定義されていた関数やメソッドを呼び出してコードを書いたが、自分がローカルで作業している間に、その関数やメソッドの挙動を変えるコミットがmasterに既にマージされていた、といった場合です。(この問題に関してはGitユーザマニュアルの「マージコミットの分割が1本線の履歴の分割よりも困難となる理由」の解説が分かりやすいです。)

だから、push直前に、最新のmasterを改めてpullで取得し、その最新のmasterにこれからpushしようとする変更を統合した状態で動作することを確認しておきたいんですね。そのためにrebaseを行ます。つまりpush直前に、自分のブランチをpullで取得した最新のmaster上にrebaseするのです。

こうすることで、上記のようにmaster側に知らないうちに仕様変更が加わっていて、競合が発生しないにもかかわらずコードが動かなくなる、という事態に陥る危険性を(ゼロではないにしても)かなり減らすことができます。また、もしそのような事態になったとしても、rebaseしておくことで原因の究明はぐっとやりやすくなります(当然ながら、これはpush前のコミットだからrebaseしてもいいのであって、たとえ提出用ブランチでも、一旦pushした後のコミットは決してrebaseしてはいけません)。

まとめると、こういう流れになります。
  1. ローカル用ブランチを作成し開発を行う。コミットは適宜好きなように行う。
  2. 提出用ブランチにてmerge --squash でローカル用ブランチの変更をまとめて取り込む。まとめる単位はredmineのチケット単位とする。
  3. masterブランチにてpullで共有リポジトリの最新の状態を取り込む。
  4. 提出用ブランチにてrebaseで変更を最新のmaster上に移し替える。
  5. rebaseされた提出用ブランチにて共有リポジトリへpushする。
  6. (masterブランチ管理者のみ) 各担当者の提出用ブランチブランチをpullし、ローカルでmasterへのマージを行い、共有リポジトリへpushする。
これが今のところいいんじゃないかと思っている方法です。

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

2014年7月11日金曜日

オープンソース時代こそ「コメント力」を

オープンソースとしてソースコードが公開されているソフトウェアを仕事で使ってたりするんですが、その際にソースコードを読み込んで目的に合わせてコードを追加したり改造したりするんですよね。で、何が一番大変かって言うと、そのソースコードを書いた作者の"意図を読み取ること"です。

プログラムの動作自体はソースコードそのものを読めば分かります。ただ、この変数をここで加算して、この関数に引数として渡して・・・といったプログラミング言語の処理自体は分かっても、そのソースコードが"何を目的に書かれているのか"が分からなければ意味がありません。

結局、ある一部分の動作がどういう意味を持つのかを知るために、関連するソースコードがを読み漁り、相当広い範囲を調べてようやく分かったりします。ただ、そこまで時間がかかるのには理由がありまして、ソースコードに"コードの目的・意図を記述した"コメントがほとんど無い場合にそうなるのです。

前にも書きましたが、ソースコードを読めば分かるってのは嘘ですからね。プログラムの機械的動作は確かに分かりますが、コードの目的や意図まで読み取るのは簡単ではありません。もちろんソースコードの関連する部分を含め広い範囲を読めば見えてくるのですが、しかしそれもどうなんでしょうか。

やはり、オープンソース時代であるからこそ、ソースコードを読む人に対する配慮は大切だと思うし、だからこそ、コードの目的や意図を分かりやすく記述できる「コメント力」が大切だと思うんですよね。

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

2014年7月10日木曜日

プログラミング言語のバイリンガル以上を

現在僕が関わっているプロジェクトは1つはRuby、もう1つがPHPをメインの言語として採用しています。自分自身の経験としても、PHPとRubyは得意とする言語なので両方とも言語を使うこと自体に苦労はありません。その他にもJavascriptは両方のプロジェクトで多用していますし、補助的にPythonも少しだけ使っています。あとは汎用言語ではありませんがシェルスクリプトも駆使します。HTML・CSSの知識は言うまでもありません。

実際プロジェクトの開発を進めてて思うんですが、2つ以上の得意な汎用言語を持っておくと色々メリットが有るんですよね。別にRubyとPHPじゃなくてPythonでもC#でもJavaでもPerlでも何でもいいんですけどね。とにかく2つあれば、色々比較ができるわけで、両方の言語を実際に使って開発した体験・経験を比べることによって言語の特徴が体感覚として分かるわけですよ。

例えば片方の言語で慣れたやり方を、もう一方の言語で実装しようとした時にすんなり行かないことがあったら、そこで言語の強みと弱みが分かります。また、そこでもう一方の言語で実装するときの最適解を考えますから、これも力になります。

それに、複数の言語での実装を知っていると、より汎用的なロジックを考えられるようになります。特定の言語に依存したロジックではく、別の言語でも問題なく実装できるようなロジックを考える傾向がつきます。その方がバグも出にくく、品質の工場にも寄与すると考えられますね。

ともあれ、プログラミング言語はバイリンガルになっておくと色々得をします。もちろん2つに留まらず、3つ4つと大いに越したことはありませんが、まずは2つを目指すとよいでしょうね。

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

2014年7月9日水曜日

capybaraのpage.execute_scriptとjQueryの組み合わせの汎用性がすごい

Webアプリ開発のテストに便利な Capybara(+Cucumber) ですが、テストに使うdriver がJavascriptに対応していれば、テスト中にJavascriptの実行を含めてブラウザの動きをシミュレートできるという素晴らしい特長があります。Javascript対応のdriverとしては、Capybara-WebkitやPoltergeistなんかが有名です。

ボタンやリンクのクリックなどのユーザの動作で発火するイベントは、Capybaraでclickなどのメソッドで指定すれば、Javascript側のイベントの動作もそのままシミュレートしてくれるようですが、マウスオーバーなどのイベントはどうやったらテストできるのでしょうか。

実は、Capybaraには execute_script メソッドがありまして、これでJavascriptのコード片を直接記述して実行できます。この中ではページで読み込んだライブラリなどが有効です。なのでjQueryが使えます。よってjQueryを使ってイベントを発火させることにより、かなりの汎用性を持ちえます。

例えば、マウスオーバーのステップ定義はこんなふうに書けてしまいます。

もし(/^".*?\((.*?)\)" にマウスポインタを乗せる$/) do |t_css|
  page.execute_script("$('#{t_css}').trigger('mouseenter')")
end

上記ステップ定義に従い、featureファイルにはこんなふうに書けますね。

もし "メニュー1(#menu1)" にマウスポインタを乗せる

カッコ内のCSSセレクタで指定した要素にmouseenterイベントが発生し、マウスオーバーの動作テストを実行することができます。

という感じで、execute_script メソッド内でjQueryを使うと簡単な記述で様々なことが実現出来そうです。特にテスト実行上重要なイベントの発火が非常に簡潔に書けるのでいいですね。

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

2014年7月8日火曜日

vimdiffでより賢いアルゴリズム(patience,histogram)を使う

vimdiff使ってますか?差分を取る際には非常に便利ですよね。git difftoolに設定して使っている人も多いと思います。しかしgit diffは差分計算のアルゴリズムを選択できますが、vimdiffはデフォルトでは差分計算のアルゴリズムを選択できません。

git diffの差分アルゴリズムには標準のもの以外にpatienceやhistogramがあり、より人間に読みやすい差分を表示してくれる「賢い」アルゴリズムになっています。それぞれgit diffコマンドのオプション--patienceや--histogramを付けるか、または~/.gitconfig設定ファイルにアルゴリズムを指定することで利用できます。

具体的なアルゴリズムの詳細は僕も詳しくないですが、patienceアルゴリズムは「ファイル内でユニークかつ比較ファイル同士で一致する行をなるべく"変化していない行"と認識する」よう働きます。また、histogramアルゴリズムは「patienceアルゴリズムの基本的ルールを継承しつつ高速化を図ったアルゴリズム」で、多くのファイルでpatienceアルゴリズムと同一の結果が得られるようです。また、histogramアルゴリズムはEclipseに統合されたGitクライアントEGit(が使うJGit)の標準アルゴリズムです。僕はコマンドラインツールのgitにおいてもhistogramアルゴリズムを使ってます。

さて、vimdiffにおいてもhistogramアルゴリズムを使いたいところです。vimの設定ではvimdiffの差分計算に使用するコマンドをdiffexprという設定値で指定できます。そして、git diffコマンドも--no-indexオプションを指定することでdiffコマンドと同じようにスタンドアロンツールとして利用できます。じゃあ、diffexprにgit diff --no-indexを指定すればいいのか・・・というと実はそれだけでは失敗します。

理由は、vimがdiffexprに設定したコマンドから受け付ける差分形式が、diffコマンドのnormal形式かed形式でなければならないからです(vimのヘルプにはed形式とだけ書いてあるのですが、normal形式も受け付けます)。そして、git diffコマンドでは直接diffコマンドのnormal形式やed形式で出力することができずunified形式のみに対応しています。

そこで、unified形式をnormal形式に変換するrubyスクリプトを書いて使うことにしました。以下がその変換スクリプトです。利用にはrubyが必要です。

#!/usr/bin/env ruby
iwhite = ''
if (ARGV[0] == '-b')
  iwhite = ARGV.shift.dup << ' '
end

diffout = `git diff --no-index --no-color -U0 #{iwhite}#{ARGV[0]} #{ARGV[1]}`
diffout.sub!(/\A.*?@@/m, '@@')
diffout.gsub!(/^\+/, '> ')
diffout.gsub!(/^-/, '< ')
diffout.gsub!(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@.*/) do
  before_start = $1
  before_size = $2
  after_start = $3
  after_size = $4

  action = 'c'
  if (before_size == '0')
    action = 'a'
  elsif (after_size == '0')
    action = 'd'
  end

  before_end = ''
  if (before_size && before_size != '0')
    before_end = ",#{before_start.to_i + before_size.to_i - 1}"
  end

  after_end = ''
  if (after_size && after_size != '0')
    after_end = ",#{after_start.to_i + after_size.to_i - 1}"
  end

  "#{before_start}#{before_end}#{action}#{after_start}#{after_end}"
end
diffout.gsub!(/^(<.*)(\r|\n|\r\n)(>)/, '\1\2---\2\3')
print diffout

上記スクリプトを~/bin/git-diff-normal-formatとして保存し、~/binにPATHを通しておきます。そしたら、.vim設定ファイルに次の記述を追加します。

" diffのコマンド
set diffexpr=MyDiff()
function MyDiff()
  let opt = ""
  if &diffopt =~ "iwhite"
    let opt = opt . "-b "
  endif
  silent execute "!git-diff-normal-format " . opt . v:fname_in . " " . v:fname_new . " > " . v:fname_out
  redraw!
endfunction

最後に、git diffで使うアルゴリズムを指定します。下記記述ではgit difftoolで起動するコマンドにvimdiffを指定する設定も一緒に行っています。

[diff]
  tool = vimdiff
  algorithm = histogram

これでvimdiffやgit difftoolを実行した時にhistogramアルゴリズムが使えるようになります(patienceを使いたい場合は、上記設定のalgorithmの値をpatienceに変えてください)。

では具体的な効果を見てみましょう。サンプルとして用意した2つの比較対象のファイルをvimでウィンドウを分割して並べたものが下の画像です(クリックで拡大できます)。


まずは、今回の設定をする前、デフォルトのアルゴリズムでvimdiffによる差分をとった結果が以下です(クリックで拡大できます)。


そして、今回の設定をした後の、histogramアルゴリズムでvimdiffによる差分をとった結果が以下です(クリックで拡大できます)。


こちらのほうが、より差分内容も分かりやすく、かつ実際に手で行った変更の操作に近いものになっていますね。
※ちなみにこのvimdiffの色設定は過去に書いたこの記事を参照。

※2014/07/10更新:diffupdateを実行した際に画面の描画がおかしくなることがあったので、回避のためMydiff()にredraw!を追加

※参考記事
Making your Git Diff/Merge More Useful | The Interim Developer
Alfedenzo - Patience Diff, a brief summary
Bram Cohen's Journal - Patience Diff Advantages
HistogramDiff (JGit - Core 2.0.0.201206130900-r API)

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

2014年7月5日土曜日

ググっても解決しない問題にこそ力を注ぐ

今のソフトウェア開発ってフリーソフトとかオープンソースとか皆が共通に利用できる資源が整ってまして、仕事で使うソフトウェアも手に入ります。その結果優れたソフトウェアは皆が同じものを使うようになります。得られたノウハウもウェブページやブログや各種SNSで公開していくために、困ったことがあればググればほとんど解決できるんですよね。その意味でネットが無ければ仕事になりません。

だから知識は重要だし、他人が既に見つけた問題の解決策を素早く見つけることも必要ではあることは確かです。僕も散々そう言ってますし。でも、そこには当然ながら限界がありまして、仕事をしていれば必ず、そう"必ず"いくらググっても解決策を見つけられない問題にぶちあたるものなのですね。

その時こそが本当に力を注ぐべき時です。ググって解決できるものをサッサと片付けるのは、この"いくらググっても解決策を見つけられない問題"に時間を掛けるためだと言っても過言ではありません。

ちなみに僕は"いくらググっても解決策を見つけられない問題"にぶちあたるのを、いつも楽しみにしてます。まあ、ググってサクサク問題を片付けるのもシューティングゲームみたいで楽しくはありますが、やはり本気で頭を使う時が一番楽しかったりするので。

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

2014年7月4日金曜日

gitでマージの事前に衝突するかどうか確認する

gitを使っていて、特定のブランチとマージしたら衝突が起きないかどうか知りたい場合があります。例えば、プロジェクトチームで共有しているリポジトリにコミットをプッシュする前に、現在作業中のブランチがmasterブランチと衝突を起こさないか確認しておきたい、なんて場合です。

そんな時は、mergeコマンドの —no-commit オプションで master をマージしてコミットせずに止めてみればいいです。あとで簡単に元に戻せます。(本来確かめたい向きとは逆向きのマージですが、衝突の確認をするだけなら結果は同じですから問題ありません。)

[user@host]% git merge master --no-commit
Auto-merging /some/file/path
Automatic merge went well; stopped before committing as requested

同一ファイルに編集があったが、自動的にマージされた場合には上記のような表示になります。衝突を起きて自動的に解決できない場合はその旨がここで表示されるはずです。

確認は終わりましたが、そのままではワーキングツリーとインデックスが変更されたままですので、元に戻します。mergeコマンドの --abort オプションを使えばいいです。

[user@host]% git merge --abort

これで元通り。

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

2014年7月3日木曜日

評価されないが重要なメンテナンス性

プログラミングをしていて、つくづくメンテナンス性って重要だと感じます。要するに後から修正や変更をしやすい構造にするってことなんですが、問題は、このメンテナンス性を高くするという行為がすぐに成果に直結しないため評価しにくいってことでしょうね。

まあ、評価されるかどうかはおいといて、エンジニアとしてのこだわり、あるいはポリシーとしてメンテナンス性は上げておきたいわけですよ。そのプログラムが使われれば使われるほど、メンテナンス性は重要になっていくわけです。

それに、他のエンジニアが自分の書いたコードを見るたびに文句をいうことになるか、あるいは逆に賞賛してくれるかは、メンテナンス性の良し悪しにかかっています。その意味でも、自分の書くコードはメンテナンス性は高く保つよう常に努力してます。

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

2014年7月2日水曜日

コーディングには自分なりのポリシーを持つ

ソフトウェア開発をしておりますと、人の個性が出る作業の代表格はプログラムのコーディングでしょう。

性格も出る気がしますし。注意すべきなのは、面と向かって話している時に感じる印象と、プログラムをコーディングしている時に、そのソースコードに現れる印象は違うことがあるということです。

ともあれ、それぞれの個性というのはありますが、その中においても、やはり各自が自分なりのポリシーを持つべきだと思います。

僕だったら、
・コードの簡潔さより、長くても読んで分かりやすい方を優先する
・コメントは積極的に入れる

などですね。自分なりのポリシーを持ち、その運用を改善していくことにおって、コーディング自体のスキルも上がると思います。

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

2014年7月1日火曜日

ボツになるコードを書く勇気

仕様書通りに動くプログラミングをするのが
我々ソフトウェア開発者にとって仕事の基本ですが
ただ言われた通りのものを作るだけじゃ面白くないわけでしてね。
改善提案をしたいわけですよ。

ところが、人間ってのは実際自分の目で見て動作を実感しないことには
口で行っただけでは提案した改善の効果が伝わらなかったりする、
なんてことが多いです。

そういう場合に、たまに僕がやる手段は、
ボツになるかもしれないのを覚悟で改善提案のコードを
書いてしまうことです。そして目の前で実際に動かしてみせるのです。
もちろんボツになったこともありますが、意外に採用してくれる
ことが多いですね。

言われたことだけ、仕様書に書いてあることだけを創っていては
見えてこない物があると思います。

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

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のステップ定義に組み込んで使っています。

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

2014年5月30日金曜日

次々に出る新技術とどう向き合うか

この記事『「新技術の習得」は麻薬だ 』を読んだんですが、共感するところが多いですね。次々と有象無象の新しい技術が出てきて、まるで「よどみにうかぶうたかた」のようにボコボコと出現しては消えていくわけですが、追いかけまくっていればそれだけで気持よくなってしまうかもしれません。

実際、技術者同士で話をしていると、知識が膨大にあるというだけで表面上は優秀に見えたりするし、相手の知らないことを自分が知っているという状況の優越感は理解はできます。でもそれが実際の仕事につながるかと問われれば、かなり怪しいですね。

僕の考えとしては、「自分が実際に今使っている、あるいはこれから仕事で使う技術」について徹底的に掘り下げて追求し、その過程で関連する新技術があれば吸収していくべきだと思っています。

注意すべきは、新技術の吸収よりも、現在使っている技術の掘り下げのほうが先だということです。実は、実際に仕事で使っているにもかかわらず、深く知っている、あるいは深く知ろうとする人って意外にも少ないです。

もちろん「深く」ってのもやりだしたらキリがないし、無意味な領域まで言ってしまっては仕方ないです。例えばWebアプリケーションの開発が仕事なのに、CPUのアーキテクチャに詳しくなっても、ちょっとそれは深く掘りすぎて反対側に突き抜けてしまった感があります。

でも、例えば自分が使っているフレームワークのソースコードを読んでみるとか、自分が使っている言語の解説本に徹底的に目を通してみるとか、そういう類のことならば少なくとも新技術を追いまくるよりは余程確実に役に立つし、実力の向上につながると思うんですよね。

そうやって、今使っている技術を掘り下げる中で関連する新技術に遭遇したならば、そこで初めて新技術を吸収すればいいと思います。そうすれば、実際の仕事に直結する形で新技術を勉強することになり、より有意義な勉強になるはずです。

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

capybara-webkitからpoltergeist(phantomjs)に乗り換え

RubyによるWebアプリケーションの自動化テストに便利なCapybara。JavascriptによるDOM操作も、capybara-webkitドライバを経由してテストすることができます。当初はそれで良かったんですが、とある事情によりXサーバ関連ファイル群を入れないで動かしたい場合があったので、Xサーバ関連ファイル群を必要とする仮想フレームバッファ(xvfb)への依存を避けたくなりました。

Capybaraにはもう1つ、JavascriptによるDOM操作を含むテストを実行できる手段がありまして、それがPoltergeistドライバを経由してPhantomJSを動かす、というものです。PhantomJSは仮想ブラウザみたいなもので(乱暴な説明)、詳しい説明は他サイトに譲ります。CabybaraからPhantomJSを動かすためのドライバがPoltergeistです。

ということで、Capybara-webkitからPoltergeist(経由のPhantomJS)に乗り換えることにしました。

まず、PhantomJSをインストールします。僕の環境としてはDebian7ですが、公式サイトのビルド済みパッケージを取ってきたらそのまま動公式サイトにLinux用のビルド済みバイナリがあるので、入手してください。2014/05/29現在の最新版はver1.9.7であり、
"phantomjs-1.9.7-linux-x86_64.tar.bz2"というリンクからダウンロードできます。

解凍して出来たディレクトリの bin/phantomjs をPATHの通った適当な場所に移動します。

例)$HOME/bin に移動する場合

[user@host]% tar jxvf phantomjs-1.9.7-linux-x86_64.tar.bz2
[user@host]% cp phantomjs-1.9.7-linux-x86_64/bin/phantomjs ~/bin

なお、phantomjsの動作には fontconfig, freetype ライブラリが必要ですが、最近のLinuxデスクトップ環境ならば、ほとんどの場合にはすでに入っているでしょう。

ruby側の対応は巷のサイトに書いてあるとおりでOKです。まずGemfileへPolgergeistの記述を追加します。

gem 'poltergeist'

僕はCucumber経由でcapybaraを使ってますので、features/support/env.rb に必要ライブラリの require と default_driver, javascript_driverの変更を記述します。(Javascirpt関連テストの時だけ使いたい場合は、default_driver側の記述は不要です。)

require 'capybara/poltergeist'
Capybara.default_driver = :poltergeist
Capybara.javascript_driver = :poltergeist

なお、Cucumberのテスト自体は書き換えずに動作しました。まだそんなにテストの量がないせいか、速度の違いは感じられません。

PhantomJSは活発に開発されているようなので、今後も色々と改善が期待できますね。

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

2014年5月29日木曜日

Padrinoの起動時のロード順・フック実行について

今回のテーマは実は、Padrinoにおいてconfig/boot.rbにはbefore_loadフックとafter_loadフックの登録を行うコード記述があるけれど、config/boot.rbファイルのトップレベルに直接書くのと before_loadやafter_loadの中に書くのではどう違うのか、という疑問が切っ掛けでした。

Padrinoが生成するconfig/boot.rbは末尾で Padrino.load! を呼び出していますが、実はその中でbefore_loadとafter_loadを呼んでいます。Padino.load!の定義は、ソースコードを調べると次のようになってます。

def load!
  return false if loaded?
  began_at = Time.now
  @_called_from = first_caller
  set_encoding
  set_load_paths(*load_paths)
  Logger.setup!
  require_dependencies("#{root}/config/database.rb")
  Reloader.lock!
  before_load.each(&:call)
  require_dependencies(*dependency_paths)
  after_load.each(&:call)
  logger.devel "Loaded Padrino in #{Time.now - began_at} seconds"
  Thread.current[:padrino_loaded] = true
end

つまりbefore_loadとafter_loadの違いは、Padrinoの各種依存ファイルのrequireの前か後か、という違いです。

上記Padino.load!をよく見てみると、Loggerだけ扱いが別になっています。Logger関連の設定はbefore_loadやafter_loadの中に書いてもそれだけでは有効になりません。config/boot.rbのトップレベルかつPadrino.load! の前に記述するか、手動でLogger.setup!を呼ぶ必要があります。

で、ようやく本題ですが、上記のPadrino.load!を見ながら、実際フレームワーク全体がどう読み込まれるのか調べてみたくなりました。Loggerのログレベルをdevelに設定すると、起動時にロードしたファイルをログに出してくれるようになるので、ロードの順番がわかります。まとめるとPadrinoのロードの主要手順はだいたいこんな感じになっているようです。

  • Loggerの設定
  • config/database.rbのロード
  • before_loadフックの実行
  • lib/の中にあるファイルのロード
  • models/の中にあるファイルのロード
  • (shared/lib/の中にあるファイルのロード)※デフォルトでは存在しない
  • (shared/models/の中にあるファイルのロード)※デフォルトでは存在しない
  • config/apps.rbのロード
  • app/app.rbのロード
  • アプリケーション名::Appのインスタンス生成
  • after_loadフックの実行
  • app/controllers/の中にあるファイルのロード
  • app/helpers/の中にあるファイルのロード

なるほど。ちなみに意外だったのは、controllersとhelpersのロード順が予想と逆だったこと。でもどうせメソッド定義なので特にロード順に影響はないはずです。

ロードの順番に影響を受けるようなコードの書き方をすることは少ない(というかなるべく避けるべき)と思いますが、デバッグの役に立つこともありますので一応知っておくとよいと思われます。

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

2014年5月28日水曜日

Padrinoのhelperで特定インスタンスを使いまわす

Padrinoではコントローラやビューから呼び出すことのできるメソッドをhelperメソッドとして定義することができます。

helperメソッドはリクエスト中に何度も呼ばれたりするので、例えばサーバへの接続を行う場合、何度も接続インスタンスをnewせずに使いまわしたい場合がありますよね。

実はある特定のインスタンスを使いまわす方法は単純で、helperメソッド内でインスタンス変数に入れておけばいいです。例えば、LDAPサーバへの接続を取得するヘルパーメソッドを書くとしたら、

MyApplication::App.helpers do
  def ldap_conn
    @ldap_conn ||= LDAP::Conn.new('localhost')
  end
end

みたいな感じになりますね。見ての通り、@ldap_connが未定義の場合は新規接続を作成してそのインスタンスを@ldap_connに代入するとともに返し、すでに@ldap_connが定義されていれば@ldap_connの中身を返します。

ちなみにこのインスタンス変数@ldap_connはどのインスタンスに属するかというと、MyApplication::Appのインスタンスに属することになります。これは get や post メソッド内で定義したインスタンス変数と同じ扱いです。テンプレートからも参照できることからわかるように、これらのインスタンス変数は同じリクエスト中は直接呼んで使いまわすことが出来ます。

今回はLDAP接続を例にしましたが、DBサーバやキャッシュサーバなどの各種サーバへの接続など生成処理の重いインスタンス全般に使えると思います。

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

2014年5月27日火曜日

Redis中のRackセッションのデータを直接見る

RubyのフレームワークPadrinoで開発しているアプリケーションのセッション管理をRack::Session::Redisでやることにしたわけですけれども、検証作業中にRedisに保存したセッションのデータをRack::Session::Redisを介さずに直接見てみたくなりました。

というわけで、Redisに格納されているセッションのデータを直接見る方法です。
セッションIDが分かっていることが条件です。RackアプリケーションでのセッションID取得方法はこちらを参照。
irbからRedisクラスを直接叩いて見てみることにします(以下、見やすいように改行を加えてあります)。

[user@host]% bundle exec irb
irb(main):001:0> require 'redis'
=> true

irb(main):002:0> redis = Redis.new(:url => 'redis://127.0.0.1:6379/0')
=> #<Redis client v3.0.7 for redis://127.0.0.1:6379/0>

irb(main):003:0> Marshal.load(redis.get('rack:session:セッションID'))
=> セッションのデータ

という感じで直接見ることができます。

Rackアプリケーションのセッションデータは、デフォルト設定では rack:session という名前空間の下に収められています。この名前空間は変更可能ですがあまり変更することはないでしょう。

データはMarshal.#dumpでシリアライズされているようで、読みだす時はMarshal.#loadを使えばよいです。

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

2014年5月24日土曜日

やっぱりセッション管理はRack::Session::Redisで

昨日Rack::Session::Dalliを使うって言った舌の根も乾かぬうちに別のことを言い始めたよコイツ、とのツッコミを受けそうです。すいません。いや実は職場の人にRack::Session::Redisってのもあるよ、とアドバイスを頂きまして、調べてみたらRedisが結構良さそうだし面白そうなのでRedis使いたくなってしまったのですよ。

ちなみに、Rack::Session::Redisについて調べてみると、このクラスを提供しているライブラリが2つ見つかります。rack-session-redisredis-rack の2つですね。

redis-rackのほうが新しいです。ソースコードも読んでみましたが、redis-rackのほうがよく整理されている印象を受けました。redisにデータを保存するための抽象化レイヤが redis-store として分離されていて、redis-rackはその上に乗っている構造のようです。

ということで、redis-rackを導入します。以下、Padrinoフレームワークを使っている前提です。

Gemfileに以下の記述を加えます。

gem ‘redis-rack’

app/app.rbに以下の記述を加えます。

set :protection, false
set :protect_from_csrf, false
disable :sessions
use Rack::Session::Redis, :expire_after => 30 * 24 * 60 * 60
use Rack::Protection
use Rack::Protection::AuthenticityToken, :authenticity_param => '_csrf_token'

セッションに使うライブラリを変更するため disable :sessions とするのですが、:protection と :protect_from_csrf は enable:sessions を前提にしておりエラーになるため事前に falseに設定し、その後手動で該当するミドルウェアである Rack::Protection と Rack::Protection::AuthenticityToken を use します。

なお、Redisの接続先はRack::Session::Redisのデフォルトのオプションで

 :redis_server => 'redis://127.0.0.1:6379/0/rack:session'

と定義されてます。特に変更の必要はないので今回はサーバを指定していません。オプションはセッションの有効期限だけを指定しています(上の例だと30日を秒数で指定)。

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

2014年5月23日金曜日

セッション管理は Rack::Session::Dalli で

昨日書いたように、Rack::Session::Pool使用中にセッションが破棄される という問題が発生してまして、その打開策として最初 Rack::Session::Memcache への移行を検討してました。

Rack::Session::Memcache はバックエンドに memcache-client パッケージを使うのですが、実は memcache-client のメンテを担当している開発者によって memcache-client の後継である dalli というパッケージが出ていました。

ここで、memcache-client と dalli という2つのパッケージが登場したので、他にもあるかと一応調べたら memdached という名前のパッケージもありますね(直球な名前だな)。これはCで書かれたAPIをRubyから呼ぶもので、速さだけで言ったら最速みたいですけれども、Rackに組み込む簡単な方法はパッと見つかりませんでした。やるとしたらRack::Session::Memcache を書き換えるのが一番現実的でしょうけど、あまりやりたくありません。

一方、dalli には Rack::Session::Dalli クラスが付属していて、これを使うと簡単に導入できるみたいでした。これは魅力的。さすがにdalliはPure Ruby実装なので速度面で劣るものの、それでもパフォーマンスは結構良好なようですので大丈夫でしょう。今回はそこまで速度重視の目的でもないですし。

ということで、 Rack::Session::Dalli を導入します。Padrinoフレームワークを前提に説明しますので、適宜読み替えてください。

Gemfileに

gem ‘dalli’

と加えます。そして、config/boot.rb に

require 'rack/session/dalli’

を追記します(Bundler.require では自動的にロードされないためです)。

そして、app/app.rb に

use Rack::Session::Dalli, :expire_after => nil, :memcache_server => '127.0.0.1:11211’

って感じに設定すればOKです。サーバはデフォルトで 'localhost:11211’ になってますが、IPアドレスを打たないときちんと認識しなかったので明示的に '127.0.0.1:11211’ を設定しています。

参考1→ http://stackoverflow.com/questions/12786976/how-to-specify-memcache-server-to-racksessionmemcache/12787492#12787492
参考2 →http://www.banana-systems.com/2010/09/rails3_memcached/

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

2014年5月22日木曜日

Rack::Session::Pool使用中にセッションが破棄される

Rack::Session::Poolを使っていると、突然セッションIDが変わる、という問題にぶちあたりました。しかもセッションIDが変わってもセッションに保存した値は引き継がれず消えるので、どうやらセッションが破棄されているようです。

Cucumberでのテストでは引っかからないので今まで気付きませんでした。ブラウザで実際に手動で操作していて初めて気づいた問題です。普通に動作していると思ったらいきなりセッションが消えてログイン状態が無効になったりします。問題発生のタイミングや間隔もランダムで、原因は、正直よくわかりません。

ネット上を探してみると、Rack::Session::Poolを使用中に似たような症状を報告している人がいました。
http://d.hatena.ne.jp/libera-softvaro+dev/20110324/1300982695

上記URLのページではリクエストごとにCookieの値が変わると書いてあります。Rack::Session::Poolを使う場合Cookieの値はセッションIDなので、同じような症状に思えます。unicornだと発生しないと書いてありますが、僕はunicornを使っており、リクエストごとに変わるということはないものの、しかしランダムなタイミングで突然セッションIDが変わります(セッションが破棄されている)。

一応原因特定のため一時的に Rack::Session::Cookieに戻してみると、やはり症状は起きず、この問題は全く発生しなくなります。

う~ん、Rack::Session::Poolはダメなのか・・・。しかし、Rack::Session::Cookieだとセッションのデータも全部Cookieに保存するからセキュリティ的に避けたいところです。となると、やっぱ、Rack::Session::Memcacheしかない気がします。ということで、memcachedの導入を本格的に考えますかね。

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

2014年5月21日水曜日

Rubyのコメント形式とYARD

PHPの時はPhpDoc形式のコメントをIDEが読み取って連携してくれるという事情もあり、ソースコードにはPhpDoc形式のコメントをつけるようにしてました。このPhpDocでは様々なタグを付けることができて、例えば関数の引数は@paramタグで指定できたりします。

ではRubyの場合はどうかと思って、今さらながら調べてみました。Rubyの場合、ソースコード内に記述するコメントのパーサとしてはRDocが代表的みたいですね。

調べてみると、RDocのコメントには、特に関数の引数や引数の型を指定する形式とか、そういうのはないみたいです。ちょっとイメージが湧かないので、有名なライブラリのソースコードをあたってみました。

ActiveRecordの場合、クラスやモジュールの先頭にガッツリ長文で説明が例も交えて解説してあります。

Rackの場合、ドバっと書いてあるファイルと、全然書いてないファイルの差が激しいです。まあ、必要なとこだけ絞って書いてあるんでしょう。

正直、決まりとか慣例とかあまり無いように思いました。うーん、自由に書けばいいのかなぁ。

などと思っていたら、どうやら、PhpDocに似たYARDってのがあるらしいじゃないですか。
※詳細はリンク先参照。
参考1 http://d.hatena.ne.jp/kitamomonga/20110825/ruby_yardoc_tag
参考2 http://morizyun.github.io/blog/yard-rails-ruby-gem-document-html/

そうそう、こういうのを探してたわけですよ。これならPhpDocと同じような感覚で書けますね。YARD自体の解説は上記リンク先に素晴らしい解説があるのでここではしませんが、やっぱりソースコードにコメントを入れるものとしては引数と戻り値を指定するタグくらいはあって欲しいわけで、そういう意味ではYARDはバッチリですな。

というわけで、今後YARD形式でコメントを書いていこうと思います。

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

2014年5月20日火曜日

Padrinoで全コントローラ共通の処理を書く

SinatraベースのフレームワークPadrinoはMVC構造を用意してくれるので処理を分かりやすく書くことができます。コントローラは自動的に雛形が用意されるので、各コントローラに関する処理はその雛形を編集すればよく、編集すべきファイルが分かりやすいです。

では、例えばログインを必要とするシステムにおけるページ表示前の認証の確認処理など、全コントローラに共通の処理はどこにどのように書けばよいでしょうか。

Padrinoのプロジェクトルートから見て、app/app.rbに書けばよいと思います。この中のAppクラスの宣言文の中に、before〜endを使って書けば全コントローラの処理前に実行される処理が書けます。

例えば上で挙げたログイン判断の場合ですと、

before do
  if /^\/login/ =~ request.path_info
    # ログインコントローラの場合、ログイン済みならトップ画面へリダイレクト
    redirect url_for(:top, :index) if logged_in?
  else
    # ログインコントローラ以外の場合、未ログインならログイン画面へリダイレクト
    redirect url_for(:login, :index) unless logged_in?
  end
end

のような感じで書けますね。上記のようにURLによって処理を分けて書くこともできます。また、ヘルパーメソッドも使えます。

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

2014年5月17日土曜日

Cucumber(+Capybara)ステップ定義の実用的サンプル

現在のプロジェクトでは初めてBDDを採用して開発してます。具体的にはCucumberを使ってステップ定義を書いています。

Cucumberについて調べると、確かにサンプルとしてGherkinによるステップ定義の例は色々出てくるんですけれども、日本語によるステップ定義で実用的というかそのまま実務に使えるサンプルってネット上に出ているものが少ない気がします。実務的じゃないものは沢山見つかるんですけどね。

ということで、BDDはまだ初心者といえる私ですけれども、実務ですぐに使える日本語で書いたGherkinによるステップ定義のサンプルを晒してみたいと思います。なお、具体的なテストコードの記述にはCapybaraを利用しています。

ちょっと長いですが、実用的なサンプルということでご理解ください。ではどうぞ。

もし(/^".*?\((.*?)\)" 画面にアクセス(?:し(、)|した)( debug)?$/) do |t_path, t_eos, debug|
  show_debug_info if debug
  visit t_path
  puts '' if t_eos.blank?
end

もし(/^HTTPステータスコードが "(\d{3})" で(?:あり(、)|ある)( debug)?$/) do |t_code, t_eos, debug|
  show_debug_info if debug
  expect(page.status_code).to eq(t_code.to_i)
  puts '' if t_eos.blank?
end

もし(/^".*?\((.*?)\)" 欄に "(.*?)" と入力(?:し(、)|した)( debug)?$/) do |t_locator, t_with, t_eos, debug|
  show_debug_info if debug
  page.fill_in(t_locator, :with => t_with)
  puts '' if t_eos.blank?
end

もし(/^".*?\((.*?)\)" ボタンを押下(?:し(、)|した)( debug)?$/) do |t_locator, t_eos, debug|
  show_debug_info if debug
  page.click_button(t_locator)
  puts '' if t_eos.blank?
end

ならば(/^ページタイトルが "(.*?)" で(?:あり(、)|あること)( debug)?$/) do |t_title, t_eos, debug|
  show_debug_info if debug
  expect(page.title).to eq(t_title)
  puts '' if t_eos.blank?
end

ならば(/^".*?\((.*?)\)" が存在(?:し(、)|すること)(\(非同期\))?( debug)?$/) do |t_css, t_eos, t_async, debug|
  show_debug_info if debug
  expect(page).to have_css(t_css, :wait => t_async ? async_time : 0)
  puts '' if t_eos.blank?
end

ならば(/^".*?\((.*?)\)" が存在(?:せず(、)|しないこと)(\(非同期\))?( debug)?$/) do |t_css, t_eos, t_async, debug|
  show_debug_info if debug
  expect(page).to have_no_css(t_css, :wait => t_async ? async_time : 0)
  puts '' if t_eos.blank?
end

ならば(/^".*?\((.*?)\)" 画面に遷移(?:し(、)|すること)( debug)?$/) do |t_path, t_eos, debug|
  show_debug_info if debug
  expect(page.current_path).to eq(t_path)
  puts '' if t_eos.blank?
end

ならば(/^".*?\((.*?)\)" に "(.*?)" と表示(?:され(、)|されていること)(\(非同期\))?( debug)?$/) do |t_css, t_text, t_eos, t_async, debug|
  show_debug_info if debug
  target_node = page.find(t_css, :text => t_text, :visible => true, :wait => t_async ? async_time : 0)
  expect(target_node.text).to eq(t_text)
  puts '' if t_eos.blank?
end

ならば(/^".*?\((.*?)\)" に "(.*?)" と表示(?:されず(、)|されていないこと)(\(非同期\))?( debug)?$/) do |t_css, t_text, t_eos, t_async, debug|
  show_debug_info if debug
  target_node = page.find(t_css, :text => t_text, :visible => true, :wait => t_async ? async_time : 0)
  expect(target_node.text).not_to eq(t_text)
  puts '' if t_eos.blank?
end

def async_time
  Capybara.default_wait_time
end

def show_debug_info
  puts 'Respnse Headers: ' + page.response_headers.to_s
  puts 'Status Code: ' + page.status_code.to_s
  puts 'Current Url: ' + page.current_url
  puts "HTML:\n" + page.html
end

以上になります。いくつかポイントを解説します。

URLやボタンや入力欄などの条件はカッコ内に記述するようにしているものがあります。例えば画面へのアクセスは "画面名(URL)" という記述です。他にも "ボタン名(HTMLのID)" とか、 "探したい要素の呼び名(CSSセレクタ)" とかですね。これはテストの見た目のわかりやすさと同時に、ステップ定義を使いまわせることを実現するためです。

各ステップは末尾に " debug" (半角スペースの後ろにdebug) とつけると、そのステップ実行直前のレスポンスについて、レスポンスヘッダ、ステータスコード、URL、HTMLコードをデバッグ情報として出力します。テストをしながら失敗したステップの時の状態が知りたいことってあると思うので、こうしてステップ定義にデバッグ情報の出力オプションを付けておけば便利ですよね。

あと、末尾に "(非同期)" とつける事のできるステップもあります。これは、Capybaraの機能で、例えばレスポンス中に指定した要素が存在するか確認するコードの場合、見つからなくても非同期処理の可能性を考慮して一定時間待ってから再度探してくれる、という機能を切り替えるためのものです。Capybaraではデフォルトで毎回2秒待ってくれるのですが、非同期を想定していない場合、この待ち時間は無駄です。テスト駆動をする場合はテスト失敗を頻繁に起こすのでなおさら無駄な待ち時間は無くしたいです。ということで、末尾に "(非同期)" と付けない場合はCapybaraの待ち時間を0秒に設定することで、無駄な待ち時間が発生しないようにしています。

あと、細かい点になりますが、ステップ定義文の最後の語尾に読点(、)をつける書き方とそうでない書き方の2通りできるようにしてまして、後者(読点なし)の場合には、次の行に空行を出力するようにしています。これは、Cucumberの実行時に、ステップ定義のソースコード上での空白行を無視して出力してしまって見辛いので、強制的に空行を再現させる目的で付けています。言葉で説明するとわかりにくいと思いますが実行結果を見れば一発でわかると思います。こんな風になります。


空行が出力されるので、結果がぐっと見やすくなりますね。

もしかしたら今後開発を進めるうちにまた色々変わってきたらアップデート版を書くかもしれませんし、書かないかもしれませんが、参考になれば幸いです。

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

2014年5月16日金曜日

やっぱり、銀の弾丸などない

僕も一時期はアジャイルに傾倒して、アジャイルこそ開発現場の諸問題を解決する究極の方法だなんて考えてた時期もありましたが、結局状況によりけりだってことを分かってきました。

テスト駆動開発(TDD)にしても、TDD原理主義者に言わせれば、いかなる状況においてもテストを先に書くべきだ、って言うんでしょうけれど、それもどうなんですかね。例えば今まで経験のない言語やフレームワークやライブラリを使う場合に、どういう結果が返ってくるか予想すらつかない場合には、テストを先に書くよりもまず、実際に色々コードを書いてみて、その動作やコーディングの勘所がある程度つかめる状態になってからテストファーストに切り替えたほうが効率がいいと思いますし。

アジャイルにしろTDDにしろ、もちろん僕は非常に優れた方法論だと思うし、実際に自分自身でも仕事の中で使っています。ですが、原理主義的にギチギチにルールを縛って適用するんではなくて、ちょっとルールを破ることがあっても、現実にうまい具合にあてはめていけるんならそれでいいんじゃないですかね。

銀の弾丸などない、という言葉をあらためて噛みしめるこの頃であります。

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

2014年5月15日木曜日

ruby-ldapでエントリを再帰的に削除する

※途中、試行錯誤の過程でボツにしたコードも載せてるので、結論だけ見たい方は途中の過程をすっ飛ばして下の方をご覧ください。

ruby-ldapを使ってrubyからLDAPの操作をしている時に子エントリを持つエントリを削除しようとするとエラーが発生し、"Operation not allowed on non-leaf” と言われます。

ruby-ldapの場合は特にエントリを再帰的に削除してくれるような便利なメソッドなんか用意されていません。ですので、先に全ての子エントリを削除するコードを自力で実装しないといけません。

最初に思いついたのは、エラーの発生を捕捉し、子エントリがあって処理に失敗した場合には各子エントリに対して再帰処理をかける方法です。

ということで最初に書いたコードがこちら。

# 指定DNを子も含めて再帰的に削除する
# ldap_connはLDAP接続を返すメソッドとして別途定義
def ldap_delete_r(dn)
  begin
    ldap_conn.delete(dn)
  rescue LDAP::ResultError
    if ldap_conn.err == 66 # "Operation not allowed on non-leaf"
      ldap_conn.search(dn,  LDAP::LDAP_SCOPE_ONELEVEL, '(objectclass=*)', ['dn']) do |entry|
        ldap_delete_r(entry.get_dn)
      end
    else
      raise
    end
  end
end

これで一応望み通りに動作しますが、ちょっとスマートではありません。
rubyに限らず一般的に例外処理のためのbegin〜rescue〜end節のような構文は処理が重いので、できれば例外捕捉をしなくて済む方法が望ましいです。
さらに、削除試行して失敗してから子エントリを検索していますが、そもそも子エントリを先に検索すればそ無駄に失敗する削除処理をしなくても済むはずです。

よって、削除処理の前に子エントリを検索しに行き、ヒットした各子エントリに対して再帰処理をかければよさそうです。子エントリがヒットしなければそのままエントリを削除します。

ということで修正したコードがこちら。

# 指定DNを子も含めて再帰的に削除する
# ldap_connはLDAP接続を返すメソッドとして別途定義
def ldap_delete_r(dn)
  ldap_conn.search(dn, LDAP::LDAP_SCOPE_ONELEVEL, '(objectclass=*)', ['dn']) do |entry|
    ldap_delete_r(entry.get_dn)
  end
  ldap_conn.delete(dn)
end

この方がスマートですね。

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

2014年5月14日水曜日

RackアプリケーションでセッションIDを取得する

Webアプリの開発をしていると、現在のセッションIDを取得したくなることがあります。PHPですとsession_idというその名もズバリの関数が言語標準で用意されてたりしますが、Rubyだと言語標準にはないです。そもそもPHPと違ってセッションの仕組み自体言語標準ではなく、よく使われるのはRack::Session系のライブラリです。これは以前紹介したように幾つか種類があります。

しかしRack::Session系のライブラリで、現在のセッションID取得をどうやるかについてはドキュメントに分かりやすく書いておらず、探しにくかったです。(Rack::Session::Cookieの場合のみ、セッションのデータ自体にセッションIDが格納されるようですが、それ以外ではIDは入りません。)結局ソースコード読んで見つけました。結論から先に言いますと、

env['rack.session'].id

で取得できます。SinatraやPadrinoを使っている場合はもっと簡潔に

session.id

でOKです。(Sinatraにおけるsessionメソッドはrequest.sessionを呼びだします。Rack::Request#sessionは@env['rack.session']を返します。Sinatraにおけるenvメソッドはインスタンス変数@envへのアクセサですので、結局同じことになります。)

なお、今回(メインで)参照したソースコードはこれ↓。
https://github.com/rack/rack/blob/master/lib/rack/session/abstract/id.rb

上記URLにはRack::Session::Abstract::SessionHashとRack::Session::Abstract::IDの定義が書かれています。

ここにセッションIDを取得していそうなメソッドを発見。Rack::Session::Abstract::ID#current_session_id です。こいつを定義している部分で、

def current_session_id(env)
  env[ENV_SESSION_KEY].id
end

とやってますね。ちなみにRack::Session::Abstractモジュールの冒頭の方で

ENV_SESSION_KEY = 'rack.session’.freeze

と定義されてます。よって、env[‘rack.session’].idを呼べばセッションIDが取得できるとわかります。

なお、env[‘rack.session’]の中身は何かというと、Rack::Session::Abstract::ID#prepare_session で

env[ENV_SESSION_KEY] = session_class.new(self, env)

としてまして、ここでsession_classが何かといえば、Rack::Session::Abstract::ID#session_class の定義が

def session_class
  SessionHash
end

となってます。要するに、env[‘rack.session’]に入っているのはRack::Session::Abstract::SessionHashのインスタンスなのでした。ちなみに、アプリケーション中で p env[‘rack.session’] などとするとハッシュに見える結果が返ってきますが、これは inspect メソッドが定義されているからです。

def inspect
  if loaded?
    @data.inspect
  else
    "#<#{self.class}:0x#{self.object_id.to_s(16)} not yet loaded>"
  end
end

インスタンス変数@data はハッシュですので、 inspect した結果がハッシュになるのですね。(Kernel.#p は内部的にObject#inspect を呼びます。)

ついでに、Rack::Session::Abstract::SessionHash#id を見てみますと

def id
  return @id if @loaded or instance_variable_defined?(:@id)
  @id = @store.send(:extract_session_id, @env)
end

となってまして、セッションIDはRack::Session::Abstract::SessionHashのインスタンス変数@idに保持されていることがわかります。

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

※2014/05/22更新:Rack::Session::Cookieの場合のみ、セッションのデータ自体にセッションIDが格納される点について追記。
※2014/06/04更新:SinatraやPadrinoの場合のより簡潔な書き方を追記。

2014年5月13日火曜日

PadrinoでSinatra::Flashを使う(Padrino::Flashでなく)

Webアプリを開発してますと、エラーメッセージを画面に出力するときに、次のリクエストの時に出力させたい、なんてことがよくあります。例えば何かの処理をした後に別のURLにリダイレクトさせて、その画面でエラーメッセージを出力させる場合がそうですね。

セッション変数に値を保持すれば、次回以降のリクエストに値を引き継げますが、もしこれがエラーメッセージだとすると、表示したらもう使わないので消さなければいけません。いちいち自分で消すのは面倒です。そこで、次のリクエストまでに限り値を保持するしくみがフレームワークやプラグインで用意されていたりします。このような仕組みは "Flash" と呼ばれています。(綴りは同じですがAdobeのFlashとは別物です。)

Sinatraの場合、公式ページにてRack:Flashを使うように書いてあります。しかしPadrinoでも使おうとしたらエラーになって使えませんでした(Gemfileに追加するだけでエラーになります。詳しい原因は追求してません)。

ということで、Padrinoの設定ファイルに記述があるSinatra:Flashを使うことにします。Sinatra:Flashは、セッション変数 session[:flash] にデータを保持しますが、直接セッション変数をアクセスせずに、flash[:key]を通じてアクセスします。ちなみに動作を見ていくとなかなか面白いです。まずは以下のコードと実行結果をみてください。

flash[:error] = "hoge"
p flash[:error]                   #=> nil
p flash.now[:error]               #=> nil
p flash.next[:error]              #=> "hoge"

flash.now[:error] = "fuga"
p flash[:error]                   #=> "fuga"
p flash.now[:error]               #=> "fuga"
p flash.next[:error]              #=> "hoge"

flash.next[:error] = "foo"
p flash[:error]                   #=> "fuga"
p flash.now[:error]               #=> "fuga"
p flash.next[:error]              #=> "foo"

入れたはずの値が入っていないように見えますが、実は以下のような仕組みです。flashはSinatra::Flashオブジェクトのインスタンスであり、Sinatra::Flashの[]= メソッドは@nextインスタンス変数に値を代入するようになっていて、[] メソッドは@nowインスタンス変数の値を出力します。そしてFlashオブジェクトのインスタンス変数@next, @nowには同名のメソッドでアクセスできるようになっています。

つまり代入は@nextへ、出力は@nowからってわけですね。よって一見上記は不思議に見えるようですがこれで正しい動作なのです。

でもって、アプリケーションのafterフックで現在の@nowの内容を@nextでまるごと置き換えてしまいます。これで次回リクエスト時には@nextの内容にアクセスされることになり、@nextへ記録した内容は次回リクエストまで限定で有効、ということになります。

ちなみに、Padrino:Flashというライブラリもあります。こちらはセッション変数 session[:_flash] にデータを保持します。使い方はSinatra:Flashと同じで、実際、Gemfileの記述をPadrino:Flashに変えただけでエラーなく動作しました。

ではデータの保存先以外に何が違うかというと、値を保存する際に、その値がシンボルである場合、自動的にI18n.traslateを通してくれること。そして付属するヘルパーメソッドのHTML出力形式が違うこと。

でも、今回は値の保存形式は独自に決めたかったし(エラーの種類ごとに更に内部にハッシュを持たせたかった)、その出力のためのヘルパーメソッドも自分で書くことにしたので、正直どちらでも良かったんですが、結局余計なことをしないシンプルなSinatra:Flashを採用したってわけです。

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

2014年5月10日土曜日

Safariで"戻る・進む"時のキャッシュを無効にする

Webアプリの開発時に、ブラウザの戻る・進むボタンを押した時にキャッシュを使われるのは時として都合が悪いことがあります。そういう時は、HTTPレスポンスヘッダを設定するのが一つの手段です。

ちなみにPHPですと、このキャッシュを無効化する機能はキャッシュリミッタとして言語に標準で実装されていたりします。さすがWebアプリに特化した言語ですよね。
http://www.php.net/manual/ja/function.session-cache-limiter.php

上記マニュアルによれば、該当するヘッダは

Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0

ですね。そこで、現在Rubyで開発しているシステムで同様のレスポンスヘッダを設定してみたところ、上手くいくと思ったらこれがSafariだと効きません・・・。

色々レスポンスヘッダをいじってみますが、結局ダメでした。ちなみに調べてみるとヘッダでもHTMLのメタタグでもダメで、Javascriptに頼るしかないようです。
http://stackoverflow.com/questions/11979156/mobile-safari-back-button

上記URLによると、次のようなスクリプトを使えばよいようです。

window.onpageshow = function(event) {
  if (event.persisted) {
    window.location.reload()
  }
};

URL中の説明にもありますが、jQuery経由では動作しないので生のJavascriptで書きます。これで Safari でも"戻る・進む"時にキャッシュではなくサーバから読むようになります。

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

2014年5月9日金曜日

Capybara で一時的に非同期処理の待ち時間を調整する

Ruby で開発している Web アプリのテストを強力に支援してくれる Capybara。デフォルトで非同期処理のテストにも対応してまして、特定の要素を見つけ出したり、条件に一致する要素の存在確認をしたりするテストにおいて、失敗した場合に一定時間(デフォルトでは2秒間)待ち、再度DOMを読み直してテストを再実行する機能が組み込まれております。

これは便利な機能ですが、しかし非同期処理を想定しないテストの時も、毎回レッドのたびに2秒待たされるのも無駄です。この非同期処理の待ち時間を変更する方法の1つは、using_wait_time メソッドを使うことです。

using_wait_time 1 do
  expect(page).to have_css '#hoge'
end

上記コードは非同期処理の待ち時間を1秒に設定してブロック内のテストを実行します。

あるいは、Capybara 2.1以降なら find メソッドにおいて :wait オプションが使えます。Capybara 2.2以降なら、matcher系メソッド(have_cssなど)でも :wait オプションが使えます。

expect(page).to have_css('#hoge', :wait => 1)

上記コードは先程載せた using_wait_time の使用例と同じく、非同期処理の待ち時間を1秒に設定してテストを実行できます。

ここで、Cucmuberとの組み合わせの例を示しておこうと思います。僕が実際使っているステップ定義の一部を紹介しましょう。

ならば(/^".*?\((.*?)\)" に "(.*?)" と表示(?:され|されていること)(\(非同期\))?( debug)?$/) do |t_css, t_text, t_async, debug|
  show_debug_info if debug
  target_node = page.find(t_css, :text => t_text, :visible => true, :wait => t_async ? async_time : 0)
  expect(target_node.text).to eq(t_text)
end

def async_time
  Capybara.default_wait_time
end

def show_debug_info
  puts page.response_headers
  puts page.status_code
  puts page.current_url
  puts page.html
end

上記のステップ定義において、末尾に (非同期) という文字列をつけると非同期処理の待ち時間をデフォルト(2秒)にし、付けない場合は0秒すなわち待たない設定としています。例えば、非同期処理を待たない場合はこんなふうになります。

ならば "ログイン名(#login-name)" に "admin" と表示されていること

ここで、もし非同期通信を待ちたければ、

ならば "ログイン名(#login-name)" に "admin" と表示されていること(非同期)

と書けばいいです。

ちなみに、このステップの実行直前にデバッグ情報を表示させたければ

ならば "ログイン名(#login-name)" に "admin" と表示されていること debug

と書けばできるようにしています。

正規表現によるステップ定義は高い柔軟性をもたせられるのが利点ですね。

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

2014年5月8日木曜日

Padrino で i18n (他言語化)対応

Padrinoは i18n ライブラリに対応しており、簡単な設定で使いはじめることができます。まずは、config/boot.rbにて以下の記述を追加します。

Padrino.before_load do
  I18n.enforce_available_locales = true
  I18n.default_locale = :ja
end

ちなみに I18n.enforce_available_locales を true にすると、その言語が利用可能ではない(翻訳ファイルが見つからない)時に例外を投げてくれるようになるらしいです。この設定はPadrino::Helpersモジュールによりデフォルトでtrueになっているのでわざわざ設定は不要な気もしますが、念のため明示的にtrueを指定してます(下記ソース参照)。
https://github.com/padrino/padrino-framework/blob/master/padrino-helpers/lib/padrino-helpers.rb

なお、 I18n.default_locale = :ja を  I18n.locale = :ja と書いている解説ページもちらほら見かけましたが、cucumberによるテストを実行した際に翻訳が効きませんでした。cucumberによるテストはマルチスレッドでして、どうやら、I18n.locale はスレッドローカルな設定であることが原因で  I18n.default_locale しか設定が効かないっぽいです。そこんとこの詳しいメカニズムまでは調べてませんが。まあ、I18n.default_locale のほうを設定しておけばいいと思います。

翻訳ファイルは、app/locale/ja.yml に置けばいいです。形式としてはYAMLで、

ja:
  login: ログイン
  top: トップ

みたいな感じです。

翻訳のためのメソッドは I18n.translate ですが、padrino が短縮で呼び出せるペルパーを用意してくれてます(Padrino::Helpers::TranslationHelpers)。
そのおかげで、

<%=  I18n.translate(:login) %>

と書かなくても

<%= t(:login) %>

と書くか、あるいはRails風に

<%=t :login %>

と書けば「ログイン」と出力してくれます。

ちなみに、 I18n.localize というのもあって、こちらはDate, DateTime,Timeのいずれかのオブジェクトを受け付け、日付や時刻を言語に対応した形式で表示してくれます。こちらも同様短縮形が用意されているので、

<%=l Time.now %>

と書くと

2014/05/07 21:01:00

などと表示してくれます。その他にも翻訳時のパラメータ対応など機能がありますが、また別の機会に書きたいと思います。

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

2014年5月3日土曜日

テスト駆動開発の自動テスト"以外"の部分

それにしても、テスト駆動開発を実際にやっていると、テスト駆動開発っていうのは自動化テストありきの話では全くないということに気づきます。つまり、自動化テストが全くないとしても、テスト駆動開発的な進め方・考え方というのは元からあったわけで、それが自動化テストという仕組みにぴったりマッチした、と捉えることもできるのですよ。

例えば、テスト駆動開発の基本的な流れは、
  1. テスト記述
  2. テスト実行(レッド)
  3. コード記述
  4. テスト実行(グリーン)
  5. リファクタリング
  6. テスト実行(グリーン)
  7. (1. に戻る)
と繰り返していきますよね。

でもこれ、例えばWebアプリ開発で言えば、自動化テストの代わりに、ブラウザ上で実際に手動で動かして確認することに置き換えると、実は昔からやっていたやり方になるんですよね。要するに、
  1. 実現したい動作を決める
  2. ブラウザで現在の動作を確認する(失敗)
  3. コード記述
  4. ブラウザで現在の動作を確認する(成功)
  5. リファクタリング
  6. ブラウザで現在の動作を確認する(成功)
  7. (1. に戻る)
という感じですね。リズムとしては、ブラウザによる動作確認が自動化テストに置き換わればそのままテスト駆動開発になってしまいます。ですので、テスト駆動開発を始めた時に、全く違和感なく実に自然に始めることができたんですよね。
多分この自動テスト"以外"の部分が以前からの開発の流れとぴったり重なると、テスト駆動開発を容易に採り入れることができ、恩恵を享受できると思います。

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

2014年5月2日金曜日

PadrinoでRack::Session::Poolを使う方法

先日、Rack::Session::PoolとRack::Session::Cookieの違いについて書きましたけれども、Padrinoで実際にRack::Session::Poolを使う設定をしてみたので、メモしておきます。

Padrinoでは、デフォルトではapp/app.rbに

enable :sessions

と記述があり、セッション管理が有効になっています。PadrinoのベースであるSinatraは上記の記述がある場合にRack::Session::Cookieをオプション無しで呼び出します。そのためここではCookieの有効期限などの細かい設定はできません。

細かい設定をしたい場合は、enable :sessions の代わりに Rack::Session::Cookie を直接使うように、Sinatraの公式ドキュメントにも書いてあります。だったら、同様にRack::Session::Cookie ではなくRack::Session::Poolを直接呼び出せば大丈夫のような気がします。

ところがPadrinoだと、プロジェクトジェネレータで生成されたアプリケーションのapp/app.rbにおいて、

disable :sessions
use Rack::Session::Pool

などとやろうとすると下記のようなエラーになってしまいます。ちなみにこれはRack::Session::Cookie を使おうとしても同様のエラーになります。

you need to set up a session middleware *before* Rack::Protection::AuthenticityToken (RuntimeError)

これは、Padrinoのデフォルトで Rack::Protection 関係のオプションが有効になっているためです。config/apps.rbの次のような記述が該当します。

Padrino.configure_apps do
  # enable :sessions
  set :session_secret, 'sessionseacretkeystring12345567890abcdef'
  set :protection, :except => :path_traversal
  set :protect_from_csrf, true
end

なので、一旦 Rack::Protection 関係のオプションを無効にし、Rack::Session::Poolを直接呼び出した後にRack::Protectionを直接呼び出します(Rack::Protection 関係のオプションを改めて有効にするとエラーになってしまいます)。app/app.rbのenable :sessionsをコメントアウトし、次の記述を追加します。disable :sessionsは不要かもしれませんが念のため入れてます。

set :protection, false
set :protect_from_csrf, false
disable :sessions
use Rack::Session::Pool, :key => 'rack.session', :path => '/', :expire_after => nil
use Rack::Protection
use Rack::Protection::AuthenticityToken, :authenticity_param => '_csrf_token'

これでエラーが消えました。セッションも、Cookieを確認すると、今までBase64でエンコードされた非常に長い文字列(おそらく暗号化したキー・バリューの組み合わせがセッションIDとともに入っている)だったのが、セッションIDのみらしき文字列に変化しています。上記コードでは有効期限を設定していませんが、指定すればCookieに反映されることは確認できました。なお、 Rack::Protection 関係がきちんど動作しているかどうかは未検証ですのでご注意ください。Rack::Protection::AuthenticityTokenも動作してるのを確認しました。

※2014/05/02 Rack::Protection::AuthenticityTokenの動作を確認したことを追記、コードの use Rack::Protection::AuthenticityToken にトークンのパラメータ名を指定する引数を追加

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

2014年5月1日木曜日

さあ、IEを捨てよう

さて、IE(Internet Explorer)のVer6〜11という幅広いバージョンに影響のある脆弱性が見つかり、さらにXPはサポート終了につきマイクロソフトは対応する予定も当然ない、というニュースが流れてますね。

いい機会です。もういい加減にIEやめましょう。と言いますかまだIE使ってるなんて勘弁して下さい。ましてやIE6とか、もう化石かと突っ込みたくなります。

もちろん、脆弱性が修正されないまま放置されるPCが多く出現するという状況自体は憂慮すべきことなのかもしれませんが、これを切っ掛けに皆がIEに見切りをつけ、FirefoxやChromeなどの他のブラウザに乗り換えてくれるなら(特にXPユーザが)、Web系エンジニアにとっては大変喜ばしい状況になるという側面も出てくるる気がします。そうなってほしい。

それにしても、IE6の呪縛が本当の意味で終わるまでにこんなに時間がかかるとは・・・。僕の周囲ではかなり消えつつありますが、まだゴキブリ並みのしつこさで生き残っている場所はあるんでしょうねぇ。ほんと、早く消えて欲しいものです。

さあ、今こそIEを捨てる時。ChromeかFirefoxに乗り換えよう。

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

2014年4月30日水曜日

Rack::Session::PoolとRack::Session::Cookieの違い

RubyでRackアプリケーションを作成している時に、セッションの保存方法を調べていて、幾つかの方法があることがわかったんですがね。とりあえず、Rackには代表的なところで言うと、
  • Rack::Session::Cookie
  • Rack::Session::Memcache
  • Rack::Session::Pool
あたりがあるようです。Rack::Session::Cookie は Cookieを利用した単純なデータストア、Rack::Session::Memcache はクッキーの代わりにmemcachedを使う、Rack::Session::Pool は @pool にハッシュとしてデータを保存する、という説明が公式のドキュメントにも書いてあります。が、Rack::Session::Pool がよく分かりません。そもそもRack::Session::Cookieとの違いは何でしょう?

と思っていたら、どうやら同じ悩みを抱える方が海外にもいたようです。

http://stackoverflow.com/questions/13573968/using-racksessionpool-over-racksessioncookie

上記サイトと、僕が幾つかのサイトで調べた結果をまとめると、つまりこういうことですかね。
  • Rack::Session::Cookie
    → セッションID、及び全てのキー・バリューのペアをCookieに保存する。
  • Rack::Session::Pool
    → セッションIDのみクッキーに保存する。データはRack::Session::Poolのインスタンス変数@poolに、メモリ上で永続化の処理をせず(つまりそのまま)保存される。そのため高速で動作し、かつ保存できるオブジェクトに(永続化しないため)制約がない。ただし、アプリケーションを再起動した場合にはデータは全て失われる。
  • Rack::Session::Memcache
    → セッションIDのみクッキーに保存する。データはmemcachedにより保存するため、当然memcachedは導入済みである必要がある。おそらく、永続化の処理は入るため保存できるデータの制約はRack::Session::Cookieと一緒と思われる。高速で動作する。速度的にはRack::Session::Poolとほぼ同等(永続化の操作の分わずかに劣ると思うが)。アプリケーションを再起動してもデータは保持される。
速度性能を追求するかどうか、そしてアプリケーションの再起動時にデータを保存する必要があるかどうか、が使い分けのポイントですかね。

※2014/05/02 Rack::Session::Poolについて、データの保存先の記述を修正しました。(アプリケーションオブジェクトのインスタンス変数 → Rack::Session::Poolのインスタンス変数)

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

2014年4月29日火曜日

PHPで、一時的に出力を抑制する

とあるオープンソースのコードを眺めていたら、下記のような記述がいくつもの箇所に使われているのを見ました。

ob_start();
〜何らかの処理〜
if (ob_get_level()) ob_end_clean();

調べてみると、ob_start()は出力のバッファリングをオンにして、すぐさま出力がされなくなります。例えば、ob_start()以降の処理で何らかのエラーが起きて出力がなされたとしても、それが画面に表示されないということです。

ob_get_level()は、出力のバッファリングのネストレベルを取得します。ob_start()はネストすることが出来て、そのネストレベル分解除しないといけません。

で、ob_end_clean()は、バッファ内の出力を破棄して(実際に出力を行わず)バッファリングを解除する関数です。

これらをまとめると、つまり、ob_start() から ob_end_clean() のまでの間の処理による出力は行われないということです。こういうやり方もあるんだなぁ。

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

一億分の一の間違いすら、許されない〜ITエンジニアの生きる世界〜

Facebookに投稿したものですが、せっかくなので、こちらにも載せときます。

================

一億分の一の間違いすら、許されない―

プログラムは、書いたとおりに動く。そこには寸分の誤解もなく、その一言一句・一文字に至るまで、書かれたプログラムはそのままコンピュータに解釈される。一文字間違ったら、人間なら「ああ、ここミスしたのね」と判断してくれるが、コンピュータは一文字間違ったらそのまま実行しようとしてエラーになる。

例えば銀行で動くような巨大なシステムともなれば、想像もつかないが、少なくとも全体では数千万行、文字にして数億文字ものプログラムになるだろう。もしかするともっと巨大かもしれない。

しかし、その1文字でも間違いがあれば、システムは正常に動かない。そう、一億分の一の間違いすら、許されない―。プログラミングとは、実際そういうものなのだ。普段何も気にすること無くATMでお金を引き出せるのは、一億分の一の間違いもなく、システムが動いているおかげである。

これは全く大げさな話ではない。確か数年前に世間を騒がした銀行のシステム不具合も、結局直接的な原因となったのは、僅か数文字のプログラムの不備によるものであった。

とはいっても、やはりプログラムは人間の書いた通りに動いただけである。コンピュータが「プログラムはこう書いてあるが、実は別の意図に違いない」などと勝手に解釈することは原理的にありえない。

そして、プログラムの記述に使うプログラミング言語の決まり事もまた、全部人間が決めたものだ。やはりそこにも、解釈にずれが生じる余地はない。つまり、完全に余すところなく、全ての支配権は「こちら」にあるのだ。

ところが、すんなりとプログラムが動くことは、めったにない。何故ならば、完全であるコンピュータは、完全に人間の命令を反映するのだから、人間の不完全さもまた、忠実に完全に再現するからだ。

ITエンジニアは、確かに、物言わず忠実に応答を返すコンピュータ相手の仕事だ。それは、外部の人間から見れば、無味乾燥とした味気のない、まるで砂漠のような世界に見えるかもしれない。

しかし、先に述べたように、コンピュータとは、人間を映す鏡でもある。組み上げられたシステムをよく見れば、意外なほどに、人間の意志や感情が映っているのを見ることができる。

無機質なようでいて、実は人間臭い―。ITエンジニアの生きる世界とは、そういう世界なのだ。

2014年4月26日土曜日

一人プロジェクトでもGitは役に立つ

ちょいと先日ある人が、「一人でやってるとGitもあまりありがたみがない」なんてことを言ってましたけれども、そんなことは無いと思いますよ。

そういう僕も昔はSubversionしか知らなくて、Gitを知った時もメリットが理解できず、移行したのは単に「将来性がありそうだから」くらいの理由だったんですがね。使ううちに、「これは明らかにGitに優位性がある」と実感できるようになりました。さらに、「たとえ一人のプロジェクトだとしてもやはりGitは有効活用できる」と思ってます。

ポイントは、リモートリポジトリとローカルリポジトリの使い分けにあります。開発中は色々と試行錯誤をしながら進めていくものですが、それらをコミットしていくと、結構変更が入り乱れてきます。一段落したところで変更をまとめてスッキリさせたいと思うわけですよ。

そういう時に、ローカルリポジトリに試行錯誤のためのブランチを作ってそこでにコミットしていき、一段落したところで、それらの変更をまとめて改めて別のブランチにコミットします。具体的には、以前この記事に書いたような merge --squash を使う方法が有用です。リモートリポジトリにプッシュするのは変更をまとめたブランチのみとしておけば、リモートリポジトリのコミットログもスッキリし、分かりやすくなります。

そういえば、ブランチの概念もSubversionとGitでは全然違いますよね。Subversionだとブランチはあくまで派生であって、trunkを更新するのが基本。Gitの方はmasterとは別のブランチにコミットして、masterにマージして取り込む、というのがよくあるパターンです。ですので一人プロジェクトでも気軽にブランチを切っていきます。

というわけで、一人プロジェクトでもGitは相当役に立ちますよ、という話でした。

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

2014年4月25日金曜日

仕様書・自動化テスト・実装の三位一体開発

タイトルは最近考えてることでしてね。ちなみに最初に申し上げておくべきことは、以下に書くことは、仕様を決める権利・スケジュールを決める権利・開発方法を決める権利を自らが持っている開発チーム及び開発者を対象としている、ということです。ということは受託開発よりも、自社サービスや自社製品の開発あるいは社内システム開発という立場向け、ってことです。

言いたいことを一言で言うと、仕様書・自動化テスト・実装を行き来しながら少しずつ作っていく、ということです。例えば、仕様書に少し書き加え、該当する自動化テストを少し書き、さらに該当する実装を少し書く。それを小さいサイクルで繰り返していく。というふうに。

もちろん、例えば実装方法に依存して考えなければならない部分があったりして、先に仕様書が書けない場合なんかもありますけど、その時は実装を先に書いてもいいわけです。その代わり可能な限り早く仕様書に反映し、該当する自動化テストも書くようにします。つまり、どれか1つだけが大きく突出しないようにします。

これの一部だけを取り出すと、自動化テストと実装の部分だけ見れば「テスト駆動開発」になるでしょう。さらに自動化テストの範囲が広がって振る舞いや挙動の記述も含むようになれば「ビヘイビア駆動開発」となりますね。

もし仕様書だけを集中して先に完成させてしまうと、いわゆるウォーターフォール型になります。あるいは仕様書も一緒に作っていくという概念を抜かせばアジャイルになります(大雑把に言えば)。

なお、「◯◯駆動開発」と呼んでしまうと、その「◯◯」を原則として先に作らなければならないということになりますが、仕様書・自動化テスト・実装は常に順番を固定して作れるようなものではなく、むしろお互いを行き来しながらバランスを取りつつ進めていく、という表現がしっくりきます。その上で、理想としては「仕様書→自動化テスト→実装」の順番のサイクルを維持する、と思っておけばいいと思います。

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

2014年4月24日木曜日

たまには「手を動かさないこと」も必要

よく手を動かすことが重要だって言われますよね。特にエンジニアの世界ですと、コマンドを覚えるとか、プログラミング言語を習得するとか、新しいツールを使うとか、ドキュメントを書くとか、そういう文脈で使われる言葉が「手を動かせ」です。確かにその通りであって、手を動かすこと重要である、という意見を否定しようだとは全く思いません。

でもしかし、いついかなる時も手を止めないことが良いことだ、とは僕は思わないわけですよ。たまには、手を止めて静かに深く黙考することも、これまた非常に大事なことだろうと思うのですね。別に長くそうしろとも言いません。1分とか2分とか、場合によっては30秒でいいから、集中的に思考する時間が必要なときもあります。

例えばプログラミングの前の設計を行うことを考えてみます。全体の構想を考えているとして、最初は図に書き出していくわけですよ。思いつくことを色々と。僕は紙に書き出すのが好きなのでマインドマップ形式で描いたりします。

で、しばらくすると全部書き尽くすわけですね。「手が止まる」わけですよ。そこで次の段階に移行して再び手を動かす作業を再開する、のではちょっと甘いのですね。そこはむしろ、描いたものを眺め直して、頭の中でイメージを作り、足りないものが無いか、もっと掘り下げて細かく書いておくべき点はないか、などを検討することで、完成度が上がるのです。その時こそ、手を動かさずに静かに深く考えるべきタイミングですよね。時間にしたらわずかに1・2分かも知れませんが、この時間を取るか取らないかの差は大きいはずです。

ちなみに手を止めて黙考していると「ほら、手が止まってるよ」とか何とか言って文句をつけてくる人が居たりするかもしれません。手を動かしていないと仕事してないと思ってたみたいです。そういう人は分かってないので、無視しておきましょう。僕も過去の職場にいましたね。その人は自分を忙しくするのが得意でいつも悲劇の主人公気取りの人でした。相手にするだけ無意味です。

もちろん、あまり長い黙考は疲れるし意味が無いので、僕が言っているのはせいぜい数十秒から数分間程度のことです。でも、それが重要な時もある、ってことは理解しておかなければいけないと思います。

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

2014年4月23日水曜日

auでテザリングする時のIPアドレスとポート

自分のホームページは自前で仮想サーバをまるごと借りて運営してるんですけど、しばらく前から、テザリング使用時に、80番ポートのHTTPアクセスはできるものの、SSHなどその他のアクセスが出来ない、という状況が続いていました。その前まではできてたんですがね。

サーバ側のファイアウォールの設定を見なおしてみたり、HTTPでリクエストを投げた時にログに記録されるIPアドレスを確認したりしてみましたが、リクエスト元のIPアドレスには、確かにSSHの22番ポートなど、HTTPの80番ポート以外のアクセスも許可されていました。それでもアクセスが出来ない状況でした。

で、調べるうちに下記ページを発見。
http://www.au.kddi.com/developer/android/kaihatsu/network/

上記リンク先のページの最後の方に、下記の記述があります。

「Port80番通信とPort80番以外の通信では送信元IPアドレスが異なる場合があります」

うおーい、多分これだよ。てことで、早速一時的にファイアウォールを無効にして、テザリング中にHTTPで80番ポートへのアクセス元IPと、SSHで22番ポートへのアクセス元IPを確認してみると・・・異なってた!で、80番ポート以外からのアクセス元IPアドレスのファイアウォール通過許可が無かったのが原因なのでした。

なお、auのテザリング時のIPアドレス一覧は、上で挙げたリンク先に載ってます。とりあえず、リンク先に書いてあるIPの範囲でファイアウォールの設定を行い、無事つながるようになりました。

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

"@IT 自分戦略研究所 エンジニアライフ"でコラム開始

お知らせです。「@IT 自分戦略研究所 エンジニアライフ」でコラムを始めます。タイトルは『「変人」は褒め言葉なりエンジニア』。週1回のペースで投稿していきます。

なお、今までこのブログは毎日更新してきましたが、今後土日はコラム執筆のためお休みとさせて頂きます。なお、原稿を書いてから編集部のチェックが入るため、実際の掲載日は月曜か火曜(担当者が忙しいともっと遅れるかも)になると思います。

エンジニアの人はもちろん、それ以外の人でも気軽に読める内容になっていますので、ぜひお読み頂ければと思います。よろしくお願いします!

コラム第1回の記事はこちら↓
http://el.jibun.atmarkit.co.jp/takaakikasai/2014/04/post-905d.html

2014年4月22日火曜日

bash か sh か、それが問題だ

Linuxのサービス制御シェルスクリプトを書いていて思ったことなんですけどね。そのプロジェクトの先人たちが書いたコードを拝見すると、一行目のシェバング行に sh と bash が混じっていたんですよ。つまり、「#!/bin/sh」と「#!/bin/bash」の両方のパターンがあったわけです。まあDebian系なので、shは正確にはashなんですがね。

それで、どういうふうに使い分けるんだろうか、と気になったわけです。shとbashではbashが拡張した独自文法がある、ということは知ってましたが、じゃあ具体的にはなんなのかと。色々調べてみたり、bashのmanページを見たりしましたが、違いをまとめたものがなかなか見つかりません。

だったら、機能が多いbashに統一すればいいのか、というとそうでもありません。bashのmanページの最後、バグの項は次の一文で始まります。

「bash は大きすぎるし、遅すぎます。」

おいおい(笑)公式でそう言っちゃうんかい。しかもmanページのバグの項目で。まあともかく、重いってことですね。まあ、起動スクリプトは軽いに越したことはありませんから、むやみやたらにbashに統一というのはあまり良くはないでしょう。

でまあ、どうしたかというと、どうしても sh では実現できない、あるいはbashを使わないと著しく大変な時だけbashを使い、それ以外の時はshで済ますことにしました。

ちなみに今までに書いたスクリプトの中にもシェバング行が「#!/bin/bash」のやつもあったので、そいつらは「#!/bin/sh」に書き換えてしまって不具合が出ない限りはそれでいく方針に。

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

2014年4月21日月曜日

基本的には独学で

先日、とある人の講演を聞いていた時に、どうやって仕事のやり方を覚えたのかという質問が出たんですね。仕事の成果はかなり認めたらている人でしたけれども、全て独学だと言ってました。仕事上使うツールにしても、別に体系だったものを誰かから教えてもらったわけではなく、わからないことを都度調べたり試行錯誤したりして積み上げてきて、使いこなせるようになった、という話でした。

それを聞いていて、ITのスキル一緒だなと思ったんですよ。例えばプログラミングにしても、僕もいま仕事で使っているプログラミング言語に関しては完全に独学ですね。あと開発に使うツール、例えばEclipseみたいな結構複雑なツールにしても同様に、わからないことを都度調べたり試行錯誤したりして積み上げて、まあなんとか仕事で使えるようになった、という感じです。

エンジニアなんてのは特に独学ができることとか、独学で何かを身に付けられることが非常に重要で不可欠だと思います。例えば技術的な問題だって、仕事をしていれば必ず、どこの誰にも応えられないような、独自の問題にぶち当たる時が来ますからね。その時に、自力で解決できる力がどうしても必要です。

コードはコピペだけ、ググって見つからないなら掲示板で質問、そんなエンジニアなんて三流以下でしょう。

独学するというのは自力で解決できる力を付けるという事でもあります。遠慮無く独学で行きましょう。

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