2 คะแนน โดย GN⁺ 2025-03-12 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • ระหว่างการรีวิวโค้ดเบสในช่วงหลัง ผู้เขียนพบว่าถึงแม้คุณภาพโค้ดจะดี แต่ก็ยังรู้สึกล้าทางความคิด
    • ปัญหานี้เกี่ยวข้องกับ ความสามารถในการอ่าน มากกว่าความซับซ้อนของโค้ด
  • จึงสรุปออกมาเป็น 8 รูปแบบ เพื่อช่วยเพิ่มความสามารถในการอ่านโค้ด

เมตริกความสามารถในการอ่านโค้ด และเมตริกความซับซ้อนทางเลือก

  • ยังไม่มีเมตริกสากลที่ใช้กันอย่างแพร่หลายสำหรับวัดความสามารถในการอ่านโค้ด
  • ที่มีอยู่ส่วนใหญ่เป็นเพียงงานวิชาการที่ไม่ถูกใช้จริง หรือเป็นความเห็นส่วนบุคคล
  • แทนที่จะสร้างเมตริกใหม่ ผู้เขียนเลือกโฟกัสที่ รูปแบบทางสายตา ซึ่งทุกคนพูดคุยกันได้ง่าย
  • เงื่อนไขสำคัญของเมตริกความซับซ้อน:
    • ต้องใช้ได้กับ source code snippet หรือฟังก์ชันเดี่ยว ๆ
    • โฟกัสที่วิธีการเขียนโค้ด แทนอัลกอริทึมคอมเพล็กซิตี
    • ไม่เน้นองค์ประกอบด้านสไตล์ (เช่น ชื่อตัวแปร เว้นวรรค การย่อหน้า ฯลฯ)

Halstead complexity metric

  • เมตริกความซับซ้อนของโค้ดที่ Maurice Halstead พัฒนาขึ้นในช่วง ทศวรรษ 1970
  • สามารถทำให้วิธีการเขียนโค้ดเป็นตัวเลขได้ โดยไม่ขึ้นกับภาษาและแพลตฟอร์ม
  • คำนวณความยาว ปริมาตร และความยากของโปรแกรมจากจำนวน operator และ operand
  • ค่าที่วัดหลัก ๆ:
    • จำนวน operator ที่ไม่ซ้ำ (n1)
    • จำนวน operand ที่ไม่ซ้ำ (n2)
    • จำนวน operator ทั้งหมด (N1)
    • จำนวน operand ทั้งหมด (N2)
  • ยิ่งมี operator และ operand มาก ความซับซ้อนของโค้ดยิ่งเพิ่มขึ้น
  • เนื่องจากนิยามของ operator และ operand ไม่ได้ชัดเจนเหมือนกันในทุกภาษา จึงควรใช้เครื่องมืออย่างสม่ำเสมอ

ข้อสังเกตที่ได้จาก Halstead complexity

  • ฟังก์ชันสั้นและมีตัวแปรน้อย จะอ่านง่ายกว่า
  • ควรลดการใช้ operator เฉพาะภาษา หรือ syntactic sugar ให้น้อยที่สุด
  • การใช้ chain แบบ functional programming (map/reduce/filter เป็นต้น) หากยาวเกินไปจะทำให้อ่านยากขึ้น

Cognitive Complexity

  • เมตริกความซับซ้อนที่พัฒนาโดย SonarSource
  • เป็นความพยายามในการวัดความยากของการอ่านโค้ดให้แม่นยำขึ้น
  • มี หลักการสำคัญ 3 ข้อ:
    1. shorthand constructs ช่วยลดความยากในการอ่าน
    2. การขัดจังหวะของ flow แบบไม่เป็นเส้นตรงเพิ่มความยาก
    3. control flow ที่ซ้อนกันเพิ่มความยาก

ข้อสังเกตที่ได้จาก Cognitive Complexity

  • shorthand constructs แม้จะกระชับ แต่ก็มีความเสี่ยงต่อบั๊กแฝง
  • เงื่อนไขและ logical operator หากใช้มากเกินไปจะทำให้อ่านยากขึ้น
  • การจัดการ exception เป็นสาเหตุสำคัญที่ทำให้โค้ดซับซ้อนขึ้น
  • โดยทั่วไปควรหลีกเลี่ยง goto แต่ในบางสถานการณ์ก็อาจมีประโยชน์
  • โครงสร้างควบคุมที่ซ้อนกัน ควรลดให้มากที่สุดเท่าที่ทำได้

