LoginSignup
21
16

More than 5 years have passed since last update.

バリデーターのあるべき論

Posted at

はじめに

最近「これはこうあるべきだ」というべき論を考えることが多く、バリデーターについても改めて考える機会があったのでまとめることにした。

べき論

バリデーターはシンプルに「検証すべき要素がバリデーションに通るか通らないか(true or false)を返すべきだ」
その際に「もしfalseの場合には何がダメだったのかの失敗原因も返すべきだ」
true/falseしか返してはいけないのにエラーメッセージも返さなければならないというのが「べき論」としてすでに矛盾している。
これをうまく回避する方法を以下のサンプルコードで示す。

サンプルコード

今回は例として「0より大きい数字ならバリデーションtrue, 0以下ならfalse」とした。

case1

case1.php
<?php
function isValid($value){
    if($value > 0){
        return array(
            'result'  => true,
            'message' => '',
        );
    } else {
        return array(
            'result'  => false,
            'message' => 'バリデート失敗',
        );
    }
}

$test1 = 0;
$test2 = 1;

var_dump(isValid($test1));
var_dump(isValid($test2));

結果

array(2) {
  ["result"]=>
  bool(false)
  ["message"]=>
  string(21) "バリデート失敗"
}
array(2) {
  ["result"]=>
  bool(true)
  ["message"]=>
  string(0) ""
}

愚直に値2つを配列として返している。
これは「べき論」で言うとtrue, false以外も返しているところが反している。
また扱いも複雑になり配列の要素(今回の例だとresult, message)にアクセスしなければならない。

case2

case2.php
<?php
function isValid($value, &$message){
    if($value > 0){
        $message = '';
        return true;
    } else {
        $message = 'バリデート失敗';
        return false;
    }
}

$test1 = 0;
$test2 = 1;

$message1 = '';
$message2 = '';

var_dump(isValid($test1, $message1));
var_dump($message1);
var_dump(isValid($test2, $message2));
var_dump($message2);

結果

bool(false)
string(21) "バリデート失敗"
bool(true)
string(0) ""

こちらは第二引数に参照渡しでメッセージ格納用の変数を予め渡しておき、その中にメッセージを入れておくという方法である。
「べき論」でいうと矛盾しておらずそこそこ有用そうに見える。
しかし検証すべき要素以外のものをバリデーターに渡さなければならず、かつ参照渡しなのでいつどこでメッセージの内容が書き換わるか分からずに副作用も強い。
例えばisValid関数を呼び出したあとにmessageを表示させるまでに間にmessage変数を上書きすることが可能である点や、
上記のようにmessage1, message2のように変数を分けずに使いまわした場合に「どのタイミングでのメッセージなのか?」という状態が発生する。
有用だが十分に気を付けて使う必要がある。

case3

case3.php
<?php
class Validator
{
    private $message;

    public function __construct(){
        $this->message = '';
    }

    public function isValid($value){
        if($value > 0){
            $this->message = '';
            return true;
        } else {
            $this->message = 'バリデート失敗';
            return false;
        }
    }

    public function getMessage(){
        return $this->message;
    }
}

$test1 = 0;
$test2 = 1;
$validator = new Validator();

$result1  = $validator->isValid($test1);
$message1 = $validator->getMessage();
var_dump($result1, $message1);

$result2  = $validator->isValid($test2);
$message2 = $validator->getMessage();
var_dump($result2, $message2);

結果

bool(false)
string(21) "バリデート失敗"
bool(true)
string(0) ""

多分これが1番良いと思います()
バリデーターを1つにクラスにしてしまえばプロパティにメッセージを持つことができるので、バリデーションと同時にメッセージを格納、欲しい時にgetする。
プロパティはprivateにしておけば上書きされる心配はない。
バリデーターのインスタンスを別途立てることで状態を持つことも回避する。

おわりに

今回バリデーターの「あるべき論」について考え、ケースを3つ挙げました。
私の結論で言うとケース3が今のところ良いかなと思っていますが、これがベストかどうかは正直分かりません。
日々より良い方法を模索していきたいですね。

おまけ

仕事でphpを書く機会が多いのでphpで書きましたが、個人的にはrubyが好きなのでrubyで上記3パターンを書いたものも置いておきます。
何かコードを書く際に複数言語で書けるとかっこいいですね:smile:

case1

case1.rb
def isValid(value)
  if value > 0
    return {result: true, message: ''}
  else
    return {result: false, message: 'バリデート失敗'}
  end
end

test1 = 0
test2 = 1

p isValid(test1)
p isValid(test2)

結果

{:result=>false, :message=>"バリデート失敗"}
{:result=>true, :message=>""}

phpの連想配列はrubyではHashで表現した。

case2

case2.rb
def isValid(arr)
  if arr[0] > 0
    arr[1] = ''
    return true
  else
    arr[1] = 'バリデート失敗'
    return false
  end
end

test1 = 0
test2 = 1

message1 = ''
message2 = ''

arr1 = [test1, message1]
arr2 = [test2, message2]

p isValid(arr1)
p arr1[1]
p isValid(arr2)
p arr2[1]

結果

false
"バリデート失敗"
true
""

rubyでは参照渡しというものがないので上記ケース2をrubyで表現するのは難しかった。
配列を渡すことで擬似的に参照渡しを表現した。

case3

case3.rb
class Validator
    def isValid(value)
      if value > 0
        @message = '';
        return true
      else
        @message = 'バリデート失敗';
        return false
      end
    end

    def getMessage
        @message
    end
end

test1 = 0
test2 = 1
validator = Validator.new

p validator.isValid(test1)
p validator.getMessage
p validator.isValid(test2)
p validator.getMessage

結果

false
"バリデート失敗"
true
""

rubyで書いてもやっぱりケース3が1番しっくりくる。
rubyのプロパティはデフォルトでprivateなのでより安全。

21
16
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
16