Red > Green > Refactor > Red

cycle is based on desire

仕様と設計の違い

f:id:mat5ukawa:20160817231309p:plain

将来どこかで誰かに

「仕様と設計を混同した時はこれ見といてね」と

言えることを念頭において書いた記事

「仕様」 と 「設計」 は別物

私なりの言葉で定義すると

仕様

= 作るものについて、満たされているべきことが定義されたもの

満たされているべきこととは「顧客が期待している結果」と「期待していない結果」のことだ

あなたが SE ならばこの定義について顧客と合意形成を取るべきだ

設計

= 仕様をどう実現するかが具体的に明確化されたもの

設計を基に SE と PG で意思疎通を図る

具体的な手段(モノ)として設計書がある


具体例を挙げるのでおおよその感覚を掴んでいただければ幸い

(突っ込み所は色々あると思うがスルーでお願いしたい)


例 - 顧客からのブログ Web アプリ作成依頼

あなたは SE だ(時には PG だったりする)

ある顧客から実現したいことについて依頼を貰った

依頼内容

弊社内で完結させる、ブログ Web アプリが欲しい
直近で (1) を実現してほしい

(1) タイトル・内容
    を投稿できる画面を作る

補足しておくと
  今考えている画面総数は 3 つ
    記事を投稿できる画面
    記事の一覧を表示する画面
    記事の内容を確認する画面
(資料を添付するので見ておいてください)

添付資料

f:id:mat5ukawa:20160817231232p:plain

顧客の依頼内容は「作るものがどうあるべきか」を示した「仕様」だ、「設計」ではない

「設計」はまだだ

あなたは「ご依頼を一度預かります」と伝え、依頼内容を確認する

「この依頼内容でシステムへ落とし込めるが、幾つか顧客が期待する結果を聞かないといけない」

と考えた

そう、入力制限がないことに違和感を覚え始めているはず

依頼内容を精査し、顧客に次の質問を持ちかけた

  • タイトルの文字数はいくつまで可能? (これは仕様だ)
  • タイトルは未入力を許してよい? (これも仕様だ)
  • 内容の文字数はいくつまで可能? (仕様だ)
  • 内容は未入力を許してよい? (これも)

その結果こうなった

(1) タイトル・内容
    を投稿できる画面を作る
(1) - 1
    タイトルは 255 文字まで
(1) - 2
    内容は 1,000 文字まで
(1) - 3
    タイトルのみ
    未入力、空白記号のみの入力で投稿はできないようにする

繰り返しになるが、これは「仕様」であり、実施していたのを仕様を決める行為だ

「設計」は一つもやっていない = 仕様をどう実現するかの具体化は、まだ何もやっていない

仕様を決めていた

f:id:mat5ukawa:20160817231450p:plain

ここからようやく「設計」だ

仕様で決まったことをどう実現するかを具体的に明確化しよう、ここから設計だ

あなたが 開発の設計者なら次の設計をするだろう

  • ユースケースを書いてユーザー視点の実行パターンを明確化する
  • ER 図を書いてテーブル連携の明確化する
  • (もう少しコード側に突っ込んで)
    文字数バリデーションを実行する手段を明確化する
    • FW が持っているバリデーションメソッドを使うか
    • form から POST された値をビジネスロジック内で
      length や size などミドルウェアのメソッドで文字数チェックし
      文字数を超えていれば 例外出力する処理とするか
  • 他、色々
  • (設計書には仕様の説明は書かない。仕様は自明である前提だ)

テストの設計者なら別のことをするかもしれない

仕様を決めた後に設計をする

f:id:mat5ukawa:20160817231527p:plain


蛇足

1

「ここからようやく「設計」だ」に関する項目が曖昧だが...

この辺り勉強中なので、色々わかり次第またの機会に

2

仕様を相談するくだりは

説明用ということでその内容を簡素(削った、という方が正確)にしたが

あなたなら顧客へ他に何を質問・相談するだろうか、考えてみてほしい

動機付けになった Web リソース

参考にした Web リソース

Java の interface method に public や abstract を宣言してもよいか

interface MyInterface {
  public abstract void myMethod(); // public と abstract の宣言
}

Oracle の Docs より

「してもよいが、冗長なので奨められない」

意訳(一部)

interface のメソッドは暗黙のうちに public である

interface のメソッドは暗黙のうちに abstract である,
なのでブロックは持たずセミコロンで表現されている必要がある

public と abstract どちらか/両方 を interface のメソッドで宣言してもよいが、
冗長なので奨められるされるやり方ではない

意訳(一部)元

Every method declaration in the body of an interface is implicitly public.

