มารู้จักการเขียน code แบบ clean code กัน (ฉบับ Javascript)

/ 10 min read

Share on social media

clean-code-js สามารถดู video ของหัวข้อนี้ก่อนได้ ดู video

Clean code คืออะไร ?

”เราทุกคนล้วนไม่ชอบบ้านรกๆครับ code ก็เช่นกัน”

การ clean code คือการเขียน code ให้ง่ายต่อการอ่าน ทำความเข้าใจและดูแลรักษา (maintain) ได้ง่ายขึ้น โดย concept ของ clean code โดยทั่วไปจะไม่ได้พูดถึงแค่การทำให้ code ดูดีอย่างเดียว แต่จะพูดถึงการทำให้ code มีประสิทธิภาพที่ดีขึ้นด้วย และ ส่งผลทำให้เกิด Error ลดลงได้ด้วย (จากการ implement code ให้ถูก Practice)

ในบทความนี้เราจะเล่าการทำ Clean code ผ่านภาษา Javascript กัน เนื่องจากเป็นภาษาที่มีผู้ใช้ใน github “มากที่สุด” ในโลก (จากช่วงปี 2023 ที่ผ่านมา)

มีวิธีไหนที่สามารถทำให้ code clean ขึ้นได้บ้าง

สิ่งนี้เป็นการรวบรวมจากประสบการณ์ผมเอง + จากการอ่านข้อมูลหลาๆแหล่งข้อมูลมาใช้ร่วมกัน โดยเราจะไล่ระดับตั้งแต่เรื่องง่ายๆ ไปจนถึงเรื่องยากๆกัน (ไม่ได้ยากในแง่การทำ แต่ยากในแง่การคิดว่าจะต้องทำสิ่งนั้นตอนไหน)

เพื่อให้เห็นภาพเราจะยกตัวอย่าง code มาเทียบกัน 2 ชุดมาเทียบกัน ว่ามีความแตกต่างกันอย่างไร โดยในบทความนี้เราจะเรียกว่า BAD คือ code ที่ไม่ตรงตาม practice ของ clean code และ GOOD คือ code ที่ตรงตาม practice ของ clean code ครับ

และ ที่สำคัญ “เรื่องนี้ไม่มีคำตอบตายตัว” ทั้งนี้อาจจะขึ้นอยู่กับทีมที่ร่วมงานและอาจจะเปลี่ยนแปลงได้ตามยุคสมัยเช่นเดียวกัน (เช่น ตัวภาษาอาจจะมีการ update บางอย่างเพิ่มเติมส่งผลทำให้การเขียนบางคำสั่งสามารถเขียนได้ง่ายขึ้น เป็นต้น)

โดยประเด็นที่เราจะหยิบมาเล่าในบทความนี้ เราจะแยกออกเป็นประเด็นตามนี้คือ

  1. Variable (ตัวแปร)
  2. Functions
  3. Concurrency (callback, promise, async-await)
  4. Error Handling
  5. อื่นๆ (Comment, Formating, Principle อื่นๆ)

ย้ำไว้ก่อนว่า ที่เขียนในนี้ยังไม่ใช่ทั้งหมดของ Clean code นะครับ จริงๆ Practice ของ clean code มันเยอะมาก (มากๆเลยแหละ) แต่ผมจะหยิบตัวที่ผมรู้สึกว่า “ควร” หรือ “ต้องทำ” จริงๆ เพื่อให้ code ออกมาเรียบร้อยมากขึ้นนะครับ (เรื่องที่อยู่ในบทความนี้จะอยู่ในระดับพื้นฐานที่ควรทำเสมอ ไม่ว่า code จะเป็น style functional หรือ OOP ก็ตามนะครับ)

1. Variable

ตั้งชื่อตัวแปรให้มีความหมาย

ใช้ชื่อตัวแปรที่สื่อโดยตรงว่ากำลังเก็บอะไรไว้อยู่ (ใช้คำที่มีความหมายจริง)

BAD:

let tmrw = new Date(); // ไม่ชัดเจนว่าคืออะไร ใช้ทำอะไร
let data = 'John Doe'; // data ของอะไร
let number = 42; // ตัวเลขของอะไร

