Cyclomatic Complexity Calculator: วัดและลดความซับซ้อนของโค้ด JavaScript และ TypeScript
Cyclomatic Complexity Calculator วิเคราะห์ function JavaScript และ TypeScript ที่วางลงในเบราว์เซอร์ พร้อม score ความซับซ้อนราย function เกรดความเสี่ยงแบบ A-D และคำแนะนำการ refactor ที่ลงมือทำได้จริง
Table of Contents
ทุกทีม dev คงเคยเจอ function แบบนี้: ยาวสองร้อยบรรทัด เต็มไปด้วย if ซ้อนกันเป็นชั้น มี loop สามวง และ switch ที่ไม่มีใครกล้าแตะ เรารู้สึกได้ว่ามัน "เสี่ยง" แต่ความรู้สึกแบบนั้นใช้เป็นข้อโต้แย้งใน code review ไม่ได้ Cyclomatic Complexity Calculator เปลี่ยนความรู้สึกนั้นให้เป็นตัวเลขที่ลงมือจัดการได้ ด้วยการวัด cyclomatic complexity ซึ่งก็คือจำนวนเส้นทางการทำงานที่เป็นอิสระต่อกันภายในโค้ดของคุณ
เพียงวาง function JavaScript หรือ TypeScript ลงไป เครื่องมือจะส่งกลับ score ราย function, เกรดความเสี่ยงแบบ A-D และคำแนะนำการ refactor สำหรับ function ที่ซับซ้อนที่สุด การวิเคราะห์เป็นแบบ heuristic และทำงานในเบราว์เซอร์ทั้งหมด ไม่ต้องติดตั้งอะไร ไม่ต้องสมัครบัญชี และโค้ดไม่เคยออกจากเครื่องของคุณ
บทความนี้จะพาไปดูวิธีใช้งานเครื่องมือ คะแนนนับอะไรกันแน่ และวิธีเปลี่ยนตัวเลขเหล่านี้ให้เป็นแผน refactor ที่ใช้ได้จริง
ทำไมต้องใช้ Cyclomatic Complexity Calculator?
- มีหลักฐานเชิงตัวเลขสำหรับ code review แทนที่จะเถียงกันเรื่องรสนิยม ให้วาง function ลงไปแล้วชี้ที่ score กับเกรด ตัวเลขจบข้อถกเถียงได้เร็วกว่าความเห็น
- เจอความเสี่ยงก่อน production เจอ งานวิจัยตั้งแต่ยุค 1970s เชื่อมโยง complexity ที่สูงกับ defect ที่มากขึ้น เส้นทางการทำงานยิ่งเยอะ วิธีพังยิ่งเยอะ
- รู้ว่าต้องเขียน test กี่เคส score เท่ากับ N หมายถึงต้องมี test ราว ๆ N เคสเพื่อครอบคลุมทุก branch ถ้าตัวเลขนั้นรู้สึกเขียน test ไม่ไหว นั่นคือสัญญาณให้ refactor
- ได้คำแนะนำที่ลงมือทำได้ ไม่ใช่แค่ตัวเลข function ที่ซับซ้อนจะมาพร้อม hint การ refactor ทำให้จบการวิเคราะห์แล้วมี to-do ติดมือ ไม่ใช่แค่คำเตือนลอย ๆ
- เป็นส่วนตัวและได้ผลทันที การวิเคราะห์แบบ heuristic รันในเบราว์เซอร์ล้วน วางโค้ดที่เป็นความลับของบริษัทได้โดยไม่ต้องกังวลเรื่องการอัปโหลด
- รองรับทั้ง JavaScript และ TypeScript ตรวจ component ฝั่ง frontend, service บน Node และ utility module ได้ด้วยเครื่องมือเดียว
ฟีเจอร์หลัก
| ฟีเจอร์ | ทำอะไร |
|---|---|
| วางโค้ด JavaScript หรือ TypeScript | วาง function เดียวหรือทั้ง module ก็ได้ วิเคราะห์ให้ทันที |
| Score ความซับซ้อนราย function | ทุก function ที่ตรวจพบมีค่า complexity ของตัวเอง ทำให้เห็นจุดร้อนทันที |
| เกรดความเสี่ยงแบบ A-D | แปลง score เป็นเกรดตัวอักษรที่สื่อความรุนแรงได้ในแวบเดียว |
| คำแนะนำการ refactor | function ที่ซับซ้อนมีข้อเสนอแนะที่จับต้องได้ เช่น วิธีแยกหรือทำให้ง่ายขึ้น |
| วิเคราะห์ในเบราว์เซอร์แบบ heuristic | ไม่มีการอัปโหลด ไม่มี backend ผลลัพธ์โผล่ทันทีที่วางโค้ด |
สองรายละเอียดที่ควรรู้:
- การดูราย function สำคัญกว่าค่าเฉลี่ยระดับไฟล์ module หนึ่งอาจดูปกติแต่ซ่อน function เดียวที่มี score 25 เอาไว้
- เกรดแบบ A-D สร้างภาษากลางของทีม เกรด A คือเขียน test ได้ง่ายมาก ส่วนเกรด D คือแหล่งรวม bug และ merge conflict
วิธีใช้งาน Cyclomatic Complexity Calculator
- วาง function ของคุณ เปิด ตัวคำนวณ แล้ววางโค้ด JavaScript หรือ TypeScript ลงในช่อง input จะเป็น function เดียวหรือหลายตัวก็ได้ เครื่องมือแยกวิเคราะห์ทีละ function
- ดู score และเกรด ทุก function จะแสดงค่า cyclomatic complexity และเกรดความเสี่ยงของตัวเอง เริ่มจากตัวแย่สุดก่อน: เกรด D มาก่อนเกรด C แล้วค่อยไล่ลงมา
- อ่านคำแนะนำการ refactor สำหรับ function เกรด C หรือ D ให้ดู hint ที่ชี้รูปแบบที่ทำให้ score พุ่ง เช่น การซ้อนกันลึกหรือเงื่อนไขยาวเป็นสาย
- Refactor ตัวแย่ที่สุดก่อน เลือก function ที่ score สูงสุดแล้วใช้หนึ่งคำแนะนำ มักเป็นการดึงบล็อกที่เกี่ยวข้องกันออกไปเป็น function ใหม่ที่ตั้งชื่อดี หรือแปลงเงื่อนไขซ้อนให้เป็น guard clause
- วัดซ้ำ วางโค้ดที่แก้แล้วกลับเข้าไปเพื่อยืนยันว่า score ลดและเกรดดีขึ้น แล้วทำซ้ำกับ function ถัดไปจนกว่าไม่มีอะไรเกินเกณฑ์ของทีม
คะแนนนับอะไรกันแน่
Cyclomatic complexity เสนอครั้งแรกโดย Thomas McCabe ในปี 1976 วัดจำนวนเส้นทางการทำงานที่เป็นอิสระต่อกันภายใน function หนึ่ง แนวคิดของสูตรคลาสสิกง่ายมาก เริ่มที่ 1 จากเส้นทางตรงเส้นเดียว แล้วบวกเพิ่ม 1 ทุกครั้งที่เจอจุดตัดสินใจที่ทำให้เส้นทางแยกออก
จุดตัดสินใจที่มักถูกนับเพิ่ม 1 คะแนน ได้แก่:
- if และ else if แต่ละชั้น
- for และ while
- case แต่ละอันใน switch
- ตัวดำเนินการ short-circuit อย่าง && และ || แต่ละตัว
- ternary ในรูป condition ? a : b
function ที่มี if สามอัน, loop สองวง และ && หนึ่งตัว จะเริ่มที่ 1 และจบที่ 7 ทุกการตัดสินใจคือเส้นทางที่ test ต้องเดินให้ครบ และเป็นจุดที่การแก้โค้ดในอนาคตอาจทำให้เกิด bug ที่ปรากฏเฉพาะบางเส้นทางเท่านั้น
เกรดช่วยแปลงตัวเลขดิบให้เป็นคำทำนายที่ใช้ได้จริง โดยประมาณที่หลายทีมใช้: score 1-5 อยู่แถวเกรด A คือโค้ดง่ายและเขียน test สบาย คะแนน 6-10 ถือว่าปานกลาง ประมาณ logic ของ function เดียว คะแนน 11-20 อยู่แถวเกรด C ซึ่งต้องวางแผนครอบคลุม branch อย่างเป็นระบบ และเป็นช่วงที่อัตรา bug เริ่มพุ่งชัดเจน ส่วนเกิน 20 คือเขตเกรด D ที่งานวิจัยด้านการทำนาย defect สอดคล้องกันว่าความหนาแน่นของ fault สูงกว่าปกติ และการเขียน test ให้ครอบคลุมทุกเส้นทางกลายเป็นโปรเจกต์ไปเอง
แล้วทำไมใช้ heuristic แทน AST parser เต็มรูปแบบ? parser ระดับคอมไพเลอร์จะเดิน control-flow graph อย่างแม่นยำ แต่ตัวใหญ่ ติดตั้งยุ่ง และต้องตามทุกมุมของภาษา เครื่องมือนี้เลือกใช้การวิเคราะห์เชิงรูปแบบที่ปรับกับ JavaScript และ TypeScript ทั่วไป ผลลัพธ์กับโค้ดธุรกิจทั่วไปจึงใกล้เคียง parser เต็มรูปแบบมาก แลกมาด้วยสิ่งที่มีค่าคือ วิเคราะห์ได้ทันทีในเบราว์เซอร์แบบส่วนตัวโดยไม่ต้องติดตั้งอะไรเลย ให้ถือว่า score เป็นสัญญาณความเสี่ยงที่เชื่อถือได้แทนที่จะเป็นค่าที่แม่นเป๊ะ ช่องว่างระหว่าง 14 กับ 15 สำคัญน้อยกว่าช่องว่างระหว่าง 6 กับ 14 อยู่แล้ว
hint ที่เครื่องมือแนะนำคือเทคนิค refactor คลาสสิกที่ลด complexity ได้จริง:
- Extract function ดึงบล็อกที่เกี่ยวข้องกันออกไปเป็น function ใหม่ที่ตั้งชื่อดี ๆ จำนวนการตัดสินใจรวมแทบไม่เปลี่ยน แต่ score ของแต่ละ function ลดลง และแต่ละตัวเขียน test แยกได้
- ใช้ตาราง lookup แทน switch ยาว ๆ switch ที่มีสิบ case เปลี่ยนเป็น object หรือ Map ที่จับคู่ key กับ handler ได้ ยุบการตัดสินใจสิบจุดให้เหลือการเข้าถึง property จุดเดียว
- Guard clause พลิก if/else ที่ซ้อนกันเป็นชั้นให้เป็น early return การ์ดแต่ละอันยังถูกนับอยู่ แต่ความลึกของการซ้อน ซึ่งเป็นตัวที่ทำร้ายความเข้าใจจริง ๆ หายไป
กรณีใช้งานจริง
คัดกรองก่อนลงมือ refactor
ก่อนแตะ module ที่อ่านยาก ให้วางมันลงเครื่องมือ ภายในไม่กี่วินาทีคุณจะได้รายการ function เรียงตามเกรดความเสี่ยง ซึ่งบอกได้ว่าควรเทแรงงาน refactor ตรงไหน และได้ baseline ไว้โชว์ความก้าวหน้า เช่น สอง function เกรด D ตอนนี้เป็นเกรด B แล้ว และ score สูงสุดของ module ลดจาก 26 เหลือ 9
หลักฐานประกอบการ review pull request
คำขอแบบ "ช่วยแยก function นี้หน่อย" จะน่าเชื่อถือขึ้นเมื่อมีตัวเลขกำกับ เมื่อคอมเมนต์ใน review ฟังดูเหมือนเรื่องรสนิยม ให้วาง function ใหม่ลงเครื่องมือ อ้าง score กับเกรด และแนบ hint ไปด้วย ฝั่ง reviewer ยอมรับ complexity budget ง่ายขึ้นเมื่อวัดได้ และฝั่ง author ก็โต้แย้งด้วยข้อมูลได้หากเงื่อนไขแตกไม่ได้จริงตาม domain
ไล่ตรวจไฟล์ legacy
เพิ่งรับงาน codebase เก่า? วางไฟล์ใหญ่ ๆ ทีละไฟล์แล้วให้เกรดช่วยวาดแผนที่ความเสี่ยง function เกรด D ในไฟล์ legacy คือตัวเลือกอันดับแรกสำหรับการเขียน characterization test ก่อนแตะโค้ด เพราะจำนวนเส้นทางบอกได้ว่าเส้นตาข่ายต้องใหญ่แค่ไหนก่อนจะกล้าแก้แม้แต่บรรทัดเดียว
สอนวินัยเรื่องการแตกเงื่อนไข
สำหรับทีมและนักเรียน เครื่องมือนี้ทำให้เรื่องนามธรรมจับต้องได้ วาง one-liner แบบ "เก่ง ๆ" ที่อัด ternary กับ short-circuit แน่น ๆ ไว้ข้างเวอร์ชันหลายบรรทัดแบบธรรมดา แล้วเทียบ score กัน การเห็นว่า expression เดียวมี complexity ถึง 8 สอนได้ดีกว่าคำบรรยายว่า โค้ดที่อ่านง่ายไม่เท่ากับโค้ดที่เรียบง่าย และ score ต่ำคือเป้าหมายเชิงดีไซน์ที่ควรปกป้อง
แนวปฏิบัติที่ดี
- ถือว่า score เป็นกลิ่น ไม่ใช่กฎหมาย score สูงเป็นสัญญาณให้ไปดู ไม่ใช่คำพิพากษา บาง domain มีสิทธิ์แตก branch เยอะอย่างชอบธรรม
- Refactor โดยมี test ล้อมรอบเสมอ ห้ามแยกหรือเขียน function ซับซ้อนใหม่โดยไม่มี test คุมพฤติกรรมเดิมก่อน score บอกความเสี่ยง test คืออุปกรณ์นิรภัย
- เลือก function เล็กที่ตั้งชื่อดี การ extract จะช่วยก็ต่อเมื่อ function ใหม่มีจุดประสงค์และชื่อที่ชัด processAndValidateAndFormatData คือ complexity ชุดเดิมที่แต่งตัวใหม่เท่านั้น
- ระวัง complexity ย้ายบ้านแทนที่จะลดลง ถ้า extract แล้วได้ parent ที่เรียกลูกห้าตัวตามลำดับแน่น ๆ ความเข้าใจรวมอาจไม่ดีขึ้นเลย ตั้งเป้าให้ score ลดจริง ไม่ใช่ย้ายที่อยู่
- ตั้งเกณฑ์ของทีม หลายทีมใช้ 10 เป็นเพดานสำหรับโค้ดใหม่ และใช้เครื่องมือนี้ใน review เพื่อกันโค้ดใหม่ไม่ให้ล้ำ พร้อมค่อย ๆ ไล่แก้จุดเก่าที่โดดออกมา
- วัดซ้ำหลังแก้พอสมควรทุกครั้ง complexity ค่อย ๆ ซึมเข้ามาทีละเงื่อนไข การวางเช็กเร็ว ๆ หลังเพิ่ม logic ช่วยให้เห็นการไหลของตัวเลขก่อนจะสายเกินไป
พร้อมดูแล้วหรือยังว่าโค้ดของคุณซ่อนความเสี่ยงไว้ตรงไหน? วาง function ลงใน Cyclomatic Complexity Calculator ดูเกรด แล้วตาม hint ไปเลย ฟังก์ชันส่วนใหญ่ลดเกรดได้ทั้งขั้นด้วย refactor เพียงรอบเดียวที่โฟกัสดี
เครื่องมือที่เกี่ยวข้องที่คุณอาจสนใจ:
- Regex Performance Tester — วัดประสิทธิภาพ regex pattern ก่อนขึ้น production
- Dockerfile Linter — จับปัญหา Dockerfile ก่อน build พัง
- JSON Formatter — จัดรูปแบบ ตรวจสอบ และดูโครงสร้าง JSON
ขอให้ refactor สนุก!
คำถามที่พบบ่อย
ถ: Cyclomatic Complexity Calculator อัปโหลดโค้ดของฉันไปที่ไหนหรือเปล่า? ตอบ: ไม่ การวิเคราะห์เป็นแบบ heuristic และรันในเบราว์เซอร์ทั้งหมด โค้ดของคุณจึงไม่เคยออกจากเครื่อง
ถ: รองรับทั้ง JavaScript และ TypeScript ใช่ไหม? ตอบ: ใช่ วางได้ทั้ง JavaScript ธรรมดาและ TypeScript รวมถึง function ที่มี type annotation และได้ score เกรด และ hint ราย function เหมือนกัน
ถ: score เท่าไรถึงถือว่าสูงเกินไป? ตอบ: หลายทีมใช้ 10 เป็นเพดานนุ่ม ๆ เกรดช่วยให้ตัดสินง่าย เกรด A กับ B ถือว่าสบาย เกรด C ควรเข้าไปดู และเกรด D คือผู้สมัคร refactor ตัวจริง
ถ: ทำไมตัวเลขถึงต่างจาก cyclomatic complexity ของ linter เล็กน้อย? ตอบ: เครื่องมือใช้การวิเคราะห์ heuristic ที่เร็วแทนการ parse AST เต็มรูปแบบ การนับใน edge case บางกรณีจึงต่างกันได้หนึ่งถึงสองคะแนน ให้ถือว่า score เป็นสัญญาณความเสี่ยงที่เชื่อถือได้ ไม่ใช่ค่าที่แม่นยำระดับสเปก