Swiftでas!と書く場合のガイドライン

as!って言うのは、要するにダウンキャストできない場合にプログラムを終了させるものである。

if let x = expr as? SomeClass {
  f(x)
} else {
  fatalError()
}

と書くのであれば、

f(expr as! SomeClass)

と、同じことなので、as!で書いた方が良い。その方がタイプ数が減るし、コードの見た目も簡潔になって理解しやすくなる。長いコードはそれだけで苦痛だし、なにか間違ったプログラミングをしていることを示すシグナルにもなる。ダウンキャスト失敗の場合に終了するためだけにifを書くことは、全体的なコードの乱雑さを増してしまい、本当の問題の見落としに繋がる。

ただし、as?とかas!とかダウンキャストをしてる時点で、そのプログラムには潜在的な問題があると考えることもできて、本当にダウンキャストが必要なのか3回くらい考えた方が良い。

as!とObjective Cのキャストの違い

  • Objective Cのキャストは常に成功する
  • as!は失敗してエラーになることがある

この違いをまず頭に入れよう。次のObjective Cプログラムはエラーにならずに実行できる。

NSArray *x = (NSArray *)@"Hello World";

キャストは、コンパイルエラーにならないし実行時エラーにもならない。NSArrayにあってNSStringにないメソッドを呼び出すと実行時エラーになるが、キャストそのものは失敗しない。この性質は、3年に1回くらいは便利なこともあるが、大体は不便である。不正にキャストしたxが、ずっと先の全然ちがうところで実行時エラーを起こしたりするので、間違いを探すのに苦労することになる。

一方、次のSwiftプログラムはコンパイルはできるが実行するとエラーになる。

let x = "Hello World" as! Array<String>

Swiftは、不正なキャストに失敗してエラーを発生させる能力を獲得したのだ*1。これは、Objective Cに比べるとかなり改善されていて、不正なキャストからずっと先の全然違うところで実行時エラーになってデバッグに苦労することがなくなる。

この点で、Objective CのキャストとSwiftのキャストはかなり性質が違うものである。isKindOfClass:でテストせずにキャストするObjective Cのコードはかなり書かないほうが良いが、as!するSwiftのコードはかなりマシであると言える。

さらに、キャストに失敗したときに何が起きるかは決まっている。あなたのプログラムが終了するだけである。iOSに影響が及ぶことはないし(あったとしたらそれはOSの問題だ)、iPhoneが壊れることもないし、鼻から悪魔が出てくることもない。(ただし、アプリケーションやユーザーの性質によっては、それでも強制終了しない方が良い場合はあるとは思う。)

as! した方が良い場合

ダウンキャストに失敗した場合にそこから回復する手段がないとき、つまり直ちにプログラム終了するしかない場合はas!してしまう方が良い。「キャストに失敗したらプログラム終了」というのがas!の意味なので、わざわざ冗長にifを書く必要はどこにもない。

as?した方が良い場合

ダウンキャストに失敗しても処理が続けられる場合は、as!してはいけない。例えば、View Controllerに書かれているような、ユーザーとのインタラクションを担うようなコードの場合は、ダウンキャストの失敗というなにかプログラム実行の大前提が崩れるような事態にも、適切なエラー回復の手段を提供できる可能性がある。

どのくらい頑張ってエラー回復するかというのは、アプリケーションやユーザーの性質に関する問題なのでなんとも言えない。

コードレビューをしていてas!を見つけたら

本当にプログラムを終了する以外に回復の方法がないのか、議論して良いし、議論するべきである。適切な回復の方法があるのであれば、そちらに直す。また、as!as?もしないことについても検討する。標準ライブラリとかObjective Cとの相互運用を除けば、「enumにしてswitchする」「protocolを追加して分岐しなくて良いようにする」などの方法がある。

Lintとどうつきあうか

個人的には、as!に警告するルールはオフにするのがお勧めだけど(僕はLint大嫌いなので、多分これは少し極端な見解だろうとは思う)。まー現実的には、堂々と

// swiftlint:disable:next force_cast

と書くのが良いだろう。これを見たレビュアーは強制終了するほかにエラー回復の方法がないのかあなたに説明を求めるだろうし、あなたは説明できなくてはいけない。

*1:ちなみに、不正なキャストに失敗する能力は、JavaC++などのCやObjective Cよりも新しい言語は、だいたい1990年代に獲得しているものである。2016年にもなって失敗する能力が欠けているObjective Cが異端であるとも言える。

