Help us understand the problem. What is going on with this article?

読みやすいコードを検証するために比較を書いてみる

More than 1 year has passed since last update.

宣言的にコードを書くと何をしているのかが解りやすい

変更前(叙述的なコード)
const makeResults = (sources) => {
    const results = [];
    for (src of sources) {
        if (!src.id) continue;
        const data = {};
        data.aa = src.aa;
        data.bb = src.bb;
        const items = [];
        for (item of src.items) {
            if (!item.name) continue;
            const data = {
                id: item.id,
                name: item.name,
            };
            items.push(item);
        }
        results.push(data);
    }
    return results;
};
変更後(宣言的なコード)
const makeResults = (sources) => {
    return sources.filter(src => {
        return src.id;
    }).map(src => ({
        aa: src.aa,
        bb: src.bb,
        items: src.items.filter(item => {
            return item.name;
        }).map(item => ({
            id: item.id,
            name: item.name,
        })),
    }));
};
ポイント
  • filter / map を使って、構造を宣言しつつ、変換を行っている。

複雑な条件による割り当てを表現するパターン

if (普通のパターン)

ifを使うパターン
let c = null;
if /*  */ ((productType === 'a' || productType === 'b') && (categoryType === 'y')) {
    c = 'A';
} else if ((productType === 'a' || productType === 'c') && (categoryType === 'z')) {
    c = 'B';
} else {
    c = 'C';
}
ポイント
  • /* */ などを使って条件部分を揃えて、縦方向に見渡せるようにすると可読性があがり、ミスを減らせる。

switch を使うパターン

switchを使うパターン
let c = null;
switch (`${productType},${categoryType}`) {
    case 'a,y':
    case 'b,y':
        c = 'A';
        break;
    case 'a,z':
    case 'c,z':
        c = 'B';
        break;
    default:
        c = 'C';
}
switchを使うパターン(1行で書く)
let c = null;
switch (`${productType},${categoryType}`) {
    case 'a,y': c = 'A'; break;
    case 'b,y': c = 'A'; break;
    case 'a,z': c = 'B'; break;
    case 'c,z': c = 'B'; break;
    default:    c = 'C';
}
ポイント
  • if よりも一覧性を高められる。

連想配列(HashMap)を使うパターン

連想配列を使うパターン
const MAP = {
    'a,y': 'A',
    'b,y': 'A',
    'a,z': 'B',
    'c,z': 'B',
};
const c = MAP[`${productType},${categoryType}`] || 'C';
連想配列を使うパターン(余分な変数を使わない)
const c = {
    'a,y': 'A',
    'b,y': 'A',
    'a,z': 'B',
    'c,z': 'B',
}[`${productType},${categoryType}`] || 'C';
ポイント
  • switch よりもさらに一覧性を高められる。
  • 連想配列の定義だけ別ファイルに切り出すことも簡単。

分岐を減らすワザ (オブジェクト指向 - Stateパターン)

全く同じ処理なのに、分岐の数を減らせる場合がある。
分岐が減れば可読性が上がり、バグが入る可能性を減らせる。
同じ処理ができるなら分岐が減る方が良いのである。

オブジェクト指向の考え方がこれを助けてくれる。

例えば下記のデータで、ユーザごとに A → B の順に出現する回数を数えたいとする。

前提
const records = [
    {user: '123', type: 'A'},
    {user: '123', type: 'A'},
    {user: '789', type: 'B'},
    {user: '456', type: 'A'},
    {user: '123', type: 'B'},
    {user: '789', type: 'A'},
    {user: '456', type: 'B'},
    {user: '123', type: 'A'},
    {user: '456', type: 'A'},
];
// ユーザごとに A → B の順に出現する回数を数える
愚直な実装
const states = {
    '123': {state: 'WAIT_A', count: 0},
    '456': {state: 'WAIT_A', count: 0},
    '789': {state: 'WAIT_A', count: 0},
}
for (const rec of records) {
    const state = states[rec.user];
    if (state.state === 'WAIT_A') {
        if (rec.type === 'A') {
            state.state = 'B';
        }
    } else if (state.state === 'WAIT_B') {
        if (rec.type === 'B') {
            count++;
            state.state = 'A';
        }
    }
}
console.log(`123: ${states['123'].count}`);
console.log(`456: ${states['456'].count}`);
console.log(`789: ${states['789'].count}`);
ステートパターンでの実装
class AbState {
    constructor() {
        this.state = this.STATE_A;
        this.count = 0;
    }
    STATE_A(rec) {
        if (rec.type === 'A') {
            this.state = STATE_B;
        }
    }
    STATE_B(rec) {
        if (rec.type === 'B') {
            this.count++;
            this.state = STATE_A;
        }
    }
}
const states = {
    '123': new AbState(),
    '456': new AbState(),
    '789': new AbState(),
};
for (const rec of records) {
    const state = states[rec.user];
    state.state(rec);
}
console.log(`123: ${states['123'].count}`);
console.log(`456: ${states['456'].count}`);
console.log(`789: ${states['789'].count}`);
ポイント
  • 分岐が減ったのは、ステートとステートに依存する処理を1つのオブジェクトにまとめたから。
  • このように依存している変数と処理をセットでオブジェクトにまとめることで分岐を減らし、可読性とメンテナンス性を高める。

分岐を減らすワザ (オブジェクト指向 - Strategyパターン)

もう一例、もっと解りやすい類似のケース。

愚直にやると
let type = null;
if (a === 'A' && b === '1') {
    type = 'A';
} else {
    type = 'B';
}

// 処理1
switch (type) {
    case 'A':
        // 処理1
        break;
    case 'B':
        // 処理1
        break;
}

// 処理2
switch (type) {
    case 'A':
        // 処理2
        break;
    case 'B':
        // 処理2
        break;
}
Strategyパターンを使う
class StrategyA {
    exec1() {
        // 処理1
    }
    exec2() {
        // 処理2
    }
}
class StrategyB {
    exec1() {
        // 処理1
    }
    exec2() {
        // 処理2
    }
}

let type = null;
if (a === 'A' && b === '1') {
    type = new StrategyA();
} else {
    type = new StrategyB();
}

// 処理1
type.exec1();

// 処理2
type.exec2();
ポイント
  • 分岐が減ったのは、前の例と同様に type に依存する処理を依存の塊ごとにオブジェクトにまとめたから。
  • 依存の塊でオブジェクトにまとめ、それに適切な名前をつけるのがキモ。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした