技術者面接総合篇(1)

技術者・SE・プログラマ面接時の技術的な質問事項というエントリをはてブで見かけたのだが、私もjavaプログラマーの面接を割とよくやっているので、よく質問する内容をまとめてみた。
(ちなみに、基本的にコーディング面接の形態を取っている)

プロジェクトの性質にもよると思うが、私の場合には、情報処理技術者試験的に基礎が満遍なく抑えられているかどうかよりも、

* すぐ答えが見つからないような課題に対して、きちんと自分でやり方を考え、対応することができるか
* 「変な」コードをコミットしたりしないか(見つけにくいバグを混入させるとか、汚いとか、遅いとか)

といった点を重視している。


■ 開発実績編
まず、何を知っているかよりも、どんなものを作れるか、どんなことができるか、という質問。
ここで強烈な回答が来る人は、たいていここより下の質問は「あー、はいはい」という感じでサラッと答えてくることが多い。

* これまでに携わってきた開発プロジェクトの中で、一番の自信作と言えるものはどんなものですか?
o その中であなたが果たした役割は?
o その中で特に工夫した点、解決が困難だと思われた課題に対してとったアプローチは?


■ プログラミング基礎編
次に、言語に依存しないプログラミングの基本の部分で、分かっていないとクリティカルなバグが混入する可能性があるような内容についての確認。

* for (int i = 0; i < list.getLength(); i++) {}の潜在的パフォーマンスボトルネック
(実際、これが原因でパフォーマンスが数倍遅くなっていた事例を見たことがある)
* 参照渡し/値渡し関係の質問
* switch fall throughなどのうっかりミスが出やすいケースでの問題箇所の指摘


■ オブジェクト指向編
次に、固有の言語に依存しないオブジェクト指向の理解の確認。
ごく基本的な内容だが、現実問題として、「Javaを5年やってます」というような人でもこの手の質問にきちんと答えられないことがたまにある。

* クラスとインスタンスの違いの説明
* インターフェースと抽象クラスをどんな基準で使い分けているか
* 継承とコンポジションをどんな基準で使い分けているか


■ デザインパターン編
デザインパターンは基本的にはコミュニケーションツール(一言で説明できてすぐ伝わる)としての位置づけが重要だと思っているので、すべて知っていないと NG、ということはまったくないが、普段普通に使っている、という人の場合には、お互いのレベルを見るのにもってこいのディスカッション材料なので、よく面接の時に話題に上る。

* Singletonパターンとオブジェクト生成コスト、Singletonパターンのマルチスレッド対応について、注意すべき点を説明してください
* Observerパターンでイベント発火順序が重要な理由を説明してください
* Mediatorパターンでメソッドが多くなりすぎてきたときにどのような工夫をしますか
* Visitorパターンを使うべき場合と、再帰的ループで対処する場合とを、どのように使い分けますか
* Compositeパターンが時として「オブジェクト指向的に気持ち悪く」なるのは何故ですか


■ Java編
次に、Java経験者の場合に質問する、Java関係の質問。
基本的なものからトリッキーなものまで、ここでは下記のような内容を思いつくままにランダムに質問してみることにしている。

* static/finalの意味
* List/Set/Mapの違い
* new Boolean(true)、new Integer(5)などがあまり推奨されない理由
* 防御的コピーの話
* Javaでのディープコピーの実装方法としてどのようなものが考えられますか(普通にclone()を実装、直列化を利用etc.)
* バージョンの異なる同一FQNのクラスを利用する必要があるとします。どのような対応方法が考えられますか(VMをわける→その場合の通信方法、クラスローダーを自作するetc.)
* スレッドプログラミング関係は得意ですか? → 答えがyesだとその場で簡単なペアプロ開始


■ その他

* その他、できるだけ相手の得意分野に合わせて都度コーディング面接。




解答:
昨日、プログラマー面接時の技術的な質問事項(アプレッソ版)を書いたところ、「自分ならこう答える」というエントリを書いてくれた人が何人かいて、個別にコメントしようかとも思ったのだが、昨日のエントリだけだと質問の投げっぱなしになってしまうところもあるので、解答編を書くことにした。

なお、「面接の質問項目を公表しちゃっていいの?」という指摘もあったが、ブログに書いたのはあくまでも質問項目の一例だし、解法を検討する過程を見れば普段どんな風に開発しているのかはだいたいわかるので、特に問題ない。

■ プログラミング基礎編