それでenumとclassのどっちにすれば良いの?

この記事には

enumが便利なのは

  • ある状態では有効だけど、別の状態では有効ではない値を定義するとき

です。

と書きましたが、「それはサブクラスっていうやつではないか」という声があります(どこに?)。その通りで、enumとクラスはよく似ている側面があって、enumでできることはクラスでも(再コンパイルの量とか、網羅性の検査とか、isKindOf:とか、キャストとかの問題を見なかったことにすれば)できるという話でした。

ここで疑問になるのは、

  • なぜSwiftにはenumとclassという似たような機能が二つ用意されているのか(TMTOWTDI?)
  • 何を考えてenumとclassを使い分ければ良いのか

でしょう。

enumとclassの違い

Swift的には「classは状態を持てるがenumは持てない」という違いがあります。これはSwiftプログラミング言語の設計として導入した恣意的な制限で*1、もう少し抽象的にはenum (ADT) とclassには次のような違いがあります。

  • enumは個々のcaseを追加するのは大変だけど、値の操作を追加するのは楽
  • classは個々のサブクラスを追加するのは楽だけど、操作を追加するのは面倒

enumにcaseを追加すると、全体的にあなたのコードは全体的にコンパイルし直しで、多分switchがエラーになりまくります。黙って実行時エラーにされるよりはマシですが、一つ一つ確認して、丁寧に手作業で修正してまわらないといけません。めんどくさい。一方で、クラスにサブクラスを追加した場合はなにが起きるかというと、全体のコンパイルは必要ないし、既にスーパークラスを使っている部分もソースコードを修正する必要がありません。楽。

逆に、enumに操作を追加するのは簡単で、switchを書けばそれで終わりです。プログラミングしてて、「あ、ここはそれぞれのcaseで別々の操作が必要だな!」とか多分考えもせずに、無意識にswitchを書くことになるでしょう。classに操作を追加するのは、スーパークラスメソッドを追加して、サブクラスで全部実装してまわると言うことです。まずメソッド名を考えないといけませんし、中身が一行だけだったりするとそもそもfuncとか書くのも面倒ですし、サブクラスを全部探すのも面倒ですし、ファイルを開いてまわる時点で面倒です。ああなんて面倒。

継承とオブジェクトの仕組みはある種のプログラミングを楽にしましたが、一方でad-hocに操作を追加できないことが問題になることもあります。isKindOf:やキャストのような仕組みでこの問題は回避できますが、一方で安全性が失われます。実行時の型検査によって危険性は押さえられていますが、もっと型システムを活用したい場面はたくさんあります。このようなad-hocに操作を追加したいようなデータ型には、enumのようなアブローチが向いています。

さて、enumとclassのどちらを使えば良いかはかなり明らかになりました。

  • caseは増えなさそうだけど、操作が増えそうな場合はenumを使う
  • 操作は現時点でだいたいわかっているけど、caseが増えそうな場合はclassを使う

簡単ですね。

ちなみに個々の型についてenumとclassのどちらを選ぶかですが、僕の場合は、ADTの方が先に頭にあるのでまずはenumで書けないか考えることになります。それで

  1. 状態を変更する方が楽に実装できそうな予感がある
  2. 継承して実装を使い回したい
  3. 関連する操作が(直感的に)すでにわかっている

など、enumで書くのは微妙な感じがする場合には、classにします。

Expression Problem

enumとclassの違いは、関数型プログラミング言語とオブジェクト指向プログラミング言語の違いとして考えることができます。この問題はExpression Problemという名前で昔々に議論されていたりします。

WikipediaからリンクされているWadlerのメール(論文*2)には、次のような文章があります。

In a functional language, the rows are fixed (cases in a datatype declaration) but it is easy to add new columns (functions). In an object-oriented language, the columns are fixed (methods in a class declaration) but it is easy to add new rows (subclasses).

http://homepages.inf.ed.ac.uk/wadler/papers/expression/expression.txt

ここで行 (row) と列 (column) は、それぞれデータの種類と操作を指しています。和訳。

関数型ブログラミング言語では、行は固定されている(datatype宣言のcase)が列を足すのは簡単(関数)。 オブジェクト指向プログラミング言語では、列は固定されているが(クラス宣言に含まれるメソッド)行を足すのは簡単(サブクラス)。

