同じ種類の関数は返り値の型を揃える
予測可能性
API を叩くのと関連した Hook のように同じ種類の関数やHookがお互い違うタイプの返り値を持っていると、コードの一貫性が損なわれ、一緒に働くチームメンバーがコードを読むのが困難になります。
📝 コード例 1: useUser
次のuseUser
とuseServerTime
HookはすべてAPIを叩くのと関連したHookです。
しかしuseUser
は@tanstack/react-query
のQuery
オブジェクトを返し、useServerTime
はサーバー時間を持ってきてデータだけを返しています。
typescript
import { useQuery } from '@tanstack/react-query';
function useUser() {
const query = useQuery({
queryKey: ['user'],
queryFn: () => fetchUser(),
});
return query;
}
function useServerTime() {
const query = useQuery({
queryKey: ['serverTime'],
queryFn: () => fetchServerTime(),
});
return query.data;
}
👃 コードの不吉な臭いを嗅いでみる
予測可能性
サーバーのAPIを叩くHookの返り値のタイプがお互いに違うと、チームメンバーはこのようなHookを使うたびに返り値が何なのか確認しないといけません。Query
オブジェクトを返すと、data
を取り出す必要があり、データだけを返してあげればそのまま値を使えますよね。
同じように動くコードが一貫性を持って規則的でないとコードを読むのが難しくなります。
✏️ リファクタリングしてみる
次のようにサーバーのAPIを叩くHookは一貫性を持たせてQuery
オブジェクトを返してあげるようにすれば、チームメンバーがコードを予測できる可能性が高まります。
typescript
import { useQuery } from '@tanstack/react-query';
function useUser() {
const query = useQuery({
queryKey: ['user'],
queryFn: () => fetchUser(),
});
return query;
}
function useServerTime() {
const query = useQuery({
queryKey: ['serverTime'],
queryFn: () => fetchServerTime(),
});
return query;
}
📝 コード例 2: checkIsValid
次のcheckIsNameValid
とcheckIsAgeValid
はすべての名前と年齢が正しいか検証する関数です。
typescript
/** ユーザーネームは20字未満でないといけません。 */
function checkIsNameValid(name: string) {
const isValid = name.length > 0 && name.length < 20;
return isValid;
}
/** ユーザーは年齢が18歳以上99歳次の自然数でないといけません。 */
function checkIsAgeValid(age: number) {
if (!Number.isInteger(age)) {
return {
ok: false,
reason: "年齢は整数でないといけません。"
};
}
if (age < 18) {
return {
ok: false,
reason: "年齢は18歳以上でないといけません。"
};
}
if (age > 99) {
return {
ok: false,
reason: "年齢は99歳以下でないといけません。"
};
}
return { ok: true };
}
👃 コードの不吉な臭いを嗅いでみる
予測可能性
検証関数の返り値が違うと、チームメンバーは関数を使うたびに返り値を確認する必要があり、コードリーディングが難しくなってしまいます。
特に厳密なブールの比較のような機能を使わない場合、コードにバグが見つかる原因になっていまいます。
typescript
// このコードは名前が規則を守っているか検証します。
if (checkIsNameValid(name)) {
// ...
}
// この関数はいつもオブジェクト { ok, ... }を返すので、
// `if`文内にあるコードはいつも信用できます
if (checkIsAgeValid(age)) {
// ...
}
✏️ リファクタリングしてみる
次のコードのように検証関数が一貫的に{ ok, ... }
タイプのオブジェクトを返すようにすることができます。
typescript
/** ユーザーの名前は20字未満でないといけません */
function checkIsNameValid(name: string) {
if (name.length === 0) {
return {
ok: false,
reason: "名前は空の値ではいけません。"
};
}
if (name.length > 20) {
return {
ok: false,
reason: "名前は20字まで入力できます。"
};
}
return { ok: true };
}
/** ユーザーは年齢が18歳以上、99歳次の自然数でなければなりません */
function checkIsAgeValid(age: number) {
if (!Number.isInteger(age)) {
return {
ok: false,
reason: "年齢は整数でないといけません。"
};
}
if (age < 18) {
return {
ok: false,
reason: "年齢は18歳以上でないといけません。"
};
}
if (age > 99) {
return {
ok: false,
reason: "年齢は99歳以下でないといけません。"
};
}
return { ok: true };
}