รูปร่างของฟังก์ชัน รูปแบบ และตัวแปร

  • "รูปร่าง" ทางสายตาของฟังก์ชันมีบทบาทสำคัญต่อความสามารถในการอ่านโค้ด
  • 3 หลักการที่ช่วยเพิ่มความสามารถในการอ่าน:
    1. ใช้ ชื่อตัวแปรที่ชัดเจนและเฉพาะเจาะจง
    • หลีกเลี่ยงการซ้ำชื่อในขอบเขตที่บดบังกัน (shadowing)
    • ใช้ชื่อที่แยกกันได้ชัดเจนทางสายตา (หลีกเลี่ยงชื่อคล้ายกันอย่าง i, j)
    1. ทำให้อายุการใช้งานของตัวแปร (liveness) สั้นลง
    • ยิ่งขอบเขตการใช้งานของตัวแปรสั้นเท่าไรยิ่งดี
    • ตัวแปรที่คงอยู่นานและข้ามขอบเขตของฟังก์ชันจะเพิ่มความซับซ้อน
    1. ใช้รูปแบบโค้ดที่คุ้นเคยซ้ำ
    • การคงรูปแบบโค้ดให้สม่ำเสมอช่วยเพิ่มความสามารถในการอ่าน
    • ควรเลือกใช้รูปแบบที่คุ้นเคยอยู่แล้วก่อนแนวทางใหม่ ๆ

8 รูปแบบที่ช่วยเพิ่มความสามารถในการอ่านโค้ด

  1. ลดจำนวนบรรทัด/operator/operand – ฟังก์ชันเล็กและตัวแปรน้อยช่วยให้อ่านง่ายขึ้น
  2. หลีกเลี่ยงแนวทางใหม่ – รักษารูปแบบที่คุ้นเคยภายในโค้ดเบส
  3. จัดกลุ่ม – แยก function chain ที่ยาว ตัววนซ้ำ หรือส่วนคล้ายกันออกเป็นฟังก์ชันช่วย
  4. ทำเงื่อนไขให้ง่าย – ทำให้เงื่อนไขสั้น และลดการผสม logical operator หลายแบบ
  5. ลดการใช้ goto – หากจำเป็นค่อยใช้แบบจำกัดเฉพาะใน error handling
  6. ลดการซ้อนกัน – ลด logic ที่ซ้อนกัน และหากจำเป็นให้แยกเป็นฟังก์ชัน
  7. ใช้ชื่อตัวแปรที่ชัดเจน – ใช้ชื่อตัวแปรที่เฉพาะเจาะจงและไม่ซ้ำกัน
  8. ทำให้อายุของตัวแปรสั้นลง – ใช้ให้อยู่ในฟังก์ชันช่วงสั้น ๆ และไม่ให้ข้ามขอบเขตฟังก์ชัน

บทสรุป

  • ความสามารถในการอ่านโค้ดเป็นองค์ประกอบสำคัญของคุณภาพโค้ด
  • Halstead และ Cognitive Complexity สามารถช่วยทำให้ปัญหาด้านการอ่านเป็นตัวเลข และชี้ทิศทางการปรับปรุงได้
  • การเขียนโค้ดให้กระชับและชัดเจนช่วยให้บำรุงรักษาได้ง่ายขึ้น และลดโอกาสเกิดบั๊ก
  • การเขียนโค้ดที่ดีที่สุดคือการให้ความสำคัญกับความเรียบง่าย ความสม่ำเสมอ และความชัดเจน

