Azuki プロジェクトで一つデグレードを起こしてしまいました。クリップボードにテキストでないデータが入っているときに貼り付け処理を実行すると「クリップボードを開いたまま」になってしまい、他のアプリケーションがクリップボードを使えなくなってしまう、というバグです。
原因は、クリップボード内のデータ種別がサポート対象外だった場合にクリップボードをクローズせずに終了していた、という単純なものです。単純化して処理を擬似コードで示すと次のようになります。
クリップボードを開く
if( 開けない ) {
return
}
if( クリップボードにUnicodeテキストがあるか )
Unicodeテキストをそのまま取得
else if( クリップボードにANSIテキストがあるか )
ANSIテキストをUnicodeに変換して取得
...
else
return // クリップボードを閉じていない...
選択範囲を取得したテキストデータで置換
クリップボードを閉じる
まったくダメダメなミスで弁解の余地もありません…。ただ、実は最初からこのような処理になっていたわけではありません。あるときの機能追加でクリップボード関連処理を拡張するまでは、次のような形になっていました。
if( クリップボードにUnicodeテキストがあるか )
データ種別 = UNICODE
else if( クリップボードにANSIテキストがあるか )
データ種別 = ANSI
else
return
クリップボードを開く
if( 開けない ) {
return
}
if( データ種別がANSIテキスト )
データをANSIテキストとして取得し、Unicodeに変換
else
データをUnicodeテキストとしてそのまま取得
選択範囲を取得したテキストデータで置換
クリップボードを閉じる
元々は大丈夫だった、つまり機能拡張時のデグレードでした。
さて。元々の実装はバグがありません。なぜならクリップボード中のデータ種別をクリップボードを開く前に判定していたため、クリップボードにサポートしない種類のデータ(画像など)しか無い場合にクリップボードを閉じる必要は無かったのです。しかしこの「閉じる必要が無い」ことは、「例外ケースを前もって切り捨ててから処理に入る」という前提条件があってこそ実現されていたと思います。元々の実装ではこの前提条件が分かりやすく表現されていませんでしたし、コメントもありませんでした。最初に実装してから1年以上経過した私は、すっかり忘れていたというわけです(2008年1月に元々の処理を実装、2009年7月に機能拡張=デグレード、2010年7月にユーザフィードバックで発見される)。
では前提条件をはっきり記すべきだったのか?というと、私は違うと思います。最善は「前提条件を意識しないで済むよう実装する」ことだった思います。簡単に言うと、try/finally構文がまったく効果ない場合でも、リソースの解放が関わる場面なので愚直にtry/finally構文を使っておけば良かったのです。元々の実装ではtry/finallyはまったく不要でしたが、それでも泥臭くエレガントでもない愚直なtry/finallyでクリップボードを閉じておくべきだった、というのが今回得た教訓です。
前提条件を意識しないで済むようなコーディングを心がける。自分の集中力が多少低下していても大丈夫という意味で安全性を高めること、実装時の文脈を持たない他人に引き継いでも間違いを起こしにくくなることから、業務ではかなり意識しています。今回デグレードが見つかった箇所は学生時代に作った部分で、今読み返してみると未熟未熟。失敗を通して、成長を振り返る機会に恵まれたなと前向きに考えることにします。