宣言的にコードを書くと何をしているのかが解りやすい
変更前(叙述的なコード)
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 に依存する処理を依存の塊ごとにオブジェクトにまとめたから。
- 依存の塊でオブジェクトにまとめ、それに適切な名前をつけるのがキモ。