1 ความคิดเห็น

 
GN⁺ 2025-03-12
ความเห็นจาก Hacker News
  • การเชื่อมโครงสร้างการเขียนโปรแกรมเชิงฟังก์ชันอย่าง map, reduce, filter เข้าด้วยกันนั้นกระชับ แต่สายโซ่ที่ยาวมักทำให้ความสามารถในการอ่านลดลง

    • นี่ไม่ใช่สิ่งที่บทความสื่อเป็นนัย
    • ให้ความรู้สึกเหมือนเป็นคำบ่นทั่วไปว่าอะไรที่ไม่คุ้นเคยก็ไม่ดี
    • ถ้าคุ้นเคยขึ้นอีกนิด ก็จะอ่านและเขียนได้ง่ายกว่าวิธีอื่น
    • การเรียนรู้พื้นฐานของการเขียนโปรแกรมเชิงฟังก์ชันเป็นเรื่องสำคัญ
    • ไม่จำเป็นต้องอธิบาย Monad แต่ควรคุ้นเคยพอที่จะไม่ตำหนิ map และ filter แบบส่งเดช
  • แง่มุมสำคัญของโค้ดที่ดีมีลักษณะเชิงคุณภาพและคล้ายงานวรรณกรรม

    • เรื่องนี้อาจทำให้โปรแกรมเมอร์และนักวิชาการที่มีแนวคิดแบบคณิตศาสตร์รู้สึกไม่สบายใจ
    • ชอบทั้งดอสโตเยฟสกีและวูดเฮาส์ แต่สไตล์การเขียนของทั้งคู่ต่างกันมาก
    • การทำความเข้าใจสไตล์ของ codebase ต้องใช้เวลา
  • ปัญหาที่ทำให้เหนื่อยที่สุดเวลาอ่านโค้ดคือความเปลี่ยนแปลงได้ของสถานะ

    • ความสามารถในการ "ตรึง" ตัวแปรให้กำหนดค่าได้เพียงครั้งเดียวถือเป็นของขวัญชิ้นใหญ่
    • กระบวนการทำความเข้าใจเมธอดควรเพิ่มขึ้นแบบทางเดียวจาก 0% ไป 100%
    • เหตุผลที่ GOTO เป็นอันตรายก็เพราะทำให้รู้สถานะของตัวแปรที่เปลี่ยนค่าได้ยาก
  • ฟังก์ชันเล็กและตัวแปรน้อยโดยทั่วไปอ่านง่าย

    • การโฟกัสที่ "ความอ่านง่าย" เอนเอียงไปทางความอ่านง่ายระดับจุลภาค
    • สิ่งนี้ทำให้โค้ดแตกย่อยมากเกินไป
    • ภาษาตระกูล APL อยู่ที่ปลายสุดอีกด้านหนึ่ง
  • TypeScript ทำให้โค้ดอ่านยากขึ้น

    • ถ้า data model ยังคงความเป็น "อะตอมมิก" ก็ยังพอรับได้
    • หากพึ่งพา type inference จะทำให้ตามย้อนฟิลด์กลับไปยังตำแหน่งเดิมได้ยาก
  • ฟังก์ชัน getOddness4 ทำให้เกิดความไม่สมมาตร

    • ฟังก์ชัน getOddness2 ให้ตัวเลือกที่สมมาตร
  • บทความน่าสนใจ แต่ยังไม่ค่อยน่าพอใจ

    • ไม่เห็นด้วยกับความเห็นที่ว่าไม่ควรใช้โอเปอเรเตอร์เฉพาะภาษา หรือ syntax sugar
    • โครงสร้างอย่าง map, reduce, filter ถ้าใช้ดี ๆ สามารถแทนโอเปอเรเตอร์อื่นและลด "ปริมาณ" ลงได้
  • ความพยายามในการนิยามความอ่านง่ายเป็นสิ่งที่น่าชื่นชม

    • สำหรับคนจำนวนมาก น่าจะค้นหามิติที่แท้จริงของความอ่านง่ายได้ผ่านการทดสอบ
  • ความซับซ้อนของโค้ดแสดงออกได้ด้วยขนาดของ syntax tree

    • การลดความซับซ้อนเฉพาะจุดไม่ได้ส่งผลมากนักต่อความซับซ้อนโดยรวม
  • สายโซ่ฟังก์ชันยาว ๆ หรือ callback ควรถูกแบ่งเป็นกลุ่มเล็ก ๆ และใช้ตัวแปรที่ตั้งชื่อได้ดี

    • ทั้งสองเวอร์ชันมีประสิทธิภาพเท่ากัน
    • ความแตกต่างอยู่ที่คอมไพเลอร์