さて、「それでは両方とも足せるようにするのはどうしたら良いでしょうか?」というのが、Expression Problemです。回答はいくつかあって、

  • OCamlではPolymorphic Variantという「すごいenum」を用意して解決した
  • Scalaもなんかうまいこと解決した(上のWadlerの論文は「俺の考えたGJならExpression Problemが良い感じに解ける」というものなので、きっと関連があるんだろうと思っていますが、Scalaはよく知らないのでわかりません)
  • Haskellもなんかできるらしい(よく知りません)

とかです。

ちなみに「既存のクラス階層にメソッドを追加しようとするとちょう大変」という側面を見れば、SwiftのExtensionやObjective-CのCategory、C#のExtension Methodsといった機能によって、ある程度は解決される問題でもあります*3GoFで言えばVisitorパターンがこの問題に取り組むものですね(キャストが必要なので型安全ではない)。

*1:というと、少しニュアンスが違うかもしれなくて、enumがimmutableなのは常識的な設計なんだけど、でも別にmutableであっていけないわけでもない。

*2:URLにpaperって入っている。

*3:でも再帰的なものを考えるとあまり上手くいかない。

SwiftのenumがObjective Cで欲しいときはどうするか

Swiftではenumという種類のデータ型を定義できます。Cのenumと違って、引数を持てるようになっています。これが便利なのは

  • ある状態では有効だけど、別の状態では有効ではない値を定義するとき

です(すごい雑な説明なので、他の使い方も調べておきましょう)。

Swiftenumとは

例えばあなたが最強のレジアプリを開発しているとしましょう。販売する商品の価格には次の二通りがあります。

  • 単価と税率が決まっているもの(単価¥100、税8%)
  • 全体の金額からの割合であるようなもの(10%割引)

enumを使わないで普通のクラスにしようとすると、こんな感じになるはずです。

class MenuItem {
  let isPercentage: Bool
  let unitPrice: NSDecimalNumber!
  let vatPercentage: NSDecimalNumber!
  let percentage: Int!
}

isPercentageが偽のときには、unitPricevatPercentageが設定されて、percentagenilになります。isPercentageが真のときはその逆。optionalがたくさんあって嫌な感じがしますし、いつか普通に間違えそうです。うっかりisPercentageな商品のunitPriceにアクセスしてエラーになるかもしれませんし、それを見た新人がisPercentageなのにunitPriceに0を設定するみたいな間違いを犯すかもしれません。少しでも状況を改善するためにはvalidationのコードを書くこともできますが、退屈なのでできるだけやりたくないですね。

こういうことを防ぐにはenumを使います。

enum MenuItemPrice {
  case Unit(price: NSDecimalNumber, vatPercentage: NSDecimalNumber)
  case Percentage(percentage: Int)
}

class MenuItem {
  let price: MenuItemPrice
}

optionalがなくなってやばそうな感じはなくなり、割引の項目の価格にはpriceがないので間違えてアクセスしようとするとコンパイラが教えてくれます。安心。priceの中身にアクセスしようとすると、必ずswitchが必要になって少し面倒ですが、諦めましょう。

switch price {
  case Unit(let p):
    ...
  case Percentage(let p):
    ...
}

ちなみにswitchを使うと、caseが網羅されているかもコンパイラがチェックしてくれるので、後で価格の種類が増えたときにも安心です。

Objective Cでどうするか

残った問題は一つだけで、2010年から開発されているあなたの最強のレジアプリはObjective Cで書かれているので、こういう良い感じのenumSwiftで定義してもそれをアプリケーションから使うことは出来ないということです。関連するコードだけでもSwiftで書き直せれば良いのですが、いろいろな事情でそうもいかないことは多いでしょう。

仕方がないのでこうします。

@interface MenuItemPrice : NSObject
@end

@interface UnitPrice : MenuItemPrice

@property (nonatomic) NSDecimalNumber *price;
@property (nonatomic) NSDecimalNumber *vatPercentage;

@end

@interface PercentagePrice : MenuItemPrice

@property (nonatomic) NSInteger percentage;

@end

@interface MenuItem

@property (nonatomic) MenuItemPrice *price;

@end

これで「単価が決まっている価格なのにうっかりpercentageにアクセスしてしまう」ことは防げるようになりました。

こうやって定義した値を使う方法はいくつかあります。

  1. MenuItemPriceメソッドを追加してサブクラスでオーバーライドする
  2. isKindOfClassして場合分けする

必要なメソッドがわかっている場合は、1.の方法が良いでしょう。例えば「価格を文字列の表現に変換する」みたいな操作は、それでいいと思います。私の場合は、さっさと諦めて、isKindOfClassで場合分けすることが多いですね。