Every method declaration in the body of an interface is implicitly abstract,
so its body is always represented by a semicolon, not a block.

It is permitted, but discouraged as a matter of style,
to redundantly specify the public and/or abstract modifier for a method declared in an interface.

struts.xml の global-exception-mappings とは何か

struts2-core v2.3.28

global-exception-mappings とは

はじめに、Struts2 には Exception mappings という機能がある

これは Action 内で throw された例外を自動的に catch

あらかじめ用意された resultマッピングする機能である

この Exception mappings

どの resultマッピングするかを定義する xml 属性が

global-exception-mappings である

2 点注意が必要

1(X)

ローカルのマッピング(action.exception-mapping)を定義できるが

これを有効化した時、グローバルのマッピング(global-exception-mappings)は無効化される

2(Y)

あくまで action#classexecute 内で発生した例外をマッピングするもので

action#classコンストラクタ内で throw された例外はマッピングされない

具体的に

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
        "http://struts.apache.org/dtds/struts-2.5.dtd">

<struts>
  <constant name="struts.devMode" value="true" />
  <constant name="struts.ui.theme" value="simple" />
  <package name="top" extends="struts-default">
    <global-results>
      <result name="myException">/Exception.jsp</result>
    </global-results>
    <global-exception-mappings>
      <exception-mapping exception="java.lang.Exception" result="myException" />
    </global-exception-mappings>
    <action name="index" class="jp.ymatsukawa.top.Index">
      <result name="success">/index.jsp</result>
    </action>
  </package>
</struts>

/top/index.action へ GET リクエストされたとする

action#class=jp.ymatsukawa.top.IndexExceptionthrow された場合

global-exception-mappingsException を捕捉する

この時 exception-mapping#result に対応する result を実行する

上の場合だと resultmyException なので

global-results.result#name="myException" が実行される

(/Exception.jspdispatch される)

(X)

(動作確認より)

global-exception-mappings.exception-mapping を定義後

action.exceptoin-mapping を用いて

個々の actionexception-mapping を定義することができる

ただし、その場合は global-exception-mapping の設定は無効になる

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
        "http://struts.apache.org/dtds/struts-2.5.dtd">

<struts>
  <constant name="struts.devMode" value="true" />
  <constant name="struts.ui.theme" value="simple" />
  <package name="top" extends="struts-default">
    <global-results>
      <result name="myException">/Exception.jsp</result>
    </global-results>
    <global-exception-mappings>
      <exception-mapping exception="java.sql.SQLException" result="myException" />
    </global-exception-mappings>
    <action name="index" class="jp.ymatsukawa.top.Index">
      <exception-mapping exception="java.lang.RuntimeException" result="error" />
      <result name="success">/index.jsp</result>
      <result name="error">/myError.jsp></result>
    </action>
  </package>
</struts>

もし jp.ymatsukawa.top.Index

java.sql.SQLExceptionthrow されても

/Exception.jspdispatch されず

/myError.jspdispatch される

(global-exception-mappings.exception-mapping#exception=java.lang.Exception

でハンドリングをまとめておき、個々の action

Exception 以外の例外捕捉を必要に応じて追加するのが良いか...)

(Y)

jp.ymatsukawa.top.Indexコンストラクタが 例外を throw し得る場合

global-exception-mappings はこの例外をマッピングしない

極端な例

package jp.ymatsukawa.top.Index;
import com.opensymphony.xwork2.ActionSupport;

public class Index extends ActionSupport {
  public Index() throws Exception {
    throw new Exception;
  }

  @Override
  public String execute() {
    return SUCCESS;
  }
}

この場合、Struts2 は例外マッピング、例外ハンドリングすることなく、エラー処理を実行する

[参考元]

https://struts.apache.org/docs/exception-configuration.html

struts.xml global-results 要素とは何か

struts2-core v2.3.24.1

struts.xml global-results 要素とは何か

action 間で共有したい result がある時、これを定義する為の要素

  • package ごとに global-results を定義できる
  • Struts2 は先ず ローカルの result(package.action.result)を走査する
    • ローカルで result が見つからない場合、global-results を走査する

(要素の設置順序が決まっているようなので設定ミスしないように)(X)

具体的に

或る package の result#name="error" を共通化する

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
        "http://struts.apache.org/dtds/struts-2.5.dtd">

<struts>
  <constant name="struts.devMode" value="true" />
  <constant name="struts.ui.theme" value="simple" />
  <package name="top" extends="struts-default">
    <global-results>
      <result name="error">/error.jsp</result>
    </global-results>
    <action name="index" class="jp.ymatsukawa.top.Index">
      <result name="success">/index.jsp</result>
    </action>
  </package>
