มารู้จักการเขียน code แบบ clean code กัน (ฉบับ Javascript)
/ 10 min read
สามารถดู 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 บางอย่างเพิ่มเติมส่งผลทำให้การเขียนบางคำสั่งสามารถเขียนได้ง่ายขึ้น เป็นต้น)
โดยประเด็นที่เราจะหยิบมาเล่าในบทความนี้ เราจะแยกออกเป็นประเด็นตามนี้คือ
- Variable (ตัวแปร)
- Functions
- Concurrency (callback, promise, async-await)
- Error Handling
- อื่นๆ (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 namelet 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 ของ citiesfor (let cityIndex = 0; cityIndex < cities.length; cityIndex++) { // process cities[cityIndex]}cities.forEach(city => { // city คือแต่ละเมืองที่กำลัง loop ไป})
ไม่ต้องใส่ Context ซ้ำๆกันในตัวแปรประเภทเดียวกัน
พวก class / object ถ้าตัวมันเองสื่อถึงความหมายอยู่แล้ว ไม่จำเป็นต้องย้ำของที่อยู่ภายในว่าเป็นสิ่งนั้น
BAD:
// ใช้ student ภายใน key ซ้ำๆกัน ทั้งที่เราก็รู้อยู่แล้วว่ามันสื่อถึง Studentconst 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}
// UsagecreateUser( { firstName: 'John', lastName: 'Doe' },);
ลบ 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);}
เหตุผลที่ทำแบบนี้ดีกว่าเพราะ
- ตอนเรากลับมาแก้ จะชัดเจนว่าแต่ละจุดของ code ทำอะไรบ้าง
- เวลาที่มีปัญหาต้องแก้กับส่วนไหน (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 อย่างเลยทั้งลบทั้ง updatefunction updateDatabaseEntry(id, data, isDelete) { if (isDelete) { database.deleteEntry(id); } else { database.updateEntry(id, data); }}
// UsageupdateDatabaseEntry(123, someData, false); // to updateupdateDatabaseEntry(123, null, true); // to delete
GOOD:
// แยกจุดประสงค์ให้ชัดเจนไปเลย อันไหน update, อันไหน deletefunction updateDatabaseEntry(id, data) { database.updateEntry(id, data);}
function deleteDatabaseEntry(id) { database.deleteEntry(id);}
// UsageupdateDatabaseEntry(123, someData); // to updatedeleteDatabaseEntry(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 unchangedconsole.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 functionfetchData("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 functionfetchData("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 catchtry { someFunction();} catch (error) { // One option (more noisy than console.log): console.error(error); // ทำ function บางอย่างเพิ่มเติมไป เพื่อให้ user รู้ว่า error เกิดขึ้น popupError(error);}
// Promise errorsomeFunction() .then(data => { functionThatMightThrow(data); }) .catch(error => { console.log(error); // ทำ function บางอย่างเพิ่มเติมไป เพื่อให้ user รู้ว่า error เกิดขึ้น popupError(error); });
5. อื่นๆ
นอกเหนือจากเรืื่องพื้นฐานเหล่านี้แล้วจริงๆ Practice ของการ clean code ยังมีอีกหลายอย่างให้ศึกษาเพิ่มเติมได้
เช่น
- การเขียน Comment ที่ดีว่าควรเขียนเป็นแบบไหน (มีหลายสำนักมาก)
- การจัด Formating ที่ดีว่าควรจัด format code ออกมาเป็นแบบไหน (อนาคตเดี๋ยวผมจะกลับมาเล่าเพิ่มเติมในหัวข้อที่เกี่ยวกับ Linter)
- 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 กันนะครับ 😁
- มาเรียนรู้การทำ Frontend Testing ผ่าน React กันมี Video
แนะนำพื้นฐานการทำ Component Testing สำหรับการทำ Unit testing ฝั่ง Frontend
- Redux และ Reactมี Video มี Github
รู้จักกับ Redux state management ที่ช่วยทำให้ application จัดการ state ได้สะดวกสบายยิ่งขึ้นกัน
- NestJS และ Mongoมี Video
เรียนรู้การผสานพลังระหว่าง NestJS framework ยอดนิยมฝั่ง Node.js กับ MongoDB ฐานข้อมูล NoSQL สุดทรงพลังกัน
- รู้จักกับ Design Pattern - Behavioral (Part 3/3)มี Video
มาเรียนรู้รูปแบบการพัฒนา Software Design Pattern ประเภทที่สาม Behavioral กัน