GOOD:

let currentDate = new Date(); // ชัดเจนว่าวันปัจจุบัน
let userName = 'JohnDoe'; // ชัดเจนว่าเก็บ user name
let maxScore = 42; // ชัดเจนว่าใช้สำหรับเก็บ score

รวมกลุ่มศัพท์ประเภทเดียวกัน

เป็นการกันสับสนว่าข้อมูลเป็นประเภทเดียวกันหรือไม่ ?

BAD:

// อ่อ user setting สักจุด
let userSettings = { theme: 'dark', language: 'English' };
// อ่อ เกี่ยวกับ theme user แต่ใช้ตรง setting ใช่ไหม ?
let userProfilePreferences = { theme: 'light', fontSize: 'medium' };
// เอ๊ะ member กับ user นี่เหมือนกันหรือป่าว ?
let memberOptions = { language: 'French', notifications: true };

GOOD:

// ชัดเจนว่าทั้ง 3 อันใช้ที่ Setting เหมือนกัน
let userSettings = { theme: 'dark', language: 'English' };
let displaySettings = { theme: 'light', fontSize: 'medium' };
let notificationSettings = { email: true, sms: false };

ใช้ชื่อที่สามารถค้นหาได้และอ่านได้

อย่าใช้ตัวแปร (เช่น ตัวแปรตัวเดียว) หรือค่าคงที่ “ที่อธิบายไม่ได้” วางไว้ใน code

BAD:

setTimeout(updateFunction, 86400000); // 86400000 คืออะไร ?

GOOD:

// อ่อ นี่คือจำนวน miliseconds ต่อวัน
const MILLISECONDS_PER_DAY = 60 * 60 * 24 * 1000; //86400000;
setTimeout(blastOff, MILLISECONDS_PER_DAY);

เลี่ยงใช้ตัวย่อ

พยายามใช้คำเต็ม เพื่อกันสับสนว่ากำลังสื่อถึงอะไรอยู่

BAD:

let loc = ['Austin', 'New York', 'San Francisco']; // loc คืออะไร ?
for (let i = 0; i < loc.length; i++) {
// process loc[i]
}
loc.forEach(l => {
// l คืออะไร ?
})

GOOD:

let cities = ['Austin', 'New York', 'San Francisco']; // อ่อมันคือ array ของ cities
for (let cityIndex = 0; cityIndex < cities.length; cityIndex++) {
// process cities[cityIndex]
}
cities.forEach(city => {
// city คือแต่ละเมืองที่กำลัง loop ไป
})

ไม่ต้องใส่ Context ซ้ำๆกันในตัวแปรประเภทเดียวกัน

พวก class / object ถ้าตัวมันเองสื่อถึงความหมายอยู่แล้ว ไม่จำเป็นต้องย้ำของที่อยู่ภายในว่าเป็นสิ่งนั้น

BAD:

// ใช้ student ภายใน key ซ้ำๆกัน ทั้งที่เราก็รู้อยู่แล้วว่ามันสื่อถึง Student
const Student = {
studentName: "Mike",
studentAge: 24,
studentGrade: 3
};

GOOD:

const Student = {
name: "Mike",
age: 24,
grade: 3
};

ใช้ค่า default แทนการ shortcut condition

เป็นการบอกกลายๆว่า ตัวแปรนี้ “เมื่อไม่มีการเรียกใช้เลย” จะการันตีว่ามีค่าอยู่แน่นอนได้

BAD:

function createGreeting(name, timeOfDay) {
name = name || 'Guest'; // Short circuiting
timeOfDay = timeOfDay || 'Day'; // Short circuiting
return `Good ${timeOfDay}, ${name}!`;
}
console.log(createGreeting()); // Output: "Good Day, Guest!"
console.log(createGreeting('Alice')); // Output: "Good Day, Alice!"

GOOD:

// ใส่ default ไปเลย จะได้ไม่ต้องมาคอยดักแต่ละค่าไว้
function createGreeting(name = 'Guest', timeOfDay = 'Day') {
return `Good ${timeOfDay}, ${name}!`;
}
console.log(createGreeting()); // Output: "Good Day, Guest!"
console.log(createGreeting('Alice')); // Output: "Good Day, Alice!"
console.log(createGreeting('Alice', 'Morning')); // Output: "Good Morning, Alice!"

2. Functions

แน่นอน ในเรื่องของการตั้งชื่อ function นั้นเราสามารถ follow ตามแบบเดียวกับหัวข้อของ Variable ได้ ในหมวดนี้เราจะพยายามเน้นไปที่สาระสำคัญของ “การสร้าง function” กันว่า ควรคำนึงถึงประเด็นไหนกันบ้างนะครับ

Function Arguments (2 or Fewer Ideally)

guideline นี้สร้างขึ้นเพื่อทำการลดทอนจำนวนของ arguments ของ function ลง เพื่อให้สามารถอ่านได้ง่ายขึ้น และเพื่อเป็นการลดทอนความซับซ้อนของ function ลงเช่นเดียวกัน (จะได้รู้ว่า function นี้เกี่ยวกับการจัดการเรื่องไหนได้เลย แทนที่จะต้องมาไล่ดูทีละตัวแปรว่ามาจากอะไร)

BAD:

// ตัวแปรทั้งหมดเกี่ยวกับ user แท้ๆ แต่พอกระจาย argument ก็จะสับสนได้ว่าแต่ละตัวแปรมีที่มายังไง
function createUser(firstName, lastName, age, email, phoneNumber, address) {
// Code to create a new user
}

GOOD:

// ก็จะเหลือเพียงแค่ 2 argument ที่ใช้จัดการใน function แทน
function createUser(name, contactDetails) {
// Code to create a new user
}
// หรือสามารถใช้ไอเดียของ `ES2015/ES6 destructuring syntax` ในการจัดการได้เหมือนกัน
function createUser({ firstName, lastName }, { age, email, phoneNumber, address }) {
// Code to create a new user
}
// Usage
createUser(
{ firstName: 'John', lastName: 'Doe' },
{ age: 30, email: 'john.doe@example.com', phoneNumber: '123-456-7890', address: '123 Main St' }
);

ลบ duplicate code !

หาก function ไหนมีการทำงานเหมือนกัน ให้รวมเป็น function เดียวกันออกมาแทน

BAD:

function updateUserProfile(userId, newProfileData) {
let user = database.fetchUserById(userId); // Fetching user
user.profile = newProfileData;
database.saveUser(user); // Saving changes
}
function updateUserEmailPreferences(userId, newEmailPreferences) {
let user = database.fetchUserById(userId); // Fetching user
user.emailPreferences = newEmailPreferences;
database.saveUser(user); // Saving changes
}

GOOD:

// รวมเป็น function สำหรับ update ตัวเดียว (เทคนิคนี้เรียกว่า High order function)
function updateUser(userId, updateFunction) {
let user = database.fetchUserById(userId);
updateFunction(user); // เรียกใช้ function ที่ส่งเข้ามาแทน
database.saveUser(user);
}
function updateUserProfile(userId, newProfileData) {
updateUser(userId, user => {
user.profile = newProfileData;
});
}
function updateUserEmailPreferences(userId, newEmailPreferences) {
updateUser(userId, user => {
user.emailPreferences = newEmailPreferences;
});
}

Functions ควรทำแค่สิ่งเดียว

เพื่อให้ง่ายตอนการใช้งานและการทำ Unit test การให้ function มีเพียงแค่จุดประสงค์เดียวจะลดความซับซ้อนของ code ลงได้

BAD:

function processAndSaveUserData(inputData) {
// ต้องแปลง data
let data = JSON.parse(inputData);
// ต้อง validate data
if (!data.name || !data.email) {
throw new Error("Invalid data - name and email are required");
}
// ต้องบันทึก data
database.save(data);
}

GOOD:

function parseUserData(inputData) {
return JSON.parse(inputData);
}
function validateUserData(data) {
if (!data.name || !data.email) {
throw new Error("Invalid data - name and email are required");
}
}
function saveUserData(data) {
database.save(data);
}
function processAndSaveUserData(inputData) {
let data = parseUserData(inputData);
validateUserData(data);
saveUserData(data);
}

เหตุผลที่ทำแบบนี้ดีกว่าเพราะ

  1. ตอนเรากลับมาแก้ จะชัดเจนว่าแต่ละจุดของ code ทำอะไรบ้าง
  2. เวลาที่มีปัญหาต้องแก้กับส่วนไหน (parseUserData, validateUserData, saveUserData) สามารถแยกส่วนแก้และแยกส่วน Test ได้

ชื่อ function ควรบอกว่าทำอะไร

ชื่อควรชัดเจนว่ามีการกระทำอะไรและมีจุดประสงค์เพื่อได้ผลลัพธ์อะไรออกมา

BAD:

// process อะไร ?
function process(data) {
if (data.purchaseAmount > 1000) {
return true;
}
return false;
}
// handle อะไรอยู่ ?
function handle(number) {
return number * number;
}

GOOD:

// อ่อ เช็คว่า user สามารถใช้ discount ได้ไหม
function isUserEligibleForDiscount(userPurchaseData) {
if (userPurchaseData.purchaseAmount > 1000) {
return true;
}
return false;
}
// อ่อ ทำการยกกำลังสองของตัวเลข
function calculateSquareOfNumber(number) {
// Code to calculate the square of a number
return number * number;
}

Set default objects ด้วย Object.assign

นอกเหนือจากการรับ argument ที่ไม่เยอะแล้ว ทุก functions ควรจะมีค่า default ของตัวเองเช่นเดียวกัน ซึ่ง javascript สามารถทำได้โดยใช้คำสั่ง Object.assign ซึ่งเป็นคำสั่งสำหรับการ merge object 2 ตัวเข้าด้วยกัน โดย Object.assign จะยึดตามค่าที่ส่งมา และจะเพิ่มเติมหากไม่มีค่าอะไรใน key นั้น เพื่อทำให้มั่นใจได้ว่า “ทุก key ใน object มีค่าเสมอแน่นอน”

BAD:

// ต้องมาคอยไล่ set แต่ละ key เพื่อให้แน่ใจ
function createReport(data, options) {
options = options || {};
let report = {
title: options.title || 'Default Report Title',
pageSize: options.pageSize || 'A4',
format: options.format || 'PDF',
headerColor: options.headerColor || '#000',
};
}

GOOD:

// เพียงใช้ Object.assign สามารถ set ได้ในบรรทัดเดียว
function createReport(data, options = {}) {
const defaultOptions = {
title: 'Default Report Title',
pageSize: 'A4',
format: 'PDF',
headerColor: '#000',
};
options = Object.assign({}, defaultOptions, options);
}

ไม่ควรใช้ parameter เพื่อแยกเคส function

จะทำให้การใช้งานเกิดการสับสนได้ว่า จุดประสงค์ของ function นี้จริงๆมันมีไว้่เพื่อทำอะไร

BAD:

// ทำได้ทั้ง 2 อย่างเลยทั้งลบทั้ง update
function updateDatabaseEntry(id, data, isDelete) {
if (isDelete) {
database.deleteEntry(id);
} else {
database.updateEntry(id, data);
}
}
// Usage
updateDatabaseEntry(123, someData, false); // to update
updateDatabaseEntry(123, null, true); // to delete

GOOD:

// แยกจุดประสงค์ให้ชัดเจนไปเลย อันไหน update, อันไหน delete
function updateDatabaseEntry(id, data) {
database.updateEntry(id, data);
}
function deleteDatabaseEntry(id) {
database.deleteEntry(id);
}
// Usage
updateDatabaseEntry(123, someData); // to update
deleteDatabaseEntry(123); // to delete

Encapsulate conditionals

การห่ออะไรก็ตามที่เป็น condition ยาวๆออกมาเป็น function แทนเพื่อให้ตรวจสอบได้ง่ายขึ้นและเข้าใจวัตถุประสงค์ของ function นั้นมากขึ้น

BAD:

const user = {
age: 25,
hasValidMembership: true,
memberSince: 3 // years
};
if (user.age > 18 && user.hasValidMembership && user.memberSince > 2) {
// Apply discount
}

GOOD:

