よりスマートな画面遷移?

先日、社内のRailsな人にSAStrutsのことを軽く説明する機会がありました。


以前のエントリーに書いた『画面に表示するデータを準備する』メソッドの
名前は遷移先のjspファイル名にあわせる、という非公式の私のお作法を説明した後に、
以下のようなコードを見てもらったのですが、どうも気に入らないと。

    @Execute(validator = false)
    public String hoge() {
        return "hoge.jsp";
    }


理由は次のとおり。

  • 遷移先のJSPファイル名とメソッド名をマッピングさせるお作法は、メソッド名と戻り値に同じ文字列が出てくるのでDRYっぽくない
  • せっかくJavaを使っているのに、戻り値がタイプセーフじゃないのはダサい


うーん、なるほど。一理ある。
ということで、以下のような感じで記述できる
インターセプター(ForwardByMethodNameInterceptor)を作ってみました。


このインターセプターを適用すると、
先ほどのコードは以下のように書き換えることが出来ます。
(一応、社内のRailsな人も納得でした。)

    @Execute(validator = false)
    public String hoge() {
        return Forward.BY_MEHTOD_NAME;
    }


実行メソッドの戻り値がForward.BY_MEHTOD_NAMEだったら、
戻り値を「メソッド名 + ".jsp"」に差し替えるのが
ForwardByMethodNameInterceptor の機能です。
Forward.BY_MEHTOD_NAME はString型の定数です。
(このキーワードはより分かりやすいものに見直したいところ。)


元々、SAStrutsはURLとメソッドはCoCによりマッピングできるが、
これにより URLとメソッドとJSPファイルが簡単なルールにより
マッピングできることになります。


ForwardByMethodNameInterceptor 導入の
メリット・デメリットについて検討してみました。


【メリット】

  • よりタイプセーフなコーディングが可能
  • メンテナンス性の向上(JSPファイル名の変更があった時に、戻り値も一緒に修正する手間を省けるため)
  • JSPファイルとメソッドのマッピングが明確となりソースコードの可読性UP
  • 『画面表示用データ準備』メソッドと『画面入力データ処理』メソッドの責務が分離しやすくなる
  • JSPファイル名から機械的に実行メソッドの雛形が作成可能となる
  • フレームワークが押し付ける機能ではない。使いたい人が使えばよい自由さがある


【デメリット】

  • やや黒魔術的という意味において、ソースコードの可読性が下がる
  • 覚えることが少し増えて、学習コストが上がる
  • フレームワークの肥大化につながる


作成したソースコードは以下のとおり。


ForwardByMethodNameInterceptor.java

public class DefaultRoutingInterceptor extends AbstractInterceptor {

    /**
     * 遷移先のパスが Forward.BY_MEHTOD_NAME だったら、
     * 戻り値を「メソッド名 + ".jsp"」に書き換えます。
     */
    public Object invoke(MethodInvocation invocation) throws Throwable {

        Object ret = invocation.proceed();

        if (isExecuteMethod(invocation.getMethod())) {
            String navitationPath = ret.toString();
            if (Forward.BY_MEHTOD_NAME.equals(navitationPath)) {
                return invocation.getMethod().getName() + ".jsp";
            }            
        }
        
        return ret;
    }

    protected boolean isExecuteMethod(Method method) {
        Execute execute = method.getAnnotation(Execute.class);

        if (execute == null
            || method.getParameterTypes().length > 0
            || method.getReturnType() != String.class) {
            return false;
        }

        return true;
    }
}


Forward.java

package tutorial.consts;

public class Forward {
    public static final String BY_MEHTOD_NAME = "sastruts.Forward.BY_MEHTOD_NAME";
}


行き過ぎたCoCはNGですが、強要しないし、マッピング系のCoCはありだと思っています。