JavaScript

JavaScriptの関数で引数の必須チェックする方法

JavaScriptにおける関数の引数のチェックについての考察。
まずは必須チェックを。

必須のチェック

required_01.js
// 必須チェック関数
function required(key) {
    throw new Error(`${key}は必須です。`);
}

// とある関数
function sayHello(name=required("名前")) {
    console.log(`ハロー${name}`)
}

try {
    sayHello("JavaScript");     // ハローJavaScript
    sayHello();
}
catch(e) {
    console.log(e.message);     // 名前は必須です。
};

カスタムErrorを投げた方が良さげなので、書きなおしてみる。

required_02.js
// RequiredErrorというカスタムエラーを作る
function RequiredError(message="必須エラー") {
    this.message = message;
    let last_part = new Error().stack.match(/[^\s]+$/);
    this.stack = `${this.name} at ${last_part}`;

}
Object.setPrototypeOf(RequiredError, Error);
RequiredError.prototype = Object.create(Error.prototype);
RequiredError.prototype.name = "RequiredError";
RequiredError.prototype.message = "";
RequiredError.prototype.constructor = RequiredError;

// 必須チェック関数(改:RequiredErrorを投げる)
function required(key) {
    //throw new Error(`${key}は必須です。`);
    throw new RequiredError(`${key}は必須です。`);
}

try {
    sayHello();
}
catch(e) {
    if ( e instanceof RequiredError ) {
        console.log(e.message);     // 名前は必須です。
    }
};

書いてはみたものの問題が浮上。

この方法は、引数のデフォルト値を利用してrequired()を呼び出しているので、
例えば型のチェックなどは、このタイミングでは出来ない。

なので、
他の引数チェック系関数を作ったとき、その使い方に統一性が失われてしまう。

また何より、必須の引数が3つもあると、もう見辛い。

素直に下のような書き方のが良いのかもしれない。

required_03.js
// 必須チェック関数
function required(val) {
    if ( valの必須チェック ) {
        throw new RequiredError();
    }
    return val;
}

// とある関数
//function sayHello(name=required("名前")) {
function sayHello(name) {
    name = required(name);
    console.log(`ハロー${name}`)
}

引数の明示と同時に、
型や必須、デフォ値の面倒みてくれるような書き方が望ましいんだけどねぇ。

型のチェックって意外に難しいのでオレオレじゃなくて、
偉い人が作ってくれたようなの、ないの?

ん、typescript使え?

引数をチェックするクラス(追記)

コメントを受けて、引数のチェッククラス(案)を書いてみました。

args-check.js
class ArgsCheck {
    get val() { return this._val; }
    constructor(val, name="") {
        this._val = val;
        this.name = name;
    }
    required() {
        if ( !this.val )
            throw Error(`エラー:${this.name} 必須です。`);
        return this;
    }
    isString() {
        if ( typeof(this.val) != "string" )
            throw Error(`エラー:${this.name} stringじゃないよ。`);
        return this;
    }
    length(...size) {
        if ( size.length == 1 ) {
            let max = size[0];
            if ( this.val.length > max )
                throw Error(`エラー:${this.name} 文字の長さは${max}までです。`);
        }
        else if ( size.length == 2 ) {
            let min = size[0];
            let max = size[1];
            if ( this.val.length < min )
                throw Error(`エラー:${this.name} 文字の長さは${min}以上です。`);
            if ( this.val.length > max )
                throw Error(`エラー:${this.name} 文字の長さは${max}までです。`);
        }
        else {
            // スルーするか、エラーか警告出すか
        }
        return this;
    }
}

// とある関数
function sayHello(name) {
    name = new ArgsCheck(name, "名前").required().isString().length(10,20).val;
    console.log(`ハロー${name}`)
}

try {
    sayHello("JavaScript");     // ハローJavaScript
    sayHello("Perl");
}
catch(e) {
    console.log(e.message);     // エラー:名前 文字の長さは10以上です。
};

チェック用のメソッドを育てていけば、まともになるのかなぁ。
も少し理想を言うと、下記のような定義を用意しておいて、
内部で、上で書いたチェック関数を使うような仕組みが望ましいですかね。

let rule = {
    name : {required:1, type:"string", length:[10,20]},
};