const user = {
age: 25,
hasValidMembership: true,
memberSince: 3, // years
isEligibleForDiscount() {
return this.age > 18 && this.hasValidMembership && this.memberSince > 2;
}
};
if (user.isEligibleForDiscount()) {
// Apply discount
}

Remove dead code

Dead code คือ code ที่จะไม่ถูกแตะหรือเข้าถึง เนื่องจากไม่ได้ใช้งานหรือไม่มีเงื่อนไขใดสามารถไปถึงมันได้แล้ว

BAD:

function processUserInput(input) {
let processedInput;
if (input === null || input === undefined) {
processedInput = 'No input provided';
} else {
processedInput = input.trim();
}
// condition นี้จะไม่มีวันได้ทำงาน เพราะ processedInput จะมีค่าเสมอจาก if - else อันบน
if (processedInput === undefined) {
return 'Undefined input';
}
return processedInput;
}
// function นี้ก็ไม่ได้ใช้
function neverUsedFunction() {
console.log("This function is never called anywhere in the code.");
}
// variable นี้ก็ไม่ได้ใช้
const redundantVariable = "Not used anywhere";
console.log(processUserInput(" Hello World "));

GOOD:

function processUserInput(input) {
if (input === null || input === undefined) {
return 'No input provided';
}
return input.trim();
}
console.log(processUserInput(" Hello World "));

Avoid Side Effects

หลีกเลี่ยงการสร้าง function ที่ให้ผลลัพธ์ไม่เหมือนเดิม เนื่องจากมีการใช้ตัวแปรที่ dependency กัน เช่น

  • มี state บางอย่างถูกใช้งานจากภายนอกหรือใช้งานร่วมกันหลายๆที่ จาก global variable
  • มีการส่ง object ที่ดัดแปลง value ใน object
  • มีการจัดการผ่าน I/O บางอย่าง ที่ส่งผลทำให้แต่ละรอบจะขึ้นอยู่กับ file อื่นๆ

ซึ่งสิ่งนี้สามารถแก้ได้โดยการให้ทุก function ต้องทำแบบ “Pure function” (function ที่ต้องไม่มีีการทำอะไรกับ external state) เพื่อให้ function สามารถให้ผลลัพธ์เหมือนกันในทุกๆรอบที่ run ออกมาได้

เช่น เคสที่ 1 global variable

BAD:

let userAge = 30;
function increaseAge(newAge) {
userAge = newAge; // Modifies the global variable userAge
}
increaseAge(31);
console.log(userAge); // 31 - The global variable has been changed

GOOD:

function getIncreasedAge(currentAge, increment) {
return currentAge + increment; // Pure function with no side effects
}
let userAge = 30;
let newUserAge = getIncreasedAge(userAge, 1);
console.log(userAge); // 30 - Original age is unchanged
console.log(newUserAge); // 31 - New age is calculated and returned

เช่น เคสที่ 2 site effect จาก object

BAD:

// เมื่อ cart มีการนำไปใช้งานต่อ จะได้ข้อมูลเพิ่มต่อได้
const addItemToCart = (cart, item) => {
cart.push({ item, date: Date.now() });
};

GOOD:

// ใช้ ... เพื่อทำการกระจายของในตัวแปรเพื่อสร้างเป็นตัวแปรใหม่ออกมา
const addItemToCart = (cart, item) => {
return [...cart, { item, date: Date.now() }];
};
  • site effect สามารถศึกษาแบบลึกซึ้งได้จากการเขียน program แบบ Functional Programming (จริงๆแล้วหลายปัญหาของ function สามารถแก้ไขจากการเขียน program style functional programming ได้)

3. Concurrency

ตระกูลนี้จะเป็น set ของ function แบบ asynchronous ที่จะมีการทำงาน function ไปพร้อมๆกัน

ใช้ Promises, ไม่ใช้ callbacks

Callback นั้นมีปัญหาทำให้ code เกิดความไม่ clean ทั้งจากการซ้อนกัน (เป็น callback hell) การต้องใช้ต่อเนื่องกันหรือแม้แต่การ handle error ที่ค่อนข้างยาก ด้วย ES2015/ES6 Promise ได้ถูก built-in เป็น default ไว้เป็นที่เรียบร้อย และส่งผลทำให้ code handle ได้ง่ายขึ้นกว่าเดิมเยอะมาก

  • ด้วย Promise สามารถทำเป็น chain ของ asynchronous function ได้แทนที่จะต้องซ้อน function กันต่อๆไปเหมือน Callback

