Switch true ลาก่อนบุกป่าฝ่าดง If
11 Jul 2023 13:00
Written by: Yosapol Jitrak
ปกติเวลาเราเขียนเงื่อนไขต่าง ๆ เรามักจะใช้ if-else กันเป็นปกติ ยกตัวอย่างเป็นโปรแกรมตัดเกรดนักศึกษา
enum Grade {
A = 'A',
B = 'B',
C = 'C',
D = 'D',
F = 'F',
}
interface Student {
id: number;
name: string;
score: number;
grade?: string;
}
const students: Student[] = [
{ id: 1, name: 'Alice', score: 85 },
{ id: 2, name: 'Bob', score: 72 },
{ id: 3, name: 'Charlie', score: 56 },
{ id: 4, name: 'David', score: 65 },
{ id: 5, name: 'Eve', score: 49 },
];
students.forEach((student) => {
if (student.score >= 80) {
student.grade = Grade.A;
} else if (student.score >= 70) {
student.grade = Grade.B;
} else if (student.score >= 60) {
student.grade = Grade.C;
} else if (student.score >= 50) {
student.grade = Grade.D;
} else {
student.grade = Grade.F;
}
});
console.table(students);
ผลลัพท์
คราวนี้เราจะมีความรู้สึกว่า มันควรจะเขียนเป็น Switch case ได้หรือเปล่านะ สำหรับโจทย์นี้
students.forEach((student) => {
switch (Math.floor(student.score / 10)) {
case 10:
case 9:
case 8:
student.grade = Grade.A;
break;
case 7:
student.grade = Grade.B;
break;
case 6:
student.grade = Grade.C;
break;
case 5:
student.grade = Grade.D;
break;
default:
student.grade = Grade.F;
break;
}
});
console.table(students);
ก็ดูโอเคขึ้น จะมีขัดใจตรง case 10, 9 และ 8
เรามาลองดูตัวอย่าง Switch true กันครับ
students.forEach((student) => {
switch (true) {
case student.score >= 80:
student.grade = Grade.A;
break;
case student.score >= 70:
student.grade = Grade.B;
break;
case student.score >= 60:
student.grade = Grade.C;
break;
case student.score >= 50:
student.grade = Grade.D;
break;
default:
student.grade = Grade.F;
break;
}
});
console.table(students);
ท่านี้ยังไงก็สวยกว่าการใช้ if-else และไม่ต้องเขียน case 10, 9 ที่ไม่จำเป็น ในกรณีที่เป็น switch case ธรรมดา รวมถึงมีความชัดเจนในตัวที่มากกว่า
ถ้าโจทย์ของจริง มันอาจจะไม่ได้ง่ายแบบนี้ หลายครั้งเราไม่สามารถใช้เงื่อนไขเดียวในการเช็คได้แบบนี้ ต้องมี and หรือ or ด้วย
ยกตัวอย่างเป็นโจทย์ชนะทางแพ้ทางของเกม
graph LR
water --> |2| fire
fire --> |2| grass
grass --> |2| earth
earth --> |2| electric
electric --> |2| water
dark --> |1| light
dark --> |1.2| water
dark --> |1.2| fire
dark --> |1.2| grass
dark --> |1.2| earth
dark --> |1.2| electric
light --> |3| dark
light --> |0.9| water
light --> |0.9| fire
light --> |0.9| grass
light --> |0.9| earth
light --> |0.9| electric
ยกตัวอย่างข้อมูลแถวแรก คือ น้ำชนะไฟ Damage จะคูณ 2 การเขียนด้วย if-else ทำได้แน่นอน แต่มันจะรกมาก
export const allElements = ['normal', 'water', 'fire', 'grass', 'earth', 'electric', 'light', 'dark'] as const;
export type ElementType = (typeof allElements)[number];
export type CalculateMultiplierInput = [ElementType, ElementType];
// water -> fire -> grass -> earth -> electric -> water | 2
// dark -> every | 1.2
// light -> dark | 3
// light -> not dark | 0.9
export const calculateDamageMultiplier = (input: CalculateMultiplierInput) => {
let multiplier: number;
if (input[0] === 'water' && input[1] === 'fire') {
multiplier = 2;
} else if (input[0] === 'fire' && input[1] === 'grass') {
multiplier = 2;
} else if (input[0] === 'grass' && input[1] === 'earth') {
multiplier = 2;
} else if (input[0] === 'earth' && input[1] === 'electric') {
multiplier = 2;
} else if (input[0] === 'electric' && input[1] === 'water') {
multiplier = 2;
} else if (input[0] === 'dark') {
multiplier = 1.2;
} else if (input[0] === 'light' && input[1] === 'dark') {
multiplier = 3;
} else if (input[0] === 'light') {
multiplier = 0.9;
} else {
multiplier = 1;
}
return multiplier;
};
คราวนี้ลองมาดู Version switch true กันบ้างครับ
export const calculateDamageMultiplier = (input: CalculateMultiplierInput) => {
let multiplier: number;
switch (true) {
case input[0] === 'water' && input[1] === 'fire':
multiplier = 2;
break;
case input[0] === 'fire' && input[1] === 'grass':
multiplier = 2;
break;
case input[0] === 'grass' && input[1] === 'earth':
multiplier = 2;
break;
case input[0] === 'earth' && input[1] === 'electric':
multiplier = 2;
break;
case input[0] === 'electric' && input[1] === 'water':
multiplier = 2;
break;
case input[0] === 'dark':
multiplier = 1.2;
break;
case input[0] === 'light' && input[1] === 'dark':
multiplier = 3;
break;
case input[0] === 'light':
multiplier = 0.9;
break;
default:
multiplier = 1;
break;
}
return multiplier;
};
จาก Code ชุดนี้ จะเห็นได้ว่า Code มีความอ่านง่ายมากกว่า if-else แบบก่อนหน้านี้ และช่วยลด Human error ได้
สำคัญ อย่าลืมเขียน Unit test ด้วยนะครับ
import { calculateDamageMultiplier, CalculateMultiplierInput, ElementType } from './element';
describe('calculateDamageMultiplier', () => {
test.each<CalculateMultiplierInput>([
['normal', 'normal'],
['fire', 'fire'],
['fire', 'electric'],
])(
'should be 1 when attackerElement and defenderElement is not match any case.',
(attackerElement, defenderElement) => {
// Arrange
const input: CalculateMultiplierInput = [attackerElement, defenderElement];
const expectedDamageMultiplier = 1;
// Act
const damageMultiplier = calculateDamageMultiplier(input);
// Assert
expect(damageMultiplier).toEqual(expectedDamageMultiplier);
},
);
test('should be 2 when attackerElement is water and defenderElement is fire.', () => {
// Arrange
const attackerElement: ElementType = 'water';
const defenderElement: ElementType = 'fire';
const input: CalculateMultiplierInput = [attackerElement, defenderElement];
const expectedDamageMultiplier = 2;
// Act
const damageMultiplier = calculateDamageMultiplier(input);
// Assert
expect(damageMultiplier).toEqual(expectedDamageMultiplier);
});
test('should be 2 when attackerElement is fire and defenderElement is grass.', () => {
// Arrange
const attackerElement: ElementType = 'fire';
const defenderElement: ElementType = 'grass';
const input: CalculateMultiplierInput = [attackerElement, defenderElement];
const expectedDamageMultiplier = 2;
// Act
const damageMultiplier = calculateDamageMultiplier(input);
// Assert
expect(damageMultiplier).toEqual(expectedDamageMultiplier);
});
test('should be 2 when attackerElement is grass and defenderElement is earth.', () => {
// Arrange
const attackerElement: ElementType = 'grass';
const defenderElement: ElementType = 'earth';
const input: CalculateMultiplierInput = [attackerElement, defenderElement];
const expectedDamageMultiplier = 2;
// Act
const damageMultiplier = calculateDamageMultiplier(input);
// Assert
expect(damageMultiplier).toEqual(expectedDamageMultiplier);
});
test('should be 2 when attackerElement is earth and defenderElement is electric.', () => {
// Arrange
const attackerElement: ElementType = 'earth';
const defenderElement: ElementType = 'electric';
const input: CalculateMultiplierInput = [attackerElement, defenderElement];
const expectedDamageMultiplier = 2;
// Act
const damageMultiplier = calculateDamageMultiplier(input);
// Assert
expect(damageMultiplier).toEqual(expectedDamageMultiplier);
});
test('should be 2 when attackerElement is electric and defenderElement is water.', () => {
// Arrange
const attackerElement: ElementType = 'electric';
const defenderElement: ElementType = 'water';
const input: CalculateMultiplierInput = [attackerElement, defenderElement];
const expectedDamageMultiplier = 2;
// Act
const damageMultiplier = calculateDamageMultiplier(input);
// Assert
expect(damageMultiplier).toEqual(expectedDamageMultiplier);
});
test.each<ElementType>(['normal', 'water', 'fire', 'grass', 'electric'])(
'should be 1.2 when attackerElement is dark and defenderElement is basic element.',
(defenderElement) => {
// Arrange
const attackerElement: ElementType = 'dark';
const input: CalculateMultiplierInput = [attackerElement, defenderElement];
const expectedDamageMultiplier = 1.2;
// Act
const damageMultiplier = calculateDamageMultiplier(input);
// Assert
expect(damageMultiplier).toEqual(expectedDamageMultiplier);
},
);
test('should be 3 when attackerElement is light and defenderElement is dark.', () => {
// Arrange
const attackerElement: ElementType = 'light';
const defenderElement: ElementType = 'dark';
const input: CalculateMultiplierInput = [attackerElement, defenderElement];
const expectedDamageMultiplier = 3;
// Act
const damageMultiplier = calculateDamageMultiplier(input);
// Assert
expect(damageMultiplier).toEqual(expectedDamageMultiplier);
});
test.each<ElementType>(['normal', 'water', 'fire', 'grass', 'electric'])(
'should be 0.9 when attackerElement is light and defenderElement is basic element.',
(defenderElement) => {
// Arrange
const attackerElement: ElementType = 'light';
const input: CalculateMultiplierInput = [attackerElement, defenderElement];
const expectedDamageMultiplier = 0.9;
// Act
const damageMultiplier = calculateDamageMultiplier(input);
// Assert
expect(damageMultiplier).toEqual(expectedDamageMultiplier);
},
);
});
จริง ๆ เอา Code ชุดนี้ผมก็ยังไม่พอใจหรอกนะครับ
มันมี Tool ที่จะมาช่วยให้มันเขียนได้สะอาดกว่านี้ได้ ซึ่งจะมาพูดถึงกันในบทความถัดไปครับ ts-pattern
ลองดูตัวอย่างที่กล่าวถึง Switch true ได้จาก:
Using the Switch(true) Pattern in JavaScript
functional-bowling-ts