สัปดาห์ที่ 1 บทนำและการป้องกันข้อบกพร่องของซอฟต์แวร์

จุดประสงค์การเรียนรู้

  1. อธิบายความหมายของ การเขียนโปรแกรมขั้นสูง (Advanced Programming) และ คุณภาพซอฟต์แวร์ (Software Quality) ได้
  2. วิเคราะห์จุดเสี่ยงของ ข้อบกพร่องของซอฟต์แวร์ (Software Defect/Bug) ในแต่ละขั้นตอนของ SDLC ได้
  3. เลือกใช้แนวทางป้องกัน Bug เช่น Coding Standard, Code Review, Static Analysis และ Test Case Design ได้
  4. สร้าง checklist และตัวอย่างการปรับปรุงโค้ดเพื่อลดความเสี่ยงต่อ Bug ได้

ภาพรวมบทเรียน

บทเรียนนี้เน้นการเปลี่ยนมุมมองจากการเขียนโปรแกรมแบบ "ให้รันได้" ไปสู่การเขียนโปรแกรมแบบ "พร้อมดูแลและขยายต่อได้" ผู้เรียนจะเห็นว่า Bug ไม่ได้เกิดจากการเขียนโค้ดผิดเพียงอย่างเดียว แต่อาจเกิดตั้งแต่การเข้าใจความต้องการผิด การออกแบบไม่ชัดเจน การทดสอบไม่ครอบคลุม หรือการแก้ไขภายหลังโดยไม่มีระบบป้องกันผลกระทบ

คำสำคัญของบทเรียน


1. ความหมายของการเขียนโปรแกรมขั้นสูง

การเขียนโปรแกรมขั้นสูง (Advanced Programming) คือการพัฒนาโปรแกรมโดยคำนึงถึงคุณภาพของระบบทั้งวงจร ไม่ใช่เพียงเขียนคำสั่งให้ได้ผลลัพธ์เฉพาะหน้า โปรแกรมที่ดีควรมีคุณสมบัติดังนี้

%%{init: {'theme': 'base', 'themeVariables': {
  'background': '#282828',
  'primaryColor': '#3c3836',
  'primaryTextColor': '#fbf1c7',
  'primaryBorderColor': '#fabd2f',
  'lineColor': '#83a598',
  'secondaryColor': '#504945',
  'tertiaryColor': '#665c54',
  'fontFamily': 'Arial'
}}}%%
mindmap
  root((Software Quality
คุณภาพซอฟต์แวร์)) Correctness
ความถูกต้อง Expected result
ผลลัพธ์ตรงโจทย์ Requirement match
ตรงตามข้อกำหนด Reliability
ความน่าเชื่อถือ Stable behavior
ทำงานสม่ำเสมอ Error tolerance
รับมือข้อผิดพลาด Maintainability
ดูแลรักษาได้ Readable code
อ่านง่าย Clear modules
แบ่งส่วนชัดเจน Security
ความปลอดภัย Input validation
ตรวจข้อมูลเข้า Protect data
ป้องกันข้อมูลสำคัญ Scalability
ขยายต่อได้ Add features
เพิ่มฟีเจอร์ More data
รองรับข้อมูลเพิ่ม

ตารางเปรียบเทียบโค้ดที่รันได้กับโค้ดที่มีคุณภาพ

ประเด็น โค้ดที่รันได้เท่านั้น โค้ดที่มีคุณภาพ
การตั้งชื่อ ใช้ชื่อสั้น เช่น x, tmp, a1 ใช้ชื่อสื่อความหมาย เช่น totalPrice, studentCount
การตรวจสอบข้อมูล เชื่อว่าผู้ใช้กรอกถูกเสมอ ตรวจข้อมูลว่าง ผิดชนิด และเกินขอบเขตก่อนประมวลผล
โครงสร้าง รวมทุกอย่างในฟังก์ชันเดียว แยกฟังก์ชันตามหน้าที่
การแก้ไข แก้จุดหนึ่งกระทบหลายจุด แก้ไขเฉพาะส่วนได้ง่าย
การทดสอบ ทดลองรันด้วยตนเองแบบไม่เป็นระบบ มี test case ที่ตรวจซ้ำได้