BAD:

function fetchData(url, callback) {
// Simulate an API call
setTimeout(() => {
try {
// Success
const data = "Sample data from " + url
callback(null, data)
} catch (error) {
// Error
callback(error, null)
}
}, 1000)
}
// Using the function
fetchData("https://api.example.com", (error, data) => {
if (error) {
console.error("Error:", error)
} else {
console.log("Received data:", data)
}
})

GOOD:

function fetchData(url) {
return new Promise((resolve, reject) => {
// Simulate an API call
setTimeout(() => {
try {
// Success
const data = "Sample data from " + url
resolve(data)
} catch (error) {
// Error
reject(error)
}
}, 1000)
})
}
// Using the function
fetchData("https://api.example.com")
.then((data) => console.log("Received data:", data))
.catch((error) => console.error("Error:", error))

ใช้ Async/Await อ่านง่ายกว่า Promises

Async/await เป็นตัวที่ถูกนำเสนอมาเพิ่มใน ECMAScript 2017 ซึ่งเป็นการเพิ่มความสามารถของ asynchronous โดยเป็นการ build on top Promises เพิ่มมาอีกที ซึ่งจุดแข็งใหญ่ๆของ Async/await เพื่อให้สามารถจัดการ asynchronous code แบบ “synchronous code” ได้

ซึ่งสิ่งนี้จะส่งผลทำให้ syntax จัดการได้ด้วยวิธีการ try/catch ตามปกติได้ (ซึ่งจะเป็นการปรับมุมมองจาก .then(), .catch() ให้ใช้งานง่ายขึ้น) ซึ่งจะส่งผลทำให้ code อ่านง่ายขึ้นกว่าเดิมพอสมควร

เช่นตามตัวอย่างนี้

BAD:

function fetchData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
try {
const data = `Data from ${url}`
resolve(data)
} catch (error) {
reject(error)
}
}, 1000)
})
}
fetchData("https://api.example.com")
.then((data) => console.log("Received data:", data))
.catch((error) => console.error("Error:", error))

GOOD:

async function fetchData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
try {
const data = `Data from ${url}`
resolve(data)
} catch (error) {
reject(error)
}
}, 1000)
})
}
async function displayData() {
try {
const data = await fetchData("https://api.example.com")
console.log("Received data:", data)
} catch (error) {
console.error("Error:", error)
}
}
displayData()

สังเกตว่า displayData() จะตรงไปตรงมาเหมือน code synchronous ส่งผลทำให้เวลากลับมาอ่าน code จะอ่าน code ได้ง่ายกว่า Promise ด้วยเช่นกัน

4. Error Handling

อีกหนึ่งเรื่องที่สำคัญคือการจัดการ Error ใน Javascript นอกเหนือจากการเขียนจัดการ control flow แล้ว การจัดการ Error ก็สำคัญเช่นเดียวกัน การจัดการ Error ให้ clean จึงเป็นเรื่องสำคัญเหมือนกัน

โดยสาระสำคัญของการจัดการ Error จะมี 2 เร่ืองใหญ่ๆเลย (ซึ่งเอาจริงๆ ถ้าใครเขียน code จนเคยน่าจะทำกันอยู่และ)

handle error เสมอกับ asynchronous

ใช่ครับ จากประสบการณ์ที่ผมเจอมา ผมเจอว่า asynchronous code ส่วนหนึ่ง (สำหรับมือใหม่) จะไม่ได้มีการ handle error เอาไว้ ส่งผลทำให้ไม่รู้ว่าตอนเกิด error แบบ asynchronous เกิดขึ้น ทำให้ไม่รู้ว่าเกิดขึ้นจากจุดไหนของ code ซึ่งไม่ว่าจะเป็นเคส Promise หรือ Async/Await ได้เตรียมวิธีจัดการให้เรียบร้อยเช่นเดียวกัน

