はじめに
驚き最小の原則(法則)という言葉があります。
Wikipediaの記事を引用すると
http://ja.wikipedia.org/wiki/%E9%A9%9A%E3%81%8D%E6%9C%80%E5%B0%8F%E3%81%AE%E5%8E%9F%E5%89%87
ユーザインタフェースやプログラミング言語の設計および人間工学において、インタフェースの2つの要素が互いに矛盾あるいは不明瞭だったときに、その動作としては人間のユーザやプログラマが最も自然に思える(驚きが少ない)ものを選択すべきだとする考え方である。
要するに、使うときに「おやっ?」という驚きが少ないほうが良いプログラムであるといえます[1]。
[1]: どっちが驚きが少ないか迷う場面もかなり多いですが・・・
この記事では敢えて驚きの多いプログラムの書き方を紹介します。驚きの多いプログラムを読むとどんな気分になるか、実際に体験してみてください。もちろん、本当は驚きが少ないプログラムを書くほうがよいので、真似してはいけませんよ?
コーディングで驚かせる
非キャメルケース
Javaではキャメルケースを利用するのが一般的です(定数を除く)。それを破ればみんな驚きます。
public class db_manager {
private String connection_url;
public void Init(){
・・・
}
・・・
}
連番名
クラス名やメソッド名、なんならフィールド名まで徹底的に管理して連番名にしてしまいましょう。名前から中身が想像できないインパクトはかなり大きいです。
public class UC8010 extends CMN0101 {
private String FLD0001;
public int MTD0001() {
・・・
}
・・・
}
publi class UC8020 extends CMN0101 {
private String FLD0001;
public int MTD0001() {
・・・
}
・・・
}
フラグ
フラグという名前でtrue,falseあるいは0,1以外の値が入っているとみんなびっくりします。
String modeFlag = getFlag();
switch(modeFlag) {
case "LOGIN":
・・・
case "LOGOFF"
・・・
}
また、フラグの名前と動作を直感と真逆にするのも良い方法です。
if (skipFlag == true) {
execute();
}
名前にないことをやる
名前に書いてないことまでやっちゃいましょう。
下の例は値のチェックを行うメソッドと見せかけて結果がOKならその後の処理も進めてしまうメソッドです。驚きでしょ?
boolean checkValue(int value) {
if (value == -1) {
return false;
}
execute(value);
return true;
}
関係ありそうでない名前
IFHogeというインタフェースとHogeというクラスがあれば、みんなHoge implements IFHoge
だと思います。そこを裏切られたときの相手の顔を想像するだけで楽しいですね。
public interface IFHoge {
void someMethod()
}
public class Hoge {
// IFHogeとは関係ないよ~ん
}
見ればわかることにコメントを書く
自明な処理にもコメントをつけると「なんて丁寧なんだ」と驚きます。
int time = endTime - startTime; // timeにendTime - startTimeを代入
名前の和訳コメント
コメントでそれがどんな処理がなのか、どんな意図があるのか、どんな注意点があるのかなどを書くのは普通すぎます。そこで、コメントでは名前の和訳に徹しましょう。「知りたいことが何も書いてない!」と驚きます。
/*
* 値チェック
*/
booelan checkValue() {
・・・
}
嘘のコメント
当たり前ですがコメントで言ってることと実際にやっていることが違っているとびっくりします。
新規コードであからさまに嘘を書くのは芸がありません。システム改修のときがチャンスです。ソースコードを書き換えたときに元ソースのコメントは更新せずそのままにしておきましょう。あっという間にコメントと処理が乖離していきます。
if (mode == STANDBY) {
// スタンバイモードのときは何もせず即リターン
standbyService.execute();
}
スコープは広く
多くの人に驚いてもらうためには多くの人に見てもらわなければ話が始まりません。積極的にpublicを使っていきましょう。スコープは広ければ広いほどよいです。
public void publicMethod() {
innerMethod();
}
public void innerMethod() {
// なんか処理
}
何かのクラスを継承する機会があれば、ついでにメソッドの可視性を全部publicに上げてあげるのも親切ですね。
使えるものはとことん使う
逆に一見使ってはいけなそうなものでも使える状態なら使ってしまいましょう。非公開APIやDeprecatedなクラスやメソッドでも積極的に使っていきましょう。もったいないですし。作者は思いもよらない使われ方にびっくりします。
sun.misc.BASE64Encoder e = new sun.misc.BASE64Encoder();
String encoded = e.encode(buf);
リフレクション
適切に設計されたコードは適切にカプセル化されているのでそのままでは触れないことがあります。そこで、リフレクションです。リフレクションはフレームワーク作成など真面目な場面を除けば人を驚かせるために存在すると言っても過言ではないでしょう。
Class c = obj.getClass();
Field f = c.getField("counter");
f.setInt(obj, 0); // カウンターをゼロにリセット
is-a ではない継承
継承も使い方次第では相手を大いに驚かせられます。
例えばDBの種類によって処理を切り替えるためにDB処理をDBStrategyとして抽象化したとします。各DBの処理はこのインタフェースを実装して行います。
interface DBStrategy {
・・・
}
class MySQLStrategy implements DBStrategy {
・・・
}
class SQLServerStrategy implements DBStrategy {
・・・
}
実装してみたら、MySQLStrategyとSQLServerStrategyには似た処理が多いことに気づきました。
そんなときはこんな風にMySQLStrategyを継承したSQLServerStrategyを作って差分のみプログラミングしましょう。みんな「あれ!?」と思います。「SQLServer is MySQL」ではないからです。
interface DBStrategy {
・・・
}
class MySQLStrategy implements DBStrategy {
・・・
}
class SQLServerStrategy extends MySQLStrategy {
・・・
}
下のようにまっとうに書くと驚きは得られません。
interface DBStrategy {
・・・
}
abstract class AbstractDbStrategy implements DBStrategy {
・・・
}
class MySQLStrategy extends AbstractDbStrategy {
・・・
}
class SQLServerStrategy extends AbstractDbStrategy {
・・・
}
ダウンキャスト
具象クラスがほしいときは積極的にダウンキャストしていきましょう。もちろん、型検査は極力行わないほうがいいですね。また、設計を見直す必要もありません。
後々、具象クラスを切り替えられなくて驚く人が出てきます。
public void execute(DBStrategy strategy) {
MySQLStrategy mySqlStrategy = (MySQLStrategy)strategy;
・・・
}
密な依存
積極的に他クラスへの依存を張りましょう。複雑であれば複雑なほどよいです。こっちをいじればあっちが壊れる、あっちに依存してて直せない。テストもかけない。それくらい主張しないと驚いてもらえません。
例外の握りつぶし
ここにきて基本中の基本です。人を驚かせたいならまずはここからと言っても過言ではありません。
出来上がったシステムが変な動きをして利用者を驚かせられますし、悪い場所を特定できずにデバッグが難しいので運用者も驚きます。
try {
someService.execute();
} (SomeServiceException e) {
// よくわからないので握りつぶす
}
根本例外を捨てる
もう少し高度な方法とて根本例外を捨てる方法があります。例外をキャッチして別の例外に変換して上位にスローするときにしれっと根本例外を捨ててしまいましょう。
try {
someService.execute();
} (SomeServiceException e) {
throw new SystemException();
// throw new SystemException(e); //←これでは驚きは得られない
}
これをやると何かエラーが起きたときにログに根本例外が表示されないのでこれ以上追えません。デバッグする人はすごく驚きます。
ハンドリングすべきでない例外のキャッチ
コーディングミスの結果として発生する例外があります。典型的な例はNullPointerExceptionです。これらの例外はそもそも発生しないようにコーディングするのが鉄則なのですが、あえてキャッチしてハンドリングしてしまいましょう。おかしな事態の発覚が遅れて驚く人が続出です。
try {
someService.execute();
} catch(NullPointerException e) {
// 回復させちゃえ
}
できるだけ状態をもたせる
オブジェクトには出来る限り状態をもたせましょう。呼ぶたびに同じ結果が保証された世界なんて面白くないでしょ?
ハッシュコード要件を無視
JavaではObject#equals()メソッドをオーバーライドしたらObject#hashCode()メソッドも正しくオーバーライドしなければいけません。これはHashtableやHashMapを使うための要件です。このクラスはHashMapに入れる予定ないし、と思ったら積極的に無視しましょう。「え?HashMapに入れたの?そりゃ動かないよ!」と驚く日が来るでしょう。
テストで驚かせる
自動テストは行わない
いまやJUnitやSeleniumといったツールを使って自動テストをするのは当たり前です。これらを行わないといえばプロジェクトメンバーは驚きます。
カバレッジ100%
どうしても自動テストを行うなら、テストカバレッジは絶対に100%を目指しましょう。その目標自体にメンバが驚きますし、カバレッジのためだけに無理やりテストを書くことになるので品質が上がらずあなたが驚くでしょう。
管理で驚かせる
書いたコードはできるだけ人に見せない
コードを人に見せるということは、それだけ情報を与えるということです。相手の驚きを最大にするためには事前情報を与えずにここぞというときに出すべきです。そのためにも、書いたコードはできるだけ人に見せる機会は最低限に抑えましょう。
履歴管理は手動で
古いソースは日付フォルダで管理しましょう。もちろんソースコードの修正箇所はコメントアウトしてソースには残し、日付と会社名・氏名を添えます。
これを見ると急に江戸時代にタイムスリップしたかのような驚き体験が得られます。
手動でビルドする
ビルドは手動で行います。間違っても自動化してはいけません。許されるならビルドの手順書も作らないのがベターです。仕上げに、出所のわからないjarをlibフォルダに突っ込んでおくとベストです。後任者はあっと驚きます。
参考文献
- Wikipedia 驚き最小の原則
- 結城浩 プログラマの心の健康
最後にちょっと蛇足
もともとこの記事は「あっと驚かせるJavaプログラミング」という名前で次のような文章で始めていました。
単なるプログラミングじゃつまらない。みんなを「あっ!」と驚かせる何かをしたい。そんなときに使える雑多なTips集です。
ありがたいことに私の想像以上に多くの方に読んでいただいて、多くの人に面白いという感想をいただけました。
一方、「本当に驚かせる方法を期待してこの記事を開いたのだけれどがっかりした」とか「真に受ける人がいないか心配」という意見もいただきました。つまり、「驚き最小の原則」を訴えようとしたこの記事そのものが驚きを含むというブーメラン状態でした。トホホ・・・
その状態はその状態で皮肉がきいてて面白いかとも思ったのですが、はっきりと「驚き最小の原則」を主張したいなと思い今の形式にしました。