2. Timeline แนวคิดคุณภาพซอฟต์แวร์

การป้องกัน Bug พัฒนามาพร้อมกับวิธีการสร้างซอฟต์แวร์ ตั้งแต่ยุคที่เน้นให้โปรแกรมทำงานได้ ไปสู่ยุคที่เน้นคุณภาพ กระบวนการ เครื่องมืออัตโนมัติ และการทำงานร่วมกันของทีม

%%{init: {'theme': 'base', 'themeVariables': {
  'background': '#282828',
  'primaryColor': '#3c3836',
  'primaryTextColor': '#fbf1c7',
  'primaryBorderColor': '#fabd2f',
  'lineColor': '#8ec07c',
  'secondaryColor': '#504945',
  'tertiaryColor': '#665c54',
  'fontFamily': 'Arial'
}}}%%
flowchart LR
  subgraph E1[Early Programming Era
ยุคเริ่มต้นการเขียนโปรแกรม] A[Run the program
เน้นให้โปรแกรมรันได้] end subgraph E2[Structured Era
ยุคโครงสร้างโปรแกรม] B[Modular design
แบ่งโปรแกรมเป็นส่วน] end subgraph E3[Quality Era
ยุคคุณภาพซอฟต์แวร์] C[Testing and review
ทดสอบและตรวจโค้ด] end subgraph E4[Modern DevOps Era
ยุคเครื่องมืออัตโนมัติ] D[Static analysis and CI
ตรวจอัตโนมัติและส่งมอบต่อเนื่อง] end A --> B --> C --> D

3. SDLC และจุดเสี่ยงของ Bug

SDLC (Software Development Life Cycle) คือวงจรการพัฒนาซอฟต์แวร์ตั้งแต่การวิเคราะห์ความต้องการจนถึงการบำรุงรักษา Bug สามารถเกิดขึ้นได้ทุกช่วง หากเกิดตั้งแต่ต้นทาง เช่น เข้าใจ requirement ผิด ต้นทุนการแก้ไขจะสูงขึ้นเพราะความผิดพลาดจะส่งผลต่อการออกแบบ โค้ด และการทดสอบ

%%{init: {'theme': 'base', 'themeVariables': {
  'background': '#282828',
  'primaryColor': '#3c3836',
  'primaryTextColor': '#fbf1c7',
  'primaryBorderColor': '#fabd2f',
  'lineColor': '#fe8019',
  'secondaryColor': '#504945',
  'tertiaryColor': '#665c54',
  'fontFamily': 'Arial'
}}}%%
flowchart LR
  A[Requirement Analysis
วิเคราะห์ความต้องการ] --> B[Design
ออกแบบระบบ] B --> C[Implementation
เขียนโปรแกรม] C --> D[Testing
ทดสอบ] D --> E[Deployment
ส่งมอบ] E --> F[Maintenance
บำรุงรักษา] A -. Risk .-> A1[Wrong requirement
เข้าใจโจทย์ผิด] B -. Risk .-> B1[Poor design
ออกแบบไม่เหมาะสม] C -. Risk .-> C1[Logic error
ตรรกะผิด] D -. Risk .-> D1[Missing test case
ทดสอบไม่ครอบคลุม] F -. Risk .-> F1[Regression bug
แก้แล้วกระทบของเดิม]

ตารางตัวอย่าง Bug ตามขั้นตอนของ SDLC

ขั้นตอน ตัวอย่าง Bug แนวทางป้องกัน
Requirement ตีความเงื่อนไขส่วนลดไม่ครบ เขียนตัวอย่าง input/output และยืนยัน requirement
Design เลือกโครงสร้างข้อมูลไม่เหมาะ วาด flow และโครงสร้างข้อมูลก่อนเขียนโค้ด
Implementation เงื่อนไข if ไม่ครอบคลุมค่าขอบเขต ตรวจ boundary case และใช้ code review
Testing ทดสอบเฉพาะข้อมูลปกติ เพิ่ม normal, edge และ invalid case
Maintenance แก้โค้ดแล้วฟีเจอร์เดิมเสีย ใช้ regression test และ version control

4. สมการต้นทุนของ Bug