if ([menuItem.price isKindOfClass:[UnitPrice class]]) {
  UnitPrice *price = (UnitPrice *)menuItem.price;
  ...
}

isKindOfClassのような操作はオブジェクト指向プログラミングでは避けるべきとされていますが、ここでは別にOOしたいわけではないので良いことにします。キャストが少し面倒なので、ヘルパーを足すことがあります。

@interface MenuItemPrice

- (void)asUnitPrice:(void (^)(UnitPrice *))block;
- (void)asPercentagedPrice:(void (^)(PercentagedPrice *))block;
- (void)switchWithUnitPriceCase:(void (^)(UnitPrice *))unitCase percentagedPriceCase:(void (^)(PercentagedPrice *))percentagedCase;

@end

もしも価格の種類が増えた場合にコンパイルエラーになるようにするため、switchWithUnitPriceCase:percentagedPriceCase:も入れてみました。

まとめ

Swiftenumって要するに代数的データ型 (Algebraic data type / ADT) なので、Objective Cに欲しい場合は、どうしてもOOPLでADTしたい場合のパターンで逃げることができます。

MacでATOKを使っている場合にバックスラッシュを入力する

MacのEl Capitanでは仮名漢字変換が変わっていて、スペースを叩かなくても自動で変換してくれるようになっている。これはこれでなかなか便利なのだけど(今、何ヶ月かぶりにATOKに切り替えてみたら変換を忘れまくるので驚いた)、一つ不満があって、漢字にしすぎること。とは言っても、イマドキの仮名漢字変換はだいたい僕の好みに比べると漢字にしすぎるんだけど、自動でやってくれるというのが問題が大きかった。なにかを入力していて、後で「ときに」とか「ことが」とかを足そうとしたときなんかに酷くて、「時に」とか「事が」とかに自動で変換してしまうので、わざわざ平仮名に変換し直す手間が発生する。これがいやなのでとりあえずはATOKに戻すことにしたのだった。(ちなみに、この挙動は「自動変換を始めるのはある程度の長さが入力されてから」みたいな制約を入れると簡単に改善できる気がするけど、考えてないとは思えないので、今の挙動がトータルで最適と判断されているんだろう。)

さて、ATOKほインストールしてまずやったことは、バックスラッシュが入力されるようにすることである。Macではキーボード左上の「¥」と印字されているキーを押すと、¥が入力されるのだが、XcodeとかSublime Textとかでプログラミングしているとこの挙動は少し都合が悪い(ちなみにRubyMineとかIDEAとかTerminalだとデフォルトがバックスラッシュになる)。Optionと同時に押すことでバックスラッシュが入力できるが、プログラミングしているときに困るという話なので(頻度の問題)、いちいち同時押しとかしたくない。Mac標準の仮名漢字変換では、英字モードの時にはバックスラッシュが入力できるオプションが用意されているのだが、ATOKには見当たらないのだった。

適当に検索したところ、Key4Remapを使えみたいな話しか出てこないので諦めて、英字入力にはATOKを使わないことにした。と同時に、せっかくなのでこの方法を書いておくことにした。(Key4Remapを使っている人はそれでいいと思うけど、僕はわざわざインストールしたくないので別の方法を採った。)

こうする。

f:id:soutaro:20160218080328p:plain

システム環境設定でキーボードのページに進み、ATOKから「半角英字」のチェックを外す。

f:id:soutaro:20160218080454p:plain

僕は「ひらがな」以外を全部オフにしたけど、そこは好みで。これで、

  • 平仮名は「ATOK
  • 英字は「日本語」

という設定になり、「日本語」の設定で¥の入力を設定しておけばバックスラッシュが入力できるようになった。

問題としては

  • 変換中に「英数」を押すと変換中のものが確定されてしまう

という点があり、入力のスタイルによっては気に入らないかもしれない。その場合はKey4Remapなどでなんとかするのが良いだろう。

bundle execを殺す

「バグの希釈」の項目を見て思い出した。

2015年に書いたコードの中で、一番意味不明で気が利いてたなーと自分では思っているやつは「lib/bundler/setup.rbに空のファイルを置く」というものだったりする。会社で少し説明したんだけど、あまりうけなかったのでここに書いておこう。