* for (int i = 0; i < list.getLength(); i++) {}の潜在的パフォーマンスボトルネック
list.getLength()がlist.getLength()回評価されてしまう。具体例としては、JREに標準で付属するDOMのライブラリの NodeListの実装はlist.getLength()内で非常に重い処理をしているため、これが原因で劇的にパフォーマンスが劣化することがある。
(まあ「getLength()」で重い処理するなよ、という話もありますが)

この問題を回避するためのイディオムとしては、
for (int i = 0, length = list.getLength(); i < length; i++) {}
といったものがあり、IBM developerWorksなどプログラマー向けのメディアでは、この書き方に統一しているものもある。

* 参照渡し/値渡し関係の質問、switch fall throughなど
これはそのままなのでパス。

オブジェクト指向編

ここは全項目そのままなのでパス。

デザインパターン編

* Singletonパターンとオブジェクト生成コスト、Singletonパターンのマルチスレッド対応
Singletonの書き方としては、まず下記のようなものが考えられる。

パターン1. フィールドの初期化時にインスタンス生成

public class Singleton {
  private static final Singleton self = new Singleton();
  private Singleton() {}
  public static Singleton getInstance() {
    return self;
  }
}

パターン2. 遅延初期化を用いて必要になったときにインスタンス生成

public class Singleton {
  private static Singleton self;
  private Singleton() {}
  public static synchronized Singleton getInstance() {
    if (self == null) {
      self = new Singleton();
    }
    return self;
  }
}

通常はパターン1.で良いとして、インスタンス生成コストの大きいオブジェクトを扱う場合にはパターン2.のような遅延初期化の採用が考えられるが、パターン2.の形にしてしまうとマルチスレッド対応が必要な場合にアクセッサーを同期化する必要があり(この例で言うとsynchronizedの箇所)、アクセッサーが頻繁に呼び出されるようなケースではこの同期化のコストを何とかしたいですね、さてどうしましょう、という話。

ちなみに、Javaに特化した話としては、下記のようにオンデマンド初期化ホルダークラスイディオム(Effective Java 第二版 p.273)を使うと、遅延初期化の恩恵を受けつつ、なおかつ同期化のコストをかけずに済むイディオムもある。

パターン3. オンデマンド初期化ホルダークラスイディオム

public class Singleton {
  private static class Holder {
    static final Singleton self = new Singleton();
  }
  private Singleton() {}
  public static Singleton getInstance() {
    return Holder.self;
  }
}

# Observerパターンでイベント発火順序が重要な理由
・イベント発火順序を正確に把握せずにObserverパターンを使ってしまうと、一つのSubjectに複数のObserverがついており、かつ一方のObserverがもう一方のObserverの値を参照しているような場合にデータの不整合が生じる危険性があるから etc.

# Mediatorパターンでメソッドが多くなりすぎてきたときの工夫
・機能のまとまりごとにインターフェースを用意し、Mediatorはそれらのインターフェースを実装し、Mediator利用者にはインターフェース渡しする
・Role Objectパターンに切り替える
・etc.

# Visitorパターンを使うべき場合と、再帰的ループで対処する場合との使い分け
単にComposite型のオブジェクトを再帰的に処理したいだけならむやみにVisitorパターンを使うと単にシンプルさが失われるだけになってしまうことがあるので要注意ですよねー、とか、その場合でも様々なところから利用されるようなクラスについては一応Visitorの口も用意してあると親切な設計になる場合もありますよねー、とか、その辺りの話。

# Compositeパターンが時として「オブジェクト指向的に気持ち悪く」なる理由
内容と容器を同一視するために、本来一方にしか含まれないインターフェースをもう一方に持たせるケースがあるから。例えば本来は容器にしか含まれない子要素取得系のメソッドが内容の側にも空実装として含まれるケースとか。


Java編

* static/final、List/Set/Mapの違い
そのままなのでパス。

* new Boolean(true)、new Integer(5)などが推奨されない理由
使い回しの利くImmutableなオブジェクトを複数生成して無駄にヒープを汚すのは望ましくないから。代わりに Boolean.valueOf(true)、Integer.valueOf(5)などを使う。

* 防御的コピーの話
防御的コピー = フィールドとして持っているオブジェクトを外部に渡すときに複製をつくって渡す。本来防御的コピーが必要なソースを書いてみて、潜在的問題として防御的コピーが行われていないですねー、とか。

* Javaでのディープコピーの実装方法としてどのようなものが考えられますか
・普通にclone()を実装
・直列化を利用
・etc.

* バージョンの異なる同一FQNのクラスを利用するための対応方法
・VMをわけて何らかの形で通信(RMI etc.)
・クラスローダーを自作
・etc.
ちなみにクラスローダーの作り方については以前Java Programming Tipsに書いた記事を参照のこと。