แนวคิดสำคัญของ Bug Prevention คือ ยิ่งพบ Bug ช้า ต้นทุนการแก้ไขยิ่งสูง สามารถอธิบายเชิงแนวคิดด้วยสมการต่อไปนี้

C = C 0 × r t

คำอธิบายตัวแปร:

ตัวอย่างการตีความ: หากพบ Bug ตั้งแต่ช่วงเขียนโค้ด อาจแก้ได้เร็ว แต่หากพบหลังส่งมอบแล้ว อาจต้องตรวจ requirement ใหม่ แก้โค้ด ทดสอบซ้ำ ทำเอกสารใหม่ และแจ้งผู้ใช้ จึงมีต้นทุนสูงกว่า


5. ประเภทของข้อบกพร่องในโปรแกรม

การแยกประเภทของ Bug ช่วยให้เลือกวิธีตรวจสอบได้เหมาะสม ข้อผิดพลาดที่พบบ่อยแบ่งได้ดังนี้

ประเภท ความหมาย ตัวอย่าง วิธีตรวจ
Syntax Error เขียนคำสั่งผิดตามกฎภาษา ลืมวงเล็บ ใช้ keyword ผิด Compiler, IDE
Runtime Error โปรแกรมรันแล้วเกิดข้อผิดพลาด หารด้วยศูนย์ เปิดไฟล์ไม่เจอ Debugger, Exception Handling
Logic Error โปรแกรมรันได้แต่ผลลัพธ์ผิด เงื่อนไขผิด สูตรคำนวณผิด Test Case, Code Review
Requirement Error โปรแกรมไม่ตรงความต้องการ ทำฟีเจอร์ครบแต่ผิดกติกาธุรกิจ Requirement Review, Prototype

6. ตัวอย่างโค้ด: Logic Error และการป้องกัน

ตัวอย่างต่อไปนี้เป็นฟังก์ชันคำนวณส่วนลดจากราคา สถานการณ์สมมติคือร้านค้ากำหนดว่า ซื้อครบ 1,000 บาทขึ้นไป ได้ส่วนลด 5%

#include <iostream>
using namespace std;

// คำนวณส่วนลดจากราคาสินค้า
// Requirement: ถ้าราคา >= 1000 ต้องได้ส่วนลด 5%
double calculateDiscount(double price) {
    // ตรวจสอบข้อมูลเข้าที่ไม่สมเหตุสมผล
    if (price < 0) {
        return 0;
    }

    // ใช้ >= เพื่อให้ราคา 1000 ได้รับส่วนลดตาม requirement
    if (price >= 1000) {
        return price * 0.05;
    }

    return 0;
}

int main() {
    // ตัวอย่างการใช้งาน
    double price1 = 1500;
    double price2 = 1000;
    double price3 = 999;

    cout << "Discount for 1500 = " << calculateDiscount(price1) << endl;
    cout << "Discount for 1000 = " << calculateDiscount(price2) << endl;
    cout << "Discount for 999 = " << calculateDiscount(price3) << endl;

    return 0;
}

ผลลัพธ์ที่คาดหวัง:

Input Expected Discount เหตุผล
1500 75 มากกว่า 1000 ได้ส่วนลด 5%
1000 50 ครบ 1000 จึงได้ส่วนลด
999 0 ยังไม่ถึงเงื่อนไข
-100 0 ข้อมูลไม่สมเหตุสมผล

7. Bug Prevention Pipeline

Bug Prevention Pipeline คือแนวทางป้องกันข้อผิดพลาดตั้งแต่ก่อนเริ่มเขียนโค้ดจนถึงหลังปรับปรุงโปรแกรม

%%{init: {'theme': 'base', 'themeVariables': {
  'background': '#282828',
  'primaryColor': '#3c3836',
  'primaryTextColor': '#fbf1c7',
  'primaryBorderColor': '#fabd2f',
  'lineColor': '#b8bb26',
  'secondaryColor': '#504945',
  'tertiaryColor': '#665c54',
  'fontFamily': 'Arial'
}}}%%
flowchart TD
  A[Clear Requirement
ความต้องการชัดเจน] --> B[Design First
ออกแบบก่อนเขียน] B --> C[Coding Standard
มาตรฐานโค้ด] C --> D[Static Analysis
ตรวจโค้ดอัตโนมัติ] D --> E[Code Review
ตรวจโดยเพื่อนร่วมทีม] E --> F[Test Case
ชุดทดสอบ] F --> G[Refactor
ปรับโครงสร้าง] G --> H[Better Software
ซอฟต์แวร์คุณภาพดีขึ้น]

