早期returnという考え方

早期return サンプルコードプログラミング
この記事は約6分で読めます。

「早期return」とは、条件が合わない場合などはreturnでただちに関数から抜けてしまえ、という手法のことです。関数内のネストを減らせるため、コードの可読性を向上させることができると言われています。

今回の記事では値を検証する関数を題材に早期returnの解説を行います。
検証関数の返り値についてはさまざまな意見があると思いますが、この記事では問題があればfalseを返すという設計にしました。

/**
 * @typedef {Object} User
 * @property {number} id
 * @property {string} name
 */

関数内で検証しているUserオブジェクトの型情報です。プロパティとしてnumber型のidとstring型のnameを持っています。

このUserオブジェクトを検証する関数を、早期return(とネストの深さ)を意識せずに書いてみました。

/**
 * @param {User} user 
 */
const validateUser = (user) => {
    if (user) {
        // userがundefinedではない
        if (user.id) {
            // user.idがundefinedではない
            if (user.id >= 1) {
                // user.idが1以上

                // ...その他の判定や処理
                return true;
            }
        }
    }
    return false;
}

console.log(validateUser()) // false
console.log(validateUser({})) // false
console.log(validateUser({id: -1})) // false
console.log(validateUser({id: 1})) // true

関数内1つ目の条件分岐ではTypeError対策に引数そのものを判定しています。判定がtrueとして評価された場合は1段階ネストが深くなり、2つ目の条件分岐が実行されます。
2つ目の条件分岐もtrueとして評価された場合は、さらにネストが深くなります。
説明のために少々大げさな書き方となってしまいましたが、このような書き方をした結果、条件分岐の度にネストが深くなってしまいました。

今回の関数では引数そのものとidの検証しか行っていませんが、今後nameなどを検証するコードを追加していくと、ネストはより深くなっていきます。

このネスト地獄を解消するために、早期returnの考え方を使って書き直してみます。

/**
 * @param {User} user 
 */
const validateUser2 = (user) => {
    if (!user) return false;

    if (!user.id) return false;
    if (user.id <= 0) return false;

    if (!user.name) return false;
    if (user.name.length < 3) return false;

    // ...その他の判定や処理

    return true;
}

console.log(validateUser2()) // false
console.log(validateUser2({})) // false
console.log(validateUser2({id: -1})) // false
console.log(validateUser2({id: 1})) // false
console.log(validateUser2({id: 1, name: "na"})) // false
console.log(validateUser2({id: 1, name: "name"})) // true

1つ目の条件分岐で引数そのものを判定していることは同様ですが、判定している条件が異なっています。前回の関数ではuserに問題がなければ判定が通るようにしていましたが、今回は問題がある場合に判定が通るようになっています。そして判定が通る場合はfalseを返却しています。
2つ目、3つ目の判定条件も以前とは異なり、問題がある場合は判定が通り、通った場合は即returnしています。

これが早期returnの考え方を利用した場合の書き方になります。
行っていることは判定条件の入れ替えと複数回returnのみです。たったこれだけの修正でネストが浅くなり、関数も読みやすくなりました。

早期returnの内容からは少しずれてしまいますが、この関数をもう少しだけ改良してみます。

/**
 * @param {User} user 
 */
const validateUser3 = (user) => {
    if (!user) return false;
    if (!validateUserId(user.id)) return false;
    if (!validateUserName(user.name)) return false;

    // ...その他の判定や処理

    return true;
}

const validateUserId = (id) => {
    if (!id) return false;
    if (id <= 0) return false;

    return true;
}

const validateUserName = (name) => {
    if (!name) return false;
    if (name.length < 3) return false;

    return true
}

console.log(validateUser3()) // false
console.log(validateUser3({})) // false
console.log(validateUser3({id: -1})) // false
console.log(validateUser3({id: 1})) // false
console.log(validateUser3({id: 1, name: "na"})) // false
console.log(validateUser3({id: 1, name: "name"})) // true

プロパティごとの条件判定部分を、別の関数として抽出してみました。抽出したそれぞれの関数内でも早期returnを使ってネストが浅くなるようにしています。
関数が増えすぎてしまうことへの賛否はあると思いますが、初期のvalidateUser関数と比べてかなり読みやすくなったと思います。

早期returnは判定条件を工夫するだけで簡単に行えるので、ネストの深さに困っている場合はぜひ利用してみてください。

タイトルとURLをコピーしました