สำหรับเคส Promise

BAD:

function fetchData(url) {
return fetch(url) // fetch returns a Promise
.then((response) => response.json())
}
fetchData("https://api.example.com/data").then((data) =>
console.log("Data:", data)
)
// No .catch() for handling errors

GOOD:

function fetchData(url) {
return fetch(url).then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
return response.json()
})
}
fetchData("https://api.example.com/data")
.then((data) => console.log("Data:", data))
.catch((error) => console.error("Failed to fetch data:", error))

สำหรับเคส Async/Await ใช้ Try catch

BAD:

async function fetchData(url) {
const response = await fetch(url)
return await response.json()
}
async function displayData() {
const data = await fetchData("https://api.example.com/data")
console.log("Data:", data)
// No error handling
}
displayData()

GOOD:

async function fetchData(url) {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
return await response.json()
}
async function displayData() {
try {
const data = await fetchData("https://api.example.com/data")
console.log("Data:", data)
} catch (error) {
console.error("Failed to fetch data:", error)
}
}
displayData()

ไม่มองข้าม Error

ไม่ว่าจะเป็นเคส Promise หรือ Async/Await ก็ตาม ในการจัดการ Error ควรจัดการอย่างอื่นเพิ่มเติมด้วย นอกเหนือจากการ console.log(error) อย่างเดียว ควรทำอย่างอื่นเพิ่มเติม เพื่อให้ฝั่ง user รู้ว่ามี Error เกิดขึ้นด้วย

BAD:

// try catch error ทิ้งไว้
try {
someFunction();
} catch (error) {
console.log(error);
}
// Promise error ทิ้งไว้
someFunction()
.then(data => {
functionThatMightThrow(data);
})
.catch(error => {
console.log(error);
});

GOOD:

// try catch
try {
someFunction();
} catch (error) {
// One option (more noisy than console.log):
console.error(error);
// ทำ function บางอย่างเพิ่มเติมไป เพื่อให้ user รู้ว่า error เกิดขึ้น
popupError(error);
}
// Promise error
someFunction()
.then(data => {
functionThatMightThrow(data);
})
.catch(error => {
console.log(error);
// ทำ function บางอย่างเพิ่มเติมไป เพื่อให้ user รู้ว่า error เกิดขึ้น
popupError(error);
});

5. อื่นๆ

นอกเหนือจากเรืื่องพื้นฐานเหล่านี้แล้วจริงๆ Practice ของการ clean code ยังมีอีกหลายอย่างให้ศึกษาเพิ่มเติมได้

เช่น

  1. การเขียน Comment ที่ดีว่าควรเขียนเป็นแบบไหน (มีหลายสำนักมาก)
  2. การจัด Formating ที่ดีว่าควรจัด format code ออกมาเป็นแบบไหน (อนาคตเดี๋ยวผมจะกลับมาเล่าเพิ่มเติมในหัวข้อที่เกี่ยวกับ Linter)
  3. Principle อื่นๆ ที่เป็น guideine ในการเขียน code ได้เช่น DRY (Don’t repeat yourself), SOLID (สำหรับสาย OOP)

ขอแนะนำบทความนี้เพิ่มเติม (เป็นบทความที่ผมใช้ Reference ด้วยเช่นเดียวกัน) สำหรับคนที่สนใจในหมวดหมู่อื่นๆเพิ่มเติม https://github.com/ryanmcdermott/clean-code-javascript

สรุป

นี่คือไอเดียของการ Clean code ของ javascript แบบเบื้องต้น การทำสิ่งเหล่านี้นอกเหนือจากการที่ทำให้ code เป็นระเบียบเรียบร้อยแล้ว จะยังทำให้ code เขียนสัั้นลงและจัดการได้ง่ายขึ้นด้วย ขอส่งท้ายไว้ว่า แต่ละภาษาก็มีวิธีการที่ไม่เหมือนกัน หากใครมีภาษาอื่นเป็นตัวหลักแนะนำให้ศึกษาภาษานั้นๆเพิ่มเติมนะครับ

หวังว่าจะ Enjoy กับการทำความสะอาด code กันนะครับ 😁

Related Post

Share on social media