</struts>

action 要素の前に global-results を設定する

global-results の下に共通化したい result を設定する

global-results.result を action.result で上書きできるか

できる

上の例より result#name="error" の時

/error.jsp ではなく, /myError.jsp を render させたい場合は

下のように設定する

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
        "http://struts.apache.org/dtds/struts-2.5.dtd">

<struts>
  <constant name="struts.devMode" value="true" />
  <constant name="struts.ui.theme" value="simple" />
  <package name="top" extends="struts-default">
    <global-results>
      <result name="error">/error.jsp</result>
    </global-results>
    <action name="index" class="jp.ymatsukawa.top.Index">
      <result name="error">/myError.jsp</result>
      <result name="success">/index.jsp</result>
    </action>
  </package>
</struts>

(X)要素の設定順序に注意

action の前に global-results を設定する

要素は上から順に以下の通りである

result-types?,
interceptors?,
default-interceptor-ref?,
default-action-ref?,
default-class-ref?,
global-results?,
global-allowed-methods?,
global-exception-mappings?,
action*

[参考元]

https://struts.apache.org/docs/result-configuration.html#ResultConfiguration-GlobalResults

http://stackoverflow.com/questions/3742379/struts2-global-results-configuration-error

struts.xml default-interceptor-ref 要素とは何か

default-interceptor-stack 要素とは何か

package 内で定義した interceptor-stack の内

package の各アクションにてデフォルトで参照させたい

interceptor を定義する

具体的に

下記 struts.xml より interceptor-stack に ロギングが定義されている

(action はまだ interceptor を参照していない)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
        "http://struts.apache.org/dtds/struts-2.5.dtd">

<struts>
  <constant name="struts.devMode" value="true" />
  <constant name="struts.ui.theme" value="simple" />
  <package name="top" extends="struts-default">
    <interceptors>
      <interceptor name="myLogging" class="jp.ymatsukawa.interceptors.Logger" />
      <interceptor-stack name="myStackLogging">
        <interceptor-ref name="myLogging" />
      </interceptor-stack>
    </interceptors>
    <action name="index" class="jp.ymatsukawa.top.Index">
      <result name="success">/index.jsp</result>
    </action>
  </package>
</struts>

actionpackage.interceptors.interceptor-stack#name="mystackLogging"

が参照されることになったので default-interceptor-ref を使い、目的を達成する

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
        "http://struts.apache.org/dtds/struts-2.5.dtd">

<struts>
  <constant name="struts.devMode" value="true" />
  <constant name="struts.ui.theme" value="simple" />
  <package name="top" extends="struts-default">
    <interceptors>
      <interceptor name="myLogging" class="jp.ymatsukawa.interceptors.Logger" />
      <interceptor-stack name="myStackLogging">
        <interceptor-ref name="myLogging" />
      </interceptor-stack>
    </interceptors>
    <default-interceptor-ref name="myStackLogging" />
    <action name="index" class="jp.ymatsukawa.top.Index">
      <result name="success">/index.jsp</result>
    </action>
  </package>
</struts>

interceptors を定義した後に

default-interceptor-ref#name="共通参照したい interceptor-stack" を定義する

action 要素の下には interceptor-ref の記載は入れない

default-interceptor-ref 定義後に
各 action で interceptor-ref を定義した時の挙動

default-interceptor-ref を定義した後、各 action

interceptor-ref を定義するとどうなるか

先に要約すると default-interceptor-ref は実行されず

action で定義された interceptor-ref が実行される

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
        "http://struts.apache.org/dtds/struts-2.5.dtd">

<struts>
  <constant name="struts.devMode" value="true" />
  <constant name="struts.ui.theme" value="simple" />
  <package name="top" extends="struts-default">
    <interceptors>
      <interceptor name="myLogging" class="jp.ymatsukawa.interceptors.Logger" />
      <interceptor name="myOtherLogging" class="jp.ymatsukawa.interceptors.OtherLogger" />
      <interceptor-stack name="myStackLogging">
        <interceptor-ref name="myLogging" />
      </interceptor-stack>
    </interceptors>
    <default-interceptor-ref name="myStackLogging" />
    <action name="index" class="jp.ymatsukawa.top.Index">
      <interceptor-ref name="myOtherLogging" />
      <result name="success">/index.jsp</result>
    </action>
  </package>
</struts>

default-interceptor-ref(myLogging) > action.interceptor-ref(myOtherLogging) が実行されると思いきや

action.interceptor-ref(myOtherLogging) のみ実行される

[参考元]

https://struts.apache.org/docs/how-do-we-configure-an-interceptor-to-be-used-with-every-action.html