แนวทางหลัก:

  1. Coding Standard
    กำหนดกติกาการเขียนโค้ด เช่น การตั้งชื่อ การจัดย่อหน้า การแยกไฟล์ และรูปแบบ comment เพื่อให้โค้ดอ่านง่ายและตรวจสอบง่าย

  2. Static Analysis
    ใช้เครื่องมือช่วยตรวจ warning, unused variable, possible null pointer, memory leak pattern หรือ style ที่ไม่ตรงมาตรฐาน โดยไม่ต้องรันโปรแกรม

  3. Code Review
    ให้ผู้อื่นช่วยอ่านโค้ดเพื่อตรวจ logic, requirement, input validation และความซับซ้อนที่ผู้เขียนอาจมองข้าม

  4. Test Case Design
    ออกแบบกรณีทดสอบให้ครอบคลุม normal case, edge case และ invalid case

  5. Modular Design
    แยกโปรแกรมเป็นฟังก์ชันหรือโมดูลที่มีหน้าที่ชัดเจน เพื่อลดผลกระทบเมื่อแก้ไข


8. Checklist สำหรับใช้ก่อนส่งงาน

รายการตรวจ คำถามที่ต้องตอบ
Requirement โปรแกรมทำงานตรงโจทย์ทุกเงื่อนไขหรือไม่
Input Validation ตรวจข้อมูลว่าง ผิดชนิด หรือนอกขอบเขตแล้วหรือไม่
Naming ชื่อตัวแปร ฟังก์ชัน และไฟล์สื่อความหมายหรือไม่
Function Size ฟังก์ชันยาวเกินไปหรือทำหลายหน้าที่หรือไม่
Duplication มีโค้ดซ้ำที่ควรแยกเป็นฟังก์ชันหรือไม่
Error Handling มีการจัดการกรณีผิดพลาดที่คาดเดาได้หรือไม่
Test Case มี normal, edge และ invalid case หรือไม่

Workshop

ให้นักศึกษาวิเคราะห์โปรแกรมตัวอย่างที่มีข้อผิดพลาด แล้วดำเนินการตามลำดับ:

  1. ระบุ Bug อย่างน้อย 5 จุด
  2. จัดประเภท Bug ว่าเป็น Syntax, Runtime, Logic หรือ Requirement Error
  3. อธิบายสาเหตุและผลกระทบของ Bug
  4. เสนอวิธีป้องกันด้วย Coding Standard, Code Review, Static Analysis หรือ Test Case
  5. ปรับปรุงโค้ดโดยไม่เปลี่ยน requirement หลักของโปรแกรม

แบบฝึกหัด

  1. เขียน Code Review Checklist สำหรับงานโปรแกรม 1 หน้า
  2. สร้าง test case อย่างน้อย 5 กรณีสำหรับฟังก์ชัน calculateDiscount
  3. ปรับปรุงโค้ดตัวอย่างให้รองรับ input ที่ผิดปกติ เช่น ราคาติดลบหรือข้อมูลว่าง

ชิ้นงานที่ต้องส่ง

รายงานสั้น 1-2 หน้าในรูปแบบ Markdown ประกอบด้วย:

คำถามทบทวน

  1. เหตุใดการป้องกัน Bug จึงสำคัญกว่าการรอแก้ Bug ภายหลัง
  2. Logic Error แตกต่างจาก Syntax Error อย่างไร
  3. Code Review ช่วยพบปัญหาที่ compiler ตรวจไม่พบได้อย่างไร
  4. ทำไม edge case เช่น 1000 ในตัวอย่างส่วนลดจึงสำคัญ
  5. หากโปรแกรมผ่าน compiler แล้ว ยังจำเป็นต้องมี test case หรือไม่ เพราะเหตุใด

กลับรายวิชา