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