ユビレジで販売している製品の中にユビレジエクステンションというのがあって、簡単に言うとRaspberry Piに電源とWi-FiアダプタとUSBハブを付けたもので、HTTPでJSONをPOSTするとレシートが印刷できる機械になっている。ふつーにDebianが動いていて、Rubyで書いたHTTPサーバとかLED点滅のためのdaemonとかレシート印刷のためのdaemonとかが動いている。問題は、特定の処理でHTTPサーバとかを再起動するときに、なんか上手く行かない子がいることが、出荷後に判明したことだった。調べてみると、再起動が上手くいかないというのは、タイムアウトしていることがわかった。

なんで上手くいく子と上手くいかない子がいるのかは良くわかっていない。SDカードを読み書きしながら動作しているので、なんかの拍子にSDカードがなんかダメになるんだと思う。SDカードをddでコピーして試すと平気で動いたりする。(これは本題ではない。)

さて、再起動と一言で言ってもなにに時間がかかっているのだろうか。主にprintfデバッグなどを繰り返したところ、Rubyプログラムの先頭にたどり着くまでに時間がかかっていることがわかった。どうしようもないじゃんそれ……とかいいつついろいろ試行錯誤していると、

$ bundle exec ruby -e "puts :hello"

と、

$ ruby -e "puts :hello"

で実行時間が大きく違うことがわかる。後者の方が速い。まあ当たり前感がある。Bundlerを起動してからrubyを起動するのと、rubyだけ起動するのでは、後者の方が速いのは道理だ。そして、出荷済のバージョンではうっかりbundle execしているのだった。

また、普通にGemfileとかGemfile.lockとかがある場所でbunde exec複数同時に実行するとめちゃくちゃ遅くなることもわかった。きっと、複数rubyが一斉にbundler経由でgemをロードすると、CPUを使いすぎるとかディスクアクセスが多すぎるとかで、なかなか終わらないんだろう。ちなみにbundle execしないで3つのrubyを実行すると、あんまり遅くない。(「めちゃくちゃ」というのは「3つ同時に起動すると10倍時間がかかるようになる」くらいの感じ。「あんまり」というのは、3〜4倍くらいの感じ。)

$ bundle exec ruby -e "puts :hello" & bundle exec ruby -e "puts :hello" & bundle exec ruby -e "puts :hello"

これで、「普通に起動したときには上手く起動するのに、(特定の場面で)サービスを再起動したときには上手くいかない」理由もわかった。普通に起動するときには、LED点滅daemonだけは先に起動してから(起動中にLEDを点滅させたいので)レシート印刷daemonとHTTPサーバを起動するが、問題となっている再起動のときには3つを一斉に起動している。2つ同時起動の場合はなんとかタイムアウトせずにすむが、3つだとダメなことがある、そんなラインにタイムアウトを設定してしまったのだろう。

どうやって直したら良いだろうか。いくつか方法がある。

  • サービス起動時のbundle execをやめる
  • サービスを3つ同時に再起動しないようにする
  • タイムアウトを長くする

どれでも良い。簡単に直せる。

……まだ出荷してなかったらね。そして既に出荷は開始されているのだった。

実は、こういうこともあろうかと、最近のバージョンにはiPadアプリ経由で更新ができる仕組みを仕込んである。あるんだけど、上に挙げたやつは、どれもソフトウェアアップデートでは更新ができないファイルに書かれたプログラムなのだった。うーむ。

どうなっているのか、もう少しちゃんと説明しよう。

  • アプリケーションの本体は1個のディレクトリにすべて格納されている。binとかlibとかvendorとかがあって、/var/ubiregi/1.3とかに保存されて、/var/ubiregi/currentみたいなリンクを張る。
  • /etc/init.dに置かれた起動スクリプトbundle exec ruby -I /var/ubiregi/current/lib /var/ubiregi/current/bin/a.rbなどとしてサービスを起動する
  • ソフトウェアアップデートは/usr/local/ubiregi以下に展開すると良い感じになるようなディレクトリをtgzにしたものの形で処理される
  • ソフトウェアアップデートはファイルの展開しかしないので、/etc/init.d以下のファイルを変更することはできない
  • ソフトウェアアップデートはソフトウェアアップデートdaemonによって処理され、このソフトウェアアップデートdaemonをソフトウェアアップデートすることはできない

そういうわけで「bundle execで実行されたアプリケーションが、rubyが実行されるまでの間に、bundle execをなかったことにする」そんな方法がないか考えることになった。(回答は、LOAD_PATHが通っている場所にbundler/setup.rbを置くことである。最初に書いた通り。)

