มารู้จักการเขียน 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:
GOOD:
รวมกลุ่มศัพท์ประเภทเดียวกัน
เป็นการกันสับสนว่าข้อมูลเป็นประเภทเดียวกันหรือไม่ ?
BAD:
GOOD:
ใช้ชื่อที่สามารถค้นหาได้และอ่านได้
อย่าใช้ตัวแปร (เช่น ตัวแปรตัวเดียว) หรือค่าคงที่ “ที่อธิบายไม่ได้” วางไว้ใน code
BAD:
GOOD:
เลี่ยงใช้ตัวย่อ
พยายามใช้คำเต็ม เพื่อกันสับสนว่ากำลังสื่อถึงอะไรอยู่
BAD:
GOOD:
ไม่ต้องใส่ Context ซ้ำๆกันในตัวแปรประเภทเดียวกัน
พวก class / object ถ้าตัวมันเองสื่อถึงความหมายอยู่แล้ว ไม่จำเป็นต้องย้ำของที่อยู่ภายในว่าเป็นสิ่งนั้น
BAD:
GOOD:
ใช้ค่า default แทนการ shortcut condition
เป็นการบอกกลายๆว่า ตัวแปรนี้ “เมื่อไม่มีการเรียกใช้เลย” จะการันตีว่ามีค่าอยู่แน่นอนได้
BAD:
GOOD:
2. Functions
แน่นอน ในเรื่องของการตั้งชื่อ function นั้นเราสามารถ follow ตามแบบเดียวกับหัวข้อของ Variable ได้ ในหมวดนี้เราจะพยายามเน้นไปที่สาระสำคัญของ “การสร้าง function” กันว่า ควรคำนึงถึงประเด็นไหนกันบ้างนะครับ
Function Arguments (2 or Fewer Ideally)
guideline นี้สร้างขึ้นเพื่อทำการลดทอนจำนวนของ arguments ของ function ลง เพื่อให้สามารถอ่านได้ง่ายขึ้น และเพื่อเป็นการลดทอนความซับซ้อนของ function ลงเช่นเดียวกัน (จะได้รู้ว่า function นี้เกี่ยวกับการจัดการเรื่องไหนได้เลย แทนที่จะต้องมาไล่ดูทีละตัวแปรว่ามาจากอะไร)
BAD:
GOOD:
ลบ duplicate code !
หาก function ไหนมีการทำงานเหมือนกัน ให้รวมเป็น function เดียวกันออกมาแทน
BAD:
GOOD:
Functions ควรทำแค่สิ่งเดียว
เพื่อให้ง่ายตอนการใช้งานและการทำ Unit test การให้ function มีเพียงแค่จุดประสงค์เดียวจะลดความซับซ้อนของ code ลงได้
BAD:
GOOD:
เหตุผลที่ทำแบบนี้ดีกว่าเพราะ
- ตอนเรากลับมาแก้ จะชัดเจนว่าแต่ละจุดของ code ทำอะไรบ้าง
- เวลาที่มีปัญหาต้องแก้กับส่วนไหน (parseUserData, validateUserData, saveUserData) สามารถแยกส่วนแก้และแยกส่วน Test ได้
ชื่อ function ควรบอกว่าทำอะไร
ชื่อควรชัดเจนว่ามีการกระทำอะไรและมีจุดประสงค์เพื่อได้ผลลัพธ์อะไรออกมา
BAD:
GOOD:
Set default objects ด้วย Object.assign
นอกเหนือจากการรับ argument ที่ไม่เยอะแล้ว ทุก functions ควรจะมีค่า default ของตัวเองเช่นเดียวกัน ซึ่ง javascript สามารถทำได้โดยใช้คำสั่ง Object.assign
ซึ่งเป็นคำสั่งสำหรับการ merge object 2 ตัวเข้าด้วยกัน โดย Object.assign
จะยึดตามค่าที่ส่งมา และจะเพิ่มเติมหากไม่มีค่าอะไรใน key นั้น เพื่อทำให้มั่นใจได้ว่า “ทุก key ใน object มีค่าเสมอแน่นอน”
BAD:
GOOD:
ไม่ควรใช้ parameter เพื่อแยกเคส function
จะทำให้การใช้งานเกิดการสับสนได้ว่า จุดประสงค์ของ function นี้จริงๆมันมีไว้่เพื่อทำอะไร
BAD:
GOOD:
Encapsulate conditionals
การห่ออะไรก็ตามที่เป็น condition ยาวๆออกมาเป็น function แทนเพื่อให้ตรวจสอบได้ง่ายขึ้นและเข้าใจวัตถุประสงค์ของ function นั้นมากขึ้น
BAD:
GOOD:
Remove dead code
Dead code คือ code ที่จะไม่ถูกแตะหรือเข้าถึง เนื่องจากไม่ได้ใช้งานหรือไม่มีเงื่อนไขใดสามารถไปถึงมันได้แล้ว
BAD:
GOOD:
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:
GOOD:
เช่น เคสที่ 2 site effect จาก object
BAD:
GOOD:
- 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:
GOOD:
ใช้ 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:
GOOD:
สังเกตว่า 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:
GOOD:
สำหรับเคส Async/Await ใช้ Try catch
BAD:
GOOD:
ไม่มองข้าม Error
ไม่ว่าจะเป็นเคส Promise หรือ Async/Await ก็ตาม ในการจัดการ Error ควรจัดการอย่างอื่นเพิ่มเติมด้วย นอกเหนือจากการ console.log(error)
อย่างเดียว ควรทำอย่างอื่นเพิ่มเติม เพื่อให้ฝั่ง user รู้ว่ามี Error เกิดขึ้นด้วย
BAD:
GOOD:
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 กัน