bundle execがやっていることを見ると、次の二つである。

  1. bundle execで実行する実行ファイルをGemfile.lockを見ながら探す
  2. RUBYOPT環境変数-rbundler/setupを設定する

今回は1.は関係ない。RUBYOPTってなんだったっけ。

Rubyインタプリタにデフォルトで渡すオプションを指定します。

環境変数 (Ruby 2.1.0)

なるほど。そして-rrequireだ。つまり、bundle execすると

require 'bundler/setup'

と書かなくて良くなるわけだ。ってことはlib/bundler/setup.rbに空のファイルを置いておけば、Bundlerの処理をスキップできるのではないだろうか。問題は、このRUBYOPTコマンドライン引数の-Iのどっちが先に処理されるかである。(サービス起動のコマンドをもう一度。)

$ bundle exec ruby -I /var/ubiregi/current/lib /var/ubiregi/current/bin/a.rb

-Iの処理がRUBYOPTよりも後なら、-rbundler/setupは標準ライブラリを見に行ってしまうので、本物のbundler/setup.rbをロードしてしまう。-Iの処理が先なら、/var/ubiregi/current/lib/bundler/setup.rbをロードするので、アプリケーションで乗っ取ることができる。rubyのマニュアルにはなんとも書いてないので、実際に実行して試してみるしかない。

-Iが先だった。

というわけで、無事Bundlerを無効にする方法が見つかって、ソフトウェアアップデートによってこの問題は解決されたのだった。bundler/setupがなくなるので、Bundlerの手を借りずにgemをロードしないといけないけど、それはStandaloneモードで解決できる。

--standalone[=<list>]

bundle-install(1) - Install the dependencies specified in your Gemfile

(ちなみにStandaloneモードだと起動がかなり速いので、Gemfile.lockの処理がなんか変なことやってて遅い気がする。わからないけど。)

そういうわけで、空のファイルを追加する謎のコミットによって問題が解決されたのだった。(うっかり不必要にbundle execしてたのが本当の問題なんだけど。)あと、あんまりやらないとは思うけど、うっかりbundler/setup.rbみたいなファイルを作るとbundle execが動かなくなるかもしれないので、注意しましょう。

コーディング面接の例

プログラマの面接をするときには実際にコーディングをしてもらうべきという話は良く聞くが、もうちょっと細かくどういうお題を出したら良いかとか、どういう風に評価したら良いかとかの話はあんまり聞かない気がする。せっかくなので、ユビレジでの面接で私がコーディングについて確認するときのパターンを、いくつか紹介してみようと思う。

実際にコードを書いてもらうパターン

候補者がどのくらいプログラミングできそうかの予備情報がない場合に、簡単なアルゴリズムを書いてもらうことが多い。例としては、

  • Linked Listを書いてください
  • Stackを書いてください

など。ここで、おもむろに

int main(int argc, char* argv[]) {

などと書き始める人は、あまり良い印象をもたれない。

class Stack

などと書き始める人は上よりは期待できる。

このとき、わざと出題で詳細をあまり明らかにしないことも多い。言語や環境によって、事前にもっと細かい仕様を確認する必要があることもあるはずだけど、そういうのに気が回ることを示せるととても良いと思う。けど、あんまり重視はしていない。基本的には、プログラムを書くことができるかどうかに注目している。

RubyRailsが書けることが前提の候補者には、典型的な構造を持つRailsのモデルについて聞くこともある。「お会計に対応するCheckoutというモデルがあって、そこに顧客を保存したい」といったお題を出す。

ここで、

  • checkouts.customer_idを追加する

みたいな議論ができるかどうかを確認する。その後、「Rubyで書けます?」みたいな話をして、

class Checkout
  belongs_to :customer
end

などとすぐにそらで書ければ良い。これは基本的なレベルなので、3秒以上詰まる人は低評価。

さらに、「でも顧客が二人いることもあるんだけど」みたいな発展をする。

  • checkouts_customersみたいな関連テーブルを追加する

みたいな話ができれば良い。has_many :throughがすぐにそらで書けなくても許容範囲内という判断は、しても良いと思う。

その人が書いたコードについて質問するパターン

GitHubなどで意味があるコードが見られる人の場合は、コードレビュー的なことをやることがある。(「Railsの練習」みたいなリポジトリは相手にしない。)

例えば、

  • この正規表現はどういう意味?この{1,3}ってなに?
  • ここのsetTimeoutいる?

などと聞く。

ちゃんと自分の書いたコードの意味をわかっているかどうかを聞くのが目的。

  • 全然見当違いの回答をする人
  • なにも答えられない人

は評価が悪い。

  • ちゃんと説明できる人
  • 忘れていたとしても(古いコードだったら「今ならそうは書かない」みたいなことはよくあることだ)、他の正しい書き方を説明できる人
  • @soutaroの勘違いであることを説得できる人

は良い評価ができる。

ユビレジのデバッグをやってもらうパターン

面接の最初に製品と会社の説明をしているときなど、じっくりユビレジの挙動を見ていると、変な挙動に気付くことがある。(もしくは、原因が判明している不具合を含むバージョンを使ってデモをする。)

こういうときに「これ変ですよねー」みたいに聞いてみて、どういう返答が得られるかを調べることがある。あんまり変なバグだと難しすぎるので、典型的な間違いを犯しているコードであると推測できるような不具合について聞くのが良い。(実際の間違いが予測できないものであったとしてもかまわないが、挙動を見た瞬間に予想できるものではないと難しすぎる。)

私の例では、

  • Ajaxのリクエストが連続して発行された場合に、先に発行されたリクエストで結果が上書きされる
  • イベントハンドラを二重に登録してしまったために、イベントへのアクションが二重に実行される

などの不具合で議論したことがある。

これらのありがちな間違いの確認を提案できる人は、当該の環境での経験がある程度あると期待できる。(本当の原因が全然違うもので、推測が見当違いだったとしても、それ自体はマイナスにはならない。)

次に「どうやって確認したらいいですかねー」などと、開発環境やデバッグの手順について聞くこともある。デバッグプリントよりもデバッガの使用を提案できる方が、評価が高い。

こういう方法をやっているという話を他に聞いたことがないので、適切な方法でないかもしれない。

コーディングについて聞かないパターン

GitHubでスターを集めまくってる候補者の場合や、よく知っている開発者からの強い推薦がある候補者の場合など、候補者が十分にコードを書く能力があることに確信が持てる場合は、コーディングをスキップすることがある。

この場合は、ユビレジで抱えている技術的な問題について議論したり、適当なライブラリについて質問したりして、一緒に上手く仕事ができそうかどうか、気分よく仕事をしてもらえそうかどうかについて考えることになる。仕事で発生するコミュニケーションのほとんどは、

  • Backboneが辛いんだけど、どうしたら良いか
  • Railsparamsが壊れている気がするんだけど、どうなってんのこれ?
  • MySQLが遅いのをなんとかする方法はあるか

みたいな感じで議論することなので、ある程度抽象的な議論をして上手くやれそうか見るのには意味があると思う。

基本的には議論になるかどうか、好みが合うかどうかを見れば良い。議論にならないパターンはいくつかあるけど、抽象化のレベルが合わないものを見落とさないように注意した方が良い。一般的すぎる議論とか具体的すぎる議論になる場合は、その候補者とあなたの興味の範囲が違いすぎている。好みが合わない場合はしょうがない。

なにがわかるのか

結局のところ、いつもやっている仕事を上手く一緒にできそうかどうか予想している。面接では、

  1. 十分なスピードでコードを書くことができるか
  2. コードレビューについてはどうか
  3. 典型的な誤りを正しい手順で調査できるか
  4. 開発の過程で実際に発生する議論が上手くかみ合うかどうか

などについて予想する材料を集めている。

ちなみに、1.については実は明らかにテストが不十分で、いつも私が使っている問題は簡単すぎてその人の腕力・体力がどのくらいあるかは上手く予想するのが難しい。

  • もうちょっと難しい問題でコーディングしてもらう
  • もうちょっと複雑な構造を持つプログラムを、あらかじめ書いてきてもらう

といった方法が考えられると思う。

参考文献

こういう本もある。例題を探すために買った。(英語版を買ったけど、和訳も結構前に出てたことに気付いた……)

世界で闘うプログラミング力を鍛える150問 ~トップIT企業のプログラマになるための本~

世界で闘うプログラミング力を鍛える150問 ~トップIT企業のプログラマになるための本~


この記事は最初Qiita Teamに公開してたけど、社内から好意的な反応をもらえたような気がしたので、@k_katsumiの提案もあって公開することにした。

UX DAY TOKYO 2015

2015.uxdaystokyo.com

行ってきた。直接に関係がある話はあんまりしません。

エクストリームプログラミングが発表されたのは1999年だったという(Wikipediaより)。

www.amazon.co.jp www.amazon.co.jp

(日本語版は2000年。英語の1st Editionがあると良かったのだけど、Amazonにあったのは2004年の2nd Editionだった。)

Embrace Change(変化を抱擁せよ)

ソフトウェア開発の失敗は、事前に仕様と設計を凍結することから発生する。仕様の決定をできるだけ遅らせることによって、開発する製品の陳腐化を防ぐ。そのためには何をすれば良い?仕様を決定できる人間が実装を行うチームの近くにいなくてはいけない。オンサイトカスタマー。そもそも完成されたソフトウェアでないと発見できない類の問題がある。反復的な開発。もしも新しい変更が失敗だったときのために、ソースコードはいつでも過去のものを参照できるようにする。バージョン管理。全てのXPのプラクティスはソフトウェアの変更をリリースされる最後の瞬間まで続けられるようにするためのものである。(どれもちょう雑な説明なので、ちゃんとした資料を確認してください。)

このアイディアは広く受け入れられ、アジャイルスクラムと呼ばれる開発手法として発展を続けた(雑な説明)。アジャイルスクラムは、XPよりもコラボレーションやプロセスに意識が移っているように見える。ストーリーカード、イテレーション、デイリースタンダップ、見積もり、振り返り。どうすれば気むずかしくて内向的なプログラマーたちを、チーム作業に適応させることができるのか。

現在ではプログラマは、品質の高いソフトウェア製品をチームで開発する方法について、一定の知見を有するようになった(もちろんXPがカバーする範囲で)。


さて、2010年になって周りを見てみると、取り残された人々がいる。より正確には、XPが生まれた時点ではソフトウェア製品の開発で重要な役割を果たすと認識されていなかった、そして現在では当時よりもはるかに重要であると認識されているデザイナと呼ばれる人たちがいる。UIデザイン、情報デザイン、ビジュアルデザイン、マイクロインタラクション。デザイナが貢献する分野は広範囲にわたっていて、どれも今日のソフトウェア製品では重要な分野として認識されている。

が、いまいちXPやアジャイルの流れで発見されてきたプラクティスは共有されていない。

使っているツールに起因する困難はある。PNGをGitで管理することにはほとんど意味がない。なぜかというとそれらはPhotoshopで編集した結果を出力したものだから。PSDを無理矢理Gitで管理することはできるがdiffをとったり、マージしたりするのはほぼ不可能である。マージできないファイルを複数人で同時に変更することはできない。プログラミングの対象となるソースコードとはかなり状況が違う。*1

ツールについては発展を待とう。しかし、プロセスにも問題がある。反復的な開発プロセスにどうやって適応すれば良いか。デザインはそれだけでは意味を持たず、ユーザーに価値を届けることが本質である。デザイナだけで議論することには意味がない。他の人たちと、どうやってコミュニケーションすれば良いか?

UX design is a team sport.

チームの関係者を集めて一緒にデザインするには、紙とペンで絵を描いて壁に貼る。その前に人間の描き方を説明しよう。UXの改善とはなにか?課題はどう定義すれば良いのか?問題があることを説得するにはどうすれば良いか?関係者みんながアクセスできるデータ共有方法はなにか?


今よりもソフトウェア製品を使ったときの体験を向上するにはどうすれば良いか?ボトルネックはデザインの技量ではなくて、別のところにある 。つまりコラボレーションでありプロセスである。そういう意識を感じた。*2

かなり良いイベントでした。

*1:多分、あと5〜10年でこの状況は改善されると思う。PhotoshopIllustratorが支配的な立場を保てるのは、紙や印刷物と計算機上で使う画像を両方サポートしているからであり、印刷屋さんとコミュニケーションするためのプラットフォームとしての役割があるからである。Sketchのような、計算機上で扱う画像のためだけのソフトウェアが一定の成功を収めることがわかった今、この分野の競争が激化することは間違いがない。そのときに重要であると考えられるに違いない(と私が信じている)のはプログラマとの協業を容易にすることであり、つまりGitで変更を管理してみんなで編集して後でマージするというワークフローとの親和性である。

*2:と言っても、そもそも私の問題意識があってこそ勝手に匂いをかぎ取っているだけかもしれないし、特に目新しい話ではないのかもしれない。プログラミングの話は大まかな流れはざっくりとは理解しているつもりだけど、この界隈の流れは良くわからないので、これはあまり意味のある言明ではない。