Sign in

Part of 10306103 หลักการเขียนโปรแกรมเบื้องต้น (Fundamentals of Programming)

2026-10-22 · updated 2026-10-30 · 25 min read

บทที่ 15 โครงงานบูรณาการ: พัฒนาโปรแกรมที่มีคุณภาพแบบครบวงจร

ShareFacebookXLinkedInLINE

แผนบริหารการสอนประจำบทที่ 15

บทที่ 15 เป็นบทสุดท้ายและเป็นโครงงานบูรณาการ (capstone) ที่รวบรวมทักษะทั้งหมดที่ฝึกสะสมมาตั้งแต่บทที่ 1 ถึงบทที่ 14 เข้าด้วยกันในงานพัฒนาโปรแกรมชิ้นเดียว บทนี้ไม่แนะนำแนวคิดใหม่ แต่ให้วิเคราะห์โจทย์ ออกแบบ พัฒนา ทดสอบ และส่งมอบโปรแกรมที่มีคุณภาพแบบครบวงจร โจทย์ที่ใช้คือระบบจัดการคะแนนนักศึกษาที่เก็บข้อมูลด้วยอาร์เรย์ของ struct แล้วคำนวณค่าเฉลี่ย หาคะแนนสูงสุด เรียงลำดับคะแนน และตรวจความถูกต้องด้วยการทดสอบระดับหน่วย ทั้งหมดนี้บูรณาการ struct จากบทที่ 9 อาร์เรย์จากบทที่ 7 ฟังก์ชันจากบทที่ 6 เงื่อนไขและการวนซ้ำจากบทที่ 4 และ 5 การเรียงลำดับและการค้นหาจากบทที่ 10 การทดสอบจากบทที่ 11 และมาตรฐานโค้ดกับแนวคิดการจัดการเวอร์ชันจากบทที่ 13 พร้อมทั้งพัฒนาด้วยความเข้าใจของตนเองอย่างซื่อสัตย์ตามบทที่ 14 บทนี้จึงบูรณาการทั้ง CLO1 ถึง CLO5

ผลลัพธ์ข้อความหลักฐานCLO
LLO15.1วิเคราะห์โจทย์และออกแบบโปรแกรมบูรณาการที่ใช้ตัวแปร การรับ-แสดงผล โครงสร้างควบคุม ฟังก์ชัน อาร์เรย์ และ structงานโครงงาน ก (ปฏิบัติการที่ 15 Lab A)CLO1, CLO2
LLO15.2พัฒนาโปรแกรมที่คอมไพล์ถูกต้องตามไวยากรณ์ มีประสิทธิภาพ และผ่านการทดสอบระดับหน่วยงานโครงงาน ข (ปฏิบัติการที่ 15 Lab A/B)CLO3
LLO15.3พัฒนาโค้ดด้วยความเข้าใจของตนเองและอ้างอิงแหล่งที่มาอย่างซื่อสัตย์งานโครงงาน ค (ปฏิบัติการที่ 15 Lab B)CLO4
LLO15.4ส่งมอบผลงานตามมาตรฐานการเขียนโค้ด พร้อมบันทึกการเปลี่ยนแปลง (commit message) ที่สื่อความหมายงานโครงงาน ง (ปฏิบัติการที่ 15 Lab B)CLO5

CLO ที่เกี่ยวข้อง (ข้อความจากหลักสูตร):

  • CLO1 อธิบายและใช้ตัวแปร ชนิดข้อมูล การรับ-แสดงผล และโครงสร้างควบคุม (if/else, switch, loop) เพื่อแก้ปัญหาและได้ผลลัพธ์ที่ถูกต้อง
  • CLO2 อธิบายขั้นตอนการทำงาน (Logic) ของภาษาโปรแกรมและเลือกใช้โครงสร้างควบคุม (Control Structure) ให้สอดคล้องกับโจทย์ที่กำหนดได้
  • CLO3 สามารถเขียนโค้ดได้ถูกต้องตามหลักไวยากรณ์ (Syntax) และมีประสิทธิภาพ (Efficiency)
  • CLO4 พัฒนาโค้ดด้วยความเข้าใจของตนเอง หลีกเลี่ยงการคัดลอกจากแหล่งอื่นโดยไม่ศึกษา เพื่อให้ผลงานถูกต้องและมีความซื่อสัตย์
  • CLO5 ปฏิบัติตามมาตรฐานการเขียนโค้ด (Indentation, Naming, Commenting, Git commits ที่มีข้อความที่สื่อความหมาย)

บทที่ 15 รวบรวมหลักฐาน CLO1–CLO5 ใน C15-WS01 รวม 20 คะแนน คิดเป็น 10% ของรายวิชา: ก 7 · ข 7 · ค 2 · ง 4 ทุกข้อเป็นคะแนน งานข้อ ค ประเมินความเข้าใจและการระบุที่มา ไม่ใช่ด่านผ่าน/ไม่ผ่าน

CLO4 สรุปผลแบบถ่วงน้ำหนัก เป้าหมาย 60% เพื่อสะท้อนผลการเรียนรู้เท่านั้น ไม่เป็นเงื่อนไขผ่านรายวิชา และไม่ทำให้ได้เกรด I โดยอัตโนมัติ

วิธีสอน

  1. ทบทวนภาพรวมของทักษะทั้งหมดที่ฝึกมาตั้งแต่บทที่ 1 ถึงบทที่ 14 แล้วชี้ให้เห็นว่าโครงงานบูรณาการเป็นการนำทุกทักษะมาประกอบเป็นงานชิ้นเดียวที่มีคุณภาพ
  2. อธิบายโจทย์ระบบจัดการคะแนนนักศึกษา แล้ววิเคราะห์ร่วมกันแบบ input-process-output ว่าโปรแกรมรับข้อมูลอะไร ประมวลผลอย่างไร และแสดงผลอะไร
  3. สาธิตการแบ่งงานใหญ่เป็นฟังก์ชันย่อยที่ทดสอบได้ ได้แก่ การคำนวณค่าเฉลี่ย การหาคะแนนสูงสุด การเรียงลำดับ และการแสดงรายชื่อ พร้อมชี้ว่าแต่ละฟังก์ชันมาจากทักษะบทใด
  4. สาธิตการพัฒนาทีละฟังก์ชันแล้วคอมไพล์ด้วย -Wall เพิ่มการทดสอบตรวจค่าเฉลี่ย และไล่แก้เมื่อผลไม่ตรงตามที่คาดหวัง
  5. อธิบายการส่งมอบที่ครบวงจร ได้แก่ การจัดรูปแบบตามมาตรฐานโค้ด การบันทึกการเปลี่ยนแปลงที่สื่อความหมาย การบันทึกแหล่งที่ศึกษา และการพัฒนาด้วยความเข้าใจของตนเอง
  6. มอบหมายให้ลงมือทำโครงงานตามใบงาน โดยกำหนดจุดตรวจระหว่างทางเพื่อให้ข้อมูลป้อนกลับทั้งด้านการออกแบบ การทดสอบ มาตรฐานโค้ด และการส่งมอบที่มีคุณภาพ

สื่อการเรียนการสอน

  • เครื่องคอมพิวเตอร์ที่เข้าถึงแพลตฟอร์มการเรียนของรายวิชาสำหรับเขียน คอมไพล์ และรันโปรแกรมภาษา C (มาตรฐาน C11) บนเว็บ
  • เอกสารประกอบบทที่ 15 และโจทย์โครงงานฉบับเต็ม (ผู้สอนกำหนด)
  • รูบริก C15-WS01 ในหัวข้อการประเมินผล รวม 20 คะแนน โครงงานมีน้ำหนัก 10% ตามมติผู้สอน 30 ต.ค. 2569
  • ไฟล์ตัวอย่างโปรแกรมอ้างอิง ch15_capstone.c สำหรับผู้สอนใช้เทียบผลงาน

การวัดผลและการประเมินผล

กำหนดส่ง: งานโครงงาน ก–ง ภายใน 23:59 น. ของวันก่อนวันปฏิบัติการในสัปดาห์ที่ 16 ส่วนชุดฝึกท้ายบทและแบบฝึกหัดวัดความเข้าใจของบทนี้ ภายใน 23:59 น. ของวันก่อนวันบรรยายในสัปดาห์ที่ 16 ดูวันที่จริงของตอนเรียนได้ในห้องเรียนออนไลน์ (มติผู้สอน 28 ต.ค. 2569)

ข้องานหลักฐานผลลัพธ์CLO
กวิเคราะห์โจทย์แบบ input-process-output และออกแบบฟังก์ชันกับ struct ของระบบจัดการคะแนนเอกสารออกแบบ + โครงฟังก์ชันและ structLLO15.1CLO1, CLO2
ขพัฒนาโปรแกรมให้คอมไพล์ผ่าน ทำงานถูกต้อง และเพิ่มการทดสอบระดับหน่วยไฟล์ซอร์สโค้ด + ผลการคอมไพล์/รัน + ผลการทดสอบLLO15.2CLO3
คบันทึกแหล่งที่ศึกษาและเครื่องมือช่วย และพัฒนาด้วยความเข้าใจของตนเองบันทึกการพัฒนา + การอ้างอิงLLO15.3CLO4
งส่งมอบโค้ดตามมาตรฐานการเขียนโค้ด พร้อมบันทึกการเปลี่ยนแปลงแต่ละครั้งด้วยข้อความ commit ที่สื่อความหมายไฟล์ซอร์สโค้ดตามมาตรฐาน + บันทึกการเปลี่ยนแปลงLLO15.4CLO5

คะแนนโครงงาน C15-WS01 (20 คะแนน)

งาน ก–ง ใช้หลักฐานชิ้นงานร่วมกัน แต่ให้คะแนนแยกตามองค์ประกอบ ไม่ต้องผ่านข้อใดก่อนจึงให้คะแนนข้ออื่น คะแนนหมวดโครงงาน = คะแนนรวม / 20 × 10

ข้อผลลัพธ์คะแนนเต็ม
ก วิเคราะห์และออกแบบLLO15.17
ข พัฒนาและทดสอบLLO15.27
ค ความเข้าใจและที่มาของงานLLO15.32
ง มาตรฐานและการส่งมอบLLO15.44

รูบริกงาน ก (0–7)

องค์ประกอบคะแนนเต็ม
input-process-output: ระบุข้อมูลเข้า กระบวนการ และผลแสดง ข้อละ 13
แบ่งฟังก์ชันตามหน้าที่ 1; ระบุพารามิเตอร์และผลคืนที่สอดคล้องกัน 12
ออกแบบ struct ครอบคลุมข้อมูล 1; อธิบายการใช้อาร์เรย์และลำดับงาน 12

รูบริกงาน ข (0–7)

องค์ประกอบคะแนนเต็ม
คอมไพล์ได้ด้วย -Wall 1; ไม่มี warning หรืออธิบายและแก้ warning ที่พบได้ 12
แสดงรายการ คำนวณค่าเฉลี่ย หาคะแนนสูงสุด และเรียงคะแนนได้ถูกต้อง ข้อละ 0.52
ทดสอบระดับหน่วย 1; ตรวจกรณีขอบตามขอบเขตงาน 1; แนบผลจริงเทียบค่าคาดพร้อมข้อจำกัด 13

รูบริกงาน ค: ความเข้าใจและที่มาของงาน (0–2)

องค์ประกอบ1 คะแนน0.5 คะแนน0 คะแนน
อธิบายการพัฒนาและตรวจความเข้าใจอธิบายฟังก์ชันที่เลือก พร้อมไล่ค่าหรือปรับตัวอย่างเล็กน้อยและคาดผลได้ตรงโค้ดอธิบายได้บางส่วน แต่เหตุผลหรือการไล่ค่ายังไม่ครบไม่มีหลักฐานคำอธิบายที่ตรวจได้
บันทึกที่มาและบทบาทความช่วยเหลือระบุแหล่ง/ส่วนที่ใช้และส่วนที่ปรับเองตามจริง หรืออธิบายขอบเขตที่ทำเองโดยไม่ใช้แหล่งภายนอกมีบันทึกแต่ยังเชื่อมที่มาหรือบทบาทกับชิ้นงานได้ไม่ครบไม่มีบันทึกที่มาหรือขอบเขตการพัฒนา

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

ไม่บังคับใช้ AI หรือ Git และไม่เรียกบทสนทนา AI ส่วนตัว ประวัติการกดแป้นพิมพ์ หรือบัญชีส่วนตัวเป็นหลักฐาน ถ้าคะแนนข้อ ค เป็น 0 ยังให้คะแนน ก ข ง ตามหลักฐาน ไม่ทำให้ได้เกรด I อัตโนมัติ

รูบริกงาน ง (0–4)

องค์ประกอบคะแนนเต็ม
ย่อหน้าสม่ำเสมอ 0.5; ตั้งชื่อสื่อความหมาย 0.51
คอมเมนต์อธิบายเหตุผลหรือข้อจำกัด ไม่เพียงทวนโค้ด1
ส่งซอร์สครบพร้อมวิธีคอมไพล์และรันที่ตรวจตามได้1
บันทึกการเปลี่ยนแปลงอย่างน้อยสองรายการที่สื่อว่าแก้อะไร ข้อละ 0.5; ใช้ข้อความธรรมดาแทน Git ได้1

องค์ประกอบเต็ม 1 ให้ 1 เมื่อครบ 0.5 เมื่อมีหลักฐานถูกต้องบางส่วน และ 0 เมื่อไม่มี องค์ประกอบเต็ม 0.5 ให้ 0.5 เมื่อถูกต้องและ 0 เมื่อไม่มี รวมแต่ละข้อไม่เกินคะแนนเต็ม

CLO4 สรุปผลแบบถ่วงน้ำหนัก เป้าหมาย 60% เพื่อสะท้อนผลการเรียนรู้เท่านั้น ไม่เป็นเงื่อนไขผ่านรายวิชา และไม่ทำให้ได้เกรด I โดยอัตโนมัติ

ใช้รูบริกนี้กับงานที่ประกาศเกณฑ์ก่อนส่ง ไม่แปลงผลผ่าน/ไม่ผ่านเดิมเป็นคะแนนย้อนหลังโดยอัตโนมัติ ผู้สอนต้องตรวจหลักฐานและบันทึกคะแนนตามเกณฑ์ที่ประกาศ

ส่งช้าใช้ประกาศของรายวิชา: หัก 10% ของคะแนนงานต่อสัปดาห์ สูงสุด 50% และรับถึงสิ้นสัปดาห์ที่ 15 งานส่งกลับแก้นับเวลาจากครั้งแรก ผู้สอนต้องแจ้งวันปฏิทินจริงให้ตรงกับกำหนดโครงงานสัปดาห์ที่ 16 ก่อนเริ่มงาน

ดูคะแนนและข้อเสนอแนะได้ที่หน้า “คะแนนของฉัน” ไม่ใช้กติกาส่งแก้จนวันส่งเกรดแบบเงื่อนไขผ่าน CLO4 เดิม

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

บทนำ

โครงงานบูรณาการคือการนำทักษะที่ฝึกแยกส่วนมาตลอดรายวิชามาประกอบเป็นงานพัฒนาโปรแกรมชิ้นเดียวที่มีคุณภาพแบบครบวงจร ตั้งแต่การวิเคราะห์โจทย์ การออกแบบด้วยฟังก์ชันและโครงสร้างข้อมูล การพัฒนาและทดสอบ ไปจนถึงการส่งมอบตามมาตรฐานและการจัดการเวอร์ชัน บทนี้ไม่แนะนำแนวคิดใหม่ แต่ให้ประยุกต์ทุกทักษะที่มีอยู่แล้วเข้ากับโจทย์เดียว โจทย์ที่ใช้คือระบบจัดการคะแนนนักศึกษาที่เก็บข้อมูลด้วยอาร์เรย์ของ struct แล้วคำนวณค่าเฉลี่ย หาคะแนนสูงสุด และเรียงลำดับคะแนน ซึ่งครอบคลุม struct อาร์เรย์ ฟังก์ชัน เงื่อนไข การวนซ้ำ การเรียงลำดับ และการค้นหา จากนั้นเพิ่มการทดสอบระดับหน่วยเพื่อยืนยันความถูกต้อง ปรับให้เป็นไปตามมาตรฐานโค้ด บันทึกการเปลี่ยนแปลงที่สื่อความหมาย และพัฒนาด้วยความเข้าใจของตนเองอย่างซื่อสัตย์ โครงงานนี้จึงบูรณาการ CLO ทั้งห้าข้อของรายวิชาไว้ในงานเดียว

บทที่ 15 เป็นบทสุดท้ายของรายวิชา จึงเป็นการสรุปรวบยอดและปิดเล่ม ไม่มีทักษะใหม่ที่ส่งต่อไปยังบทถัดไป แต่เป็นการประมวลว่าทักษะทั้งหมดที่เรียนมาทำงานร่วมกันอย่างไรในงานจริง

สิ่งที่ได้จากบทนี้ที่มา
การวิเคราะห์โจทย์และออกแบบระบบด้วยฟังก์ชันและ structบูรณาการบท 6, 7, 9
การพัฒนา คอมไพล์ และทดสอบโปรแกรมที่ถูกต้องบูรณาการบท 4, 5, 10, 11
การส่งมอบตามมาตรฐานโค้ดและการบันทึกการเปลี่ยนแปลงที่สื่อความหมายบูรณาการบท 13
การพัฒนาด้วยความเข้าใจของตนเองอย่างซื่อสัตย์บูรณาการบท 14

ความรู้พื้นฐาน เครื่องมือ และรุ่นซอฟต์แวร์

บทนี้ต้องใช้สภาพแวดล้อมสำหรับเขียน คอมไพล์ และรันโปรแกรมภาษา C และต้องมีความรู้จากทุกบทที่ผ่านมา ได้แก่ ตัวแปรและชนิดข้อมูล การรับ-แสดงผล เงื่อนไขและการวนซ้ำ ฟังก์ชัน อาร์เรย์ struct การเรียงลำดับและการค้นหา การทดสอบระดับหน่วย มาตรฐานการเขียนโค้ด และแนวคิดการจัดการเวอร์ชัน ตารางด้านล่างเป็นเครื่องมือสำหรับชุดสอนฉบับนี้ แยกจากบันทึกตัวอย่างเดิมซึ่งระบุ GCC 13.3.0

เครื่องมือรุ่นที่ทดสอบหน้าที่
คอมไพเลอร์ GCC6.3.0 (MinGW-w64 Windows), 12.2.0 (Debian Linux)ตรวจโปรแกรมตัวอย่างในชุดสอนฉบับนี้
Git (กิจกรรมเสริม)ดูรุ่นและผลตรวจในบทที่ 13ไม่บังคับใช้ในงานส่งบทนี้
มาตรฐานภาษาC11 (-std=c11)กำหนดไวยากรณ์ภาษา C ที่ใช้อ้างอิง
⚠️Warning

บันทึกโปรแกรมอ้างอิงเดิมระบุ GCC 13.3.0 บน Linux ส่วนชุดสอนฉบับนี้ตรวจซ้ำด้วยเครื่องมือตามตาราง ไม่อ้างว่าได้รันรุ่นเดิมซ้ำ ผลการรันที่แสดงมาจากการรันจริงของโปรแกรมอ้างอิง ส่วนรหัส commit (hash) ของ Git ที่อาจปรากฏในปฏิบัติการเป็นเพียงตัวอย่าง รหัสขึ้นกับวัตถุ commit รวมข้อมูลผู้เขียน เวลา parent และข้อความ ถ้าวัตถุเหมือนกันทุกส่วนจะได้รหัสเดียวกัน ไม่จำเป็นต้องเปลี่ยนทุกครั้ง หากใช้เครื่องมือหรือระบบปฏิบัติการอื่น ข้อความบางส่วนอาจต่างกันเล็กน้อย แต่ซอร์สโค้ดภาษา C มาตรฐานและลำดับคำสั่งใช้ได้เหมือนกัน

วิธีใช้ชุดสอนบทสุดท้าย

เริ่มจาก บทที่ 14 ความซื่อสัตย์ในการพัฒนาโค้ด แล้วนำบันทึกที่มาและผลตรวจมาใช้กับโครงงาน บทนี้ให้วิเคราะห์ ออกแบบ พัฒนา และส่งมอบงานที่ตรวจซ้ำได้ตาม LLO15.1–15.4

ทำใบงานขอบเขตก่อนเขียนโค้ด ใช้ตัวอย่างเป็นเครื่องมือทบทวน จากนั้นลงมือทำ Lab A–B ของตน พร้อมเก็บหลักฐานงาน ก–ง ไม่เพิ่มบัญชี Git หรือบริการ AI เป็นข้อบังคับ โครงงานยังมีน้ำหนัก 10% และใช้คะแนน ก 7 · ข 7 · ค 2 · ง 4 โดยไม่มีเงื่อนไขผ่าน CLO4

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

ใบงาน 1: กำหนดขอบเขตชิ้นงานที่ตรวจรับได้

งานหลักของ Lab ใช้ข้อมูลคงที่ในโปรแกรม แสดงรายการ คำนวณค่าเฉลี่ย หาคะแนนสูงสุด และเรียงคะแนน ฟังก์ชันค้นหาเป็นตัวเลือกงาน A.1 การรับข้อมูลผ่านเมนูหรือบันทึกแฟ้มยังไม่ใช่ความสามารถที่ตัวอย่างนี้ทำแล้ว

ความต้องการของงานข้อมูลเข้าผลที่ต้องเห็นฟังก์ชันกรณีตรวจ
แสดงรายการ
คำนวณค่าเฉลี่ย
หาคะแนนสูงสุด
เรียงคะแนน
ส่วนเพิ่มที่ตนเลือก

แยกช่อง “ต้องทำตามโจทย์” กับ “เลือกขยาย” และระบุสิ่งที่ยังไม่รองรับ ผู้สอนยังต้องประกาศโจทย์ฉบับเต็มกับรูบริกละเอียดก่อนใช้ให้คะแนน ตารางนี้ช่วยวางแผน ไม่ใช่การประกาศเกณฑ์คะแนนใหม่

15.1 ภาพรวมโครงงานและการวิเคราะห์โจทย์

ระดับที่ 1: โครงงานบูรณาการเป็นงานที่รวมทุกทักษะของรายวิชาเข้าด้วยกันในโปรแกรมชิ้นเดียว แทนที่จะฝึกแต่ละทักษะแยกส่วนเหมือนบทก่อน บทนี้ให้ประกอบทักษะทั้งหมดเป็นงานจริงที่มีคุณภาพแบบครบวงจร

ระดับที่ 2: โจทย์ที่ใช้คือระบบจัดการคะแนนนักศึกษา โปรแกรมเก็บข้อมูลนักศึกษาหลายคน คือ ชื่อ รหัส และคะแนน แล้วทำงานหลักสี่อย่าง ได้แก่ แสดงรายชื่อทั้งหมด คำนวณค่าเฉลี่ยของคะแนน หานักศึกษาที่ได้คะแนนสูงสุด และเรียงลำดับนักศึกษาตามคะแนนจากมากไปน้อย การวิเคราะห์โจทย์ใช้กรอบ input-process-output คือ input เป็นข้อมูลนักศึกษาที่กำหนดไว้ในโปรแกรม process เป็นการคำนวณค่าเฉลี่ย การค้นหาค่าสูงสุด และการเรียงลำดับ ส่วน output เป็นรายชื่อ ค่าเฉลี่ย คะแนนสูงสุด และรายชื่อหลังเรียง การวิเคราะห์เช่นนี้ช่วยให้เห็นว่าต้องใช้โครงสร้างข้อมูลและฟังก์ชันใดบ้าง

ระดับที่ 3: หัวใจของการทำโครงงานขนาดใหญ่คือการแบ่งงานใหญ่เป็นส่วนย่อยที่ทดสอบได้ แต่ละงานหลักแปลงเป็นฟังก์ชันหนึ่งฟังก์ชันที่รับข้อมูลเข้าและคืนผลออกมาอย่างชัดเจน การแบ่งเช่นนี้ทำให้พัฒนาและทดสอบทีละส่วนได้ และเมื่อผลรวมไม่ถูกต้องก็ไล่หาสาเหตุในฟังก์ชันที่เกี่ยวข้องได้ง่าย แนวทางนี้ต่อยอดจากการออกแบบแบบโมดูลด้วยฟังก์ชันในบทที่ 6 และการทดสอบระดับหน่วยในบทที่ 11

คำถามตรวจความเข้าใจ 15.1: การวิเคราะห์โจทย์แบบ input-process-output ช่วยในการออกแบบโปรแกรมอย่างไร

ใบงาน 2: ข้อตกลงของข้อมูลและฟังก์ชัน

ตัวอย่างเดิมใน 15.6 ใช้ code เป็นสตริงกับคะแนน int ส่วน Lab A และตัวอย่างใหม่ด้านล่างใช้ id เป็น int กับคะแนน double เป็นคนละแบบจำลอง ไม่คัดลอกชื่อฟิลด์หรือสูตรข้ามแบบโดยไม่ปรับให้ตรงทั้งโปรแกรม

สำหรับตัวอย่างใหม่ count อยู่ในช่วง 0–5 และไม่เกินพื้นที่ที่อ่านได้ ทุกระเบียนที่ใช้งานมีชื่อปิดท้ายด้วย NUL รหัสไม่ซ้ำ และคะแนนเป็นค่าจำกัดช่วง 0–100 ฟังก์ชันไม่ได้รับข้อมูลดิบหรือทำหน้าที่ตรวจข้อความผู้ใช้แทนผู้เรียก

ฟังก์ชันจำนวนสมาชิกที่รับผลและกรณีสำคัญเปลี่ยนข้อมูลหรือไม่
average_score1–5ค่าเฉลี่ย ใช้เมื่อมีข้อมูลเท่านั้น
index_of_top0–5ดัชนีค่าสูงสุดตัวแรกในลำดับปัจจุบัน; ว่างคืน -1
sort_by_score_desc0–5คะแนนลดลง ถ้าเท่ากันเรียง id เพิ่มขึ้น
find_by_id0–5ดัชนีที่พบหรือ -1
print_all0–5ว่างแสดง No data

เมื่อ count เป็น 0 ผู้เรียกต้องแสดงไม่มีข้อมูลและไม่เรียก average_score ไม่ใช้ 0.0 เป็นค่าเฉลี่ยปลอมของชุดว่าง ส่วนผลค้นหา -1 ต้องตรวจเงื่อนไขก่อนนำไปอ่านสมาชิก

15.2 การออกแบบด้วยฟังก์ชันและโครงสร้างข้อมูล

ระดับที่ 1: ข้อมูลนักศึกษาหนึ่งคนมีหลายส่วนที่สัมพันธ์กัน คือ ชื่อ รหัส และคะแนน จึงเหมาะกับการใช้ struct จากบทที่ 9 เก็บไว้ด้วยกันเป็นระเบียนเดียว แล้วใช้อาร์เรย์ของ struct จากบทที่ 7 เก็บนักศึกษาหลายคน

ระดับที่ 2: การออกแบบแบ่งระบบเป็นฟังก์ชันย่อยสี่ฟังก์ชันตามงานหลัก ฟังก์ชัน average_score รับอาร์เรย์ของ struct กับจำนวนสมาชิกแล้วคืนค่าเฉลี่ยของคะแนน ฟังก์ชัน index_of_top คืนดัชนีของนักศึกษาที่ได้คะแนนสูงสุดด้วยการค้นหาแบบเชิงเส้นจากบทที่ 10 ฟังก์ชัน sort_by_score_desc เรียงลำดับนักศึกษาตามคะแนนจากมากไปน้อยด้วยการเรียงแบบเลือก (selection sort) จากบทที่ 10 และฟังก์ชัน print_all แสดงรายชื่อทั้งหมด แต่ละฟังก์ชันรับอาร์เรย์ของ struct กับจำนวนสมาชิกเป็นพารามิเตอร์ตามรูปแบบการส่งอาร์เรย์เข้าฟังก์ชันจากบทที่ 7 โครงของ struct เป็นดังนี้

C
struct Student {
    char name[20];
    int id;
    double score;
};

ระดับที่ 3: การเลือกโครงสร้างควบคุมและขั้นตอนวิธีให้เหมาะกับงานเป็นส่วนของ CLO2 ฟังก์ชัน average_score ใช้การวนซ้ำ for สะสมผลรวมแล้วหารด้วยจำนวนสมาชิก ฟังก์ชัน index_of_top ใช้การวนซ้ำร่วมกับเงื่อนไข if เพื่อจำค่าดัชนีที่คะแนนมากที่สุด ส่วน sort_by_score_desc ใช้การวนซ้ำซ้อนของการเรียงแบบเลือกและสลับระเบียนด้วยตัวแปรชั่วคราว การคืนดัชนีจาก index_of_top แทนการคืนทั้งระเบียนเป็นการออกแบบที่ให้ผู้เรียกนำดัชนีไปใช้อ้างถึงสมาชิกตัวจริงในอาร์เรย์ได้ ซึ่งยืดหยุ่นกว่าการคืนสำเนา

คำถามตรวจความเข้าใจ 15.2: เหตุใดการใช้ struct เก็บชื่อ รหัส และคะแนนไว้ด้วยกันจึงเหมาะกว่าการแยกเป็นอาร์เรย์สามชุด

ใบงาน 3: พัฒนาให้ครบหนึ่งเส้นทางก่อนเพิ่มงาน

เริ่มจากสามระเบียนสมมติให้แสดงผลได้ จากนั้นเพิ่มค่าเฉลี่ยและค่าสูงสุด แล้วเพิ่มการเรียงพร้อมผลตรวจทีละขั้น งานที่คอมไพล์ได้แต่ยังไม่รันให้บันทึกตามจริง ไม่ทำเครื่องหมายผ่านล่วงหน้า

ระยะสิ่งที่ทำให้ใช้ได้สิ่งที่ต้องคงเดิมหลักฐานจริงจุดที่ยังติด
V1แสดงระเบียน
V2ค่าเฉลี่ยและค่าสูงสุด
V3เรียงและตรวจคู่ข้อมูล
V4 ที่เลือกทำค้นหาหรือส่วนเพิ่ม

ถ้าผลไม่ตรง ให้จำกัดปัญหาว่าเป็นข้อมูลตั้งต้น สูตร ขอบเขตลูป หรือการแสดงผลก่อนแก้หลายส่วนพร้อมกัน ทุกระยะควรอธิบายได้ว่ารับอะไร คืนอะไร และตรวจจากหลักฐานใด

15.3 การพัฒนาและการทดสอบ

ระดับที่ 1: เมื่อออกแบบฟังก์ชันครบแล้ว การพัฒนาควรเขียนทีละฟังก์ชันแล้วรวมเข้าด้วยกัน แทนการเขียนทั้งโปรแกรมพร้อมกัน เพื่อให้ตรวจสอบความถูกต้องของแต่ละส่วนได้ก่อน

ระดับที่ 2: หลังเขียนครบให้คอมไพล์ด้วยตัวเลือก -Wall เพื่อเปิดชุดการแจ้งเตือนที่ตัวเลือกนี้กำหนด ไม่ใช่คำเตือนทั้งหมดของ GCC แล้วแก้ไขจนไม่มีการแจ้งเตือนหลงเหลือ จากนั้นเพิ่มการทดสอบระดับหน่วยจากบทที่ 11 เพื่อตรวจว่าค่าที่คำนวณได้ตรงกับที่คาดหวัง ในชุดสอนฉบับนี้ใช้ค่าคาดจากข้อมูล Lab A และค่าคลาดเคลื่อนที่กำหนด แล้วรายงาน PASS หรือ FAIL พร้อมสถานะจบไม่เป็นศูนย์เมื่อผิด ตัวอย่างย่อยนี้ต้องอยู่ใน main หลังเตรียม room และ n แล้ว

C
/* This check uses the three scores specified in Lab A and n == 3. */
double avg = average_score(room, n);
const double expected = (78.5 + 92.0 + 85.5) / 3.0;
double difference = avg - expected;
if (difference < 0.0) {
    difference = -difference;
}
if (difference <= 0.000000001) {
    printf("PASS: ค่าเฉลี่ยตรงค่าที่คาดหวัง\n");
} else {
    printf("FAIL: ค่าเฉลี่ยผิดจากที่คาดหวัง\n");
    return 1;
}

ระดับที่ 3: เมื่อผลไม่ตรงกับที่คาดหวัง การไล่แก้ใช้แนวทางจากบทที่ 11 คือตรวจทีละฟังก์ชันด้วยการพิมพ์ค่าระหว่างทางหรือเพิ่มกรณีทดสอบย่อย เพื่อจำกัดว่าความผิดพลาดอยู่ที่ฟังก์ชันใด ข้อผิดพลาดที่พบบ่อยในโจทย์นี้คือการหารค่าเฉลี่ยโดยลืมให้ตัวตั้งเป็นชนิดทศนิยม ทำให้ได้ผลเป็นจำนวนเต็ม การวนเกินขอบเขตอาร์เรย์ และการสลับระเบียนผิดตอนเรียง การทดสอบที่ตรวจค่าเฉลี่ยจึงช่วยยืนยันว่าการคำนวณถูกต้องก่อนนำไปใช้ต่อ

คำถามตรวจความเข้าใจ 15.3: การเพิ่มการทดสอบที่ตรวจค่าเฉลี่ยช่วยยืนยันความถูกต้องของโปรแกรมอย่างไร

15.4 คุณภาพ มาตรฐาน และการส่งมอบ

ระดับที่ 1: โปรแกรมที่ดีไม่ได้วัดจากการทำงานถูกต้องเพียงอย่างเดียว แต่ต้องอ่านง่าย ผ่านการทดสอบ และส่งมอบอย่างมีมาตรฐานด้วย

ระดับที่ 2: การส่งมอบที่ครบวงจรประกอบด้วยสี่ส่วน ส่วนแรกคือจัดรูปแบบโค้ดตามมาตรฐานจากบทที่ 13 ได้แก่ การจัดย่อหน้า 4 ช่องว่าง การตั้งชื่อแบบ snake_case ที่สื่อความหมาย และการใส่คอมเมนต์อธิบายส่วนที่เข้าใจยาก ส่วนที่สองคือบันทึกการเปลี่ยนแปลงของโปรแกรมด้วยข้อความ commit ที่สื่อความหมายอย่างน้อยสองรายการ ตามแนวคิดการจัดการเวอร์ชันจากบทที่ 13 ส่วนที่สามคือพัฒนาด้วยความเข้าใจของตนเองและอ้างอิงแหล่งที่ศึกษาอย่างซื่อสัตย์ตามบทที่ 14 และส่วนที่สี่คือยืนยันว่าโปรแกรมผ่านการทดสอบและทำงานถูกต้อง

ระดับที่ 3: การส่งมอบที่ครบวงจรและรับผิดชอบเป็นการบูรณาการ CLO ทั้งห้าข้อ กล่าวคือการใช้ตัวแปรและโครงสร้างควบคุมให้ได้ผลถูกต้องตอบ CLO1 การเลือกโครงสร้างควบคุมและขั้นตอนวิธีให้เหมาะตอบ CLO2 การเขียนโค้ดถูกไวยากรณ์และมีประสิทธิภาพตอบ CLO3 การพัฒนาด้วยความเข้าใจของตนเองอย่างซื่อสัตย์ตอบ CLO4 และการปฏิบัติตามมาตรฐานโค้ดกับการบันทึกการเปลี่ยนแปลงที่สื่อความหมายตอบ CLO5 โครงงานบูรณาการจึงเป็นหลักฐานรวมที่แสดงว่าทักษะทั้งหมดทำงานร่วมกันได้ในงานจริง

คำถามตรวจความเข้าใจ 15.4: การส่งมอบที่มีคุณภาพประกอบด้วยอะไรบ้าง และเชื่อมกับ CLO ข้อใด

15.5 กรณีศึกษาต่อเนื่อง: จากข้อกำหนดสู่ชิ้นงานที่ตรวจรับได้

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

15.5.1 แปลงความต้องการเป็นเงื่อนไขตรวจรับ

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

ความต้องการกรณีตัวอย่างผลที่ยอมรับ
รายงานค่าเฉลี่ย70, 90, 8080.00
เรียงคะแนนลดลง70, 90, 8090, 80, 70 โดยรหัสติดตามรายการเดิม
คะแนนเท่ากัน80, 80ปฏิบัติตามกติกาที่ประกาศไว้
ไม่มีข้อมูลจำนวนรายการ 0ไม่หารด้วยศูนย์และไม่อ่านสมาชิกแรก
ค้นหารหัสไม่พบรหัสที่ไม่อยู่ในชุดแจ้งไม่พบโดยไม่ใช้ดัชนี -1 อ่านอาร์เรย์

กติกาเมื่อคะแนนเท่ากันต้องตัดสินก่อน ไม่ใช้ผลที่อัลกอริทึมบังเอิญให้เป็นข้อกำหนดย้อนหลัง ในตัวอย่างต่อไปใช้ selection sort แบบสลับทั้งระเบียนและยังไม่รับประกันความคงลำดับของคะแนนเท่ากัน หากงานต้องรักษาลำดับเดิมหรือเรียงรหัสเป็นกุญแจรอง ต้องเพิ่มกติกาและการทดสอบโดยตั้งใจ

15.6 โปรแกรมสาธิตแบบครบไฟล์และการอ่านทีละโมดูล

C
#include <stdio.h>
 
enum { CAPACITY = 5 };
 
struct Student {
    char code[5];
    char name[16];
    int score;
};
 
int total_score(const struct Student students[], int count)
{
    int total = 0;
    for (int i = 0; i < count; ++i) {
        total += students[i].score;
    }
    return total;
}
 
void sort_by_score(struct Student students[], int count)
{
    for (int i = 0; i < count - 1; ++i) {
        int best = i;
        for (int j = i + 1; j < count; ++j) {
            if (students[j].score > students[best].score) {
                best = j;
            }
        }
        if (best != i) {
            struct Student temporary = students[i];
            students[i] = students[best];
            students[best] = temporary;
        }
    }
}
 
void print_report(const struct Student students[], int count)
{
    if (count == 0) {
        puts("No data");
        return;
    }
 
    for (int i = 0; i < count; ++i) {
        printf("%s %-10s %3d\n",
               students[i].code, students[i].name,
               students[i].score);
    }
    printf("Average: %.2f\n",
           (double)total_score(students, count) / count);
}
 
int main(void)
{
    struct Student students[CAPACITY] = {
        {"0017", "Sample-A", 70},
        {"0018", "Sample-B", 90},
        {"0019", "Sample-C", 80}
    };
    int count = 3;
 
    puts("Before sorting");
    print_report(students, count);
    sort_by_score(students, count);
    puts("After sorting");
    print_report(students, count);
    return 0;
}

ก่อนเรียง รายการอยู่ตามลำดับ 0017, 0018, 0019 หลังเรียงเป็น 0018, 0019, 0017 โดยคะแนน 90, 80, 70 ตามลำดับ ค่าเฉลี่ยยังเป็น 80.00 ทั้งก่อนและหลัง เพราะการเรียงเปลี่ยนตำแหน่ง ไม่ได้เปลี่ยนเนื้อหาหรือจำนวนรายการ หากค่าเฉลี่ยเปลี่ยน แสดงว่ามีข้อมูลหาย ซ้ำ หรือถูกแก้ระหว่างสลับ

15.6.1 เหตุผลของแต่ละโมดูล

total_score อ่านข้อมูลโดยไม่แก้สมาชิก sort_by_score แก้ลำดับและแสดงเจตนาด้วยพารามิเตอร์ที่ไม่เป็น const ส่วน print_report รับผิดชอบรูปแบบแสดงและกรณีชุดว่าง ผู้เรียกต้องรักษาว่า count อยู่ระหว่างศูนย์กับความจุ และทุกคะแนนอยู่ใน 0–100 ดังนั้นผลรวมไม่เกิน 500 ในตัวอย่างนี้

ใช้ enum ตั้งชื่อความจุให้เป็นค่าคงที่จำนวนเต็มที่ใช้เป็นขนาดอาร์เรย์ได้ ไม่ใช้ความจุห้าแทนจำนวนรายการจริงสาม ข้อมูลเริ่มต้นที่เหลือในอาร์เรย์ไม่ถูกนำมารายงานเพราะลูปใช้ count เป็นขอบเขต งานนี้ยังไม่เก็บข้อมูลข้ามการรัน ไม่อ่านไฟล์ และไม่ใช้หน่วยความจำแบบพลวัต จึงต้องระบุข้อจำกัดนี้เมื่ออธิบายชิ้นงาน

15.6.2 เชื่อมหลักฐานกับข้อเท็จจริงที่ใช้

กติกาดัชนีอ้างอิงจาก GNU C: Accessing Array Elements การสลับระเบียนทั้งก้อนอาศัย GNU C: Structure Assignment และวิธีเลือกค่าสูงสุดทีละรอบเป็นการปรับทิศทางของแนวคิด NIST: Selection Sort โค้ดและข้อมูลสาธิตในบทนี้เขียนขึ้นเพื่ออธิบายการประยุกต์ ไม่คัดผลทดลองเวลาจากแหล่งเหล่านั้น

ตัวอย่างพร้อมใช้: รายงานอันดับจากสำเนา

ไฟล์ capstone_report.c ใช้แบบข้อมูลเดียวกับ Lab และเพิ่มการค้นหาเพื่อสาธิต A.1 กติกาของตัวอย่างนี้เมื่อคะแนนเท่ากันให้เรียง id เพิ่มขึ้น เป็นข้อตกลงสาธิต ไม่เพิ่มกติกาประเมินให้ทุกโครงงานโดยอัตโนมัติ

C
/* capstone_report.c : synthetic records; no persistent storage */
#include <stdio.h>
#include <string.h>
 
enum { CAPACITY = 5 };
 
struct Student {
    char name[20];
    int id;
    double score;
};
 
/* Caller supplies 1..CAPACITY initialized records with finite scores 0..100. */
double average_score(const struct Student students[], int count)
{
    double total = 0.0;
    for (int i = 0; i < count; ++i) {
        total += students[i].score;
    }
    return total / count;
}
 
/* Return the first maximum in the current order, or -1 for no records. */
int index_of_top(const struct Student students[], int count)
{
    if (count == 0) {
        return -1;
    }
    int top = 0;
    for (int i = 1; i < count; ++i) {
        if (students[i].score > students[top].score) {
            top = i;
        }
    }
    return top;
}
 
int find_by_id(const struct Student students[], int count, int target_id)
{
    for (int i = 0; i < count; ++i) {
        if (students[i].id == target_id) {
            return i;
        }
    }
    return -1;
}
 
/* Sort whole records: descending score, then ascending unique id. */
void sort_by_score_desc(struct Student students[], int count)
{
    for (int i = 0; i < count - 1; ++i) {
        int best = i;
        for (int j = i + 1; j < count; ++j) {
            if (students[j].score > students[best].score ||
                (students[j].score == students[best].score &&
                 students[j].id < students[best].id)) {
                best = j;
            }
        }
        if (best != i) {
            struct Student temporary = students[i];
            students[i] = students[best];
            students[best] = temporary;
        }
    }
}
 
void print_all(const struct Student students[], int count)
{
    if (count == 0) {
        puts("No data");
        return;
    }
    for (int i = 0; i < count; ++i) {
        printf("%d %s %.2f\n",
               students[i].id, students[i].name, students[i].score);
    }
}
 
int main(void)
{
    const struct Student original[CAPACITY] = {
        {"Sample-A", 1, 78.5},
        {"Sample-B", 2, 92.0},
        {"Sample-C", 3, 85.5}
    };
    const int count = 3;
    struct Student ranked[CAPACITY];
 
    puts("Original");
    print_all(original, count);
    if (count > 0) {
        printf("Average: %.2f\n", average_score(original, count));
    }
    const int top = index_of_top(original, count);
    if (top >= 0) {
        printf("Top: %d %s %.2f\n",
               original[top].id, original[top].name, original[top].score);
    }
    for (int i = 0; i < count; ++i) {
        ranked[i] = original[i];
    }
    sort_by_score_desc(ranked, count);
    puts("Ranking copy");
    print_all(ranked, count);
    puts("Original again");
    print_all(original, count);
 
    const int found = find_by_id(original, count, 2);
    if (found >= 0) {
        printf("Found: %s\n", original[found].name);
    }
    const int missing = find_by_id(original, count, 99);
    if (missing < 0) {
        puts("Not found");
    }
    return 0;
}

โปรแกรมคัดลอกเฉพาะจำนวนระเบียนที่ใช้งานแล้วเรียงสำเนา จึงยังรายงานลำดับข้อมูลหลักซ้ำได้ การกำหนดค่า struct คัดลอกฟิลด์อาร์เรย์ภายในด้วย

ดูหลักการใน GNU C: Structure Assignment

ขั้นสาธิตสิ่งที่คาดว่าจะเห็นผลจริงตรงกับข้อตกลงใด
Original
Average และ Top
Ranking copy
Original again
Found และ Not found

คำสั่งค้นหาใช้การวนตามรหัสจริง ไม่สมมติว่าเรียงตามคะแนนแล้วค้นรหัสด้วย binary search ได้ กุญแจที่เรียงกับกุญแจที่ค้นต้องสอดคล้องกับเงื่อนไขของวิธีค้นนั้น

ตัวอย่างพร้อมใช้: ตรวจหน่วยและความสัมพันธ์หลังเรียง

ไฟล์ capstone_regression.c เป็นโปรแกรมตรวจแยก มีฟังก์ชันหลักซ้ำกับตัวอย่างเพื่อให้คอมไพล์ครบไฟล์ได้ หากแก้ฟังก์ชันต้องปรับและตรวจทั้งสองฉบับให้ตรงกัน ไม่ใช้ผลตรวจของคนละซอร์สแทนกัน

C
/* capstone_regression.c : independent executable with explicit checks */
#include <stdio.h>
#include <string.h>
 
enum { CAPACITY = 5 };
 
struct Student {
    char name[20];
    int id;
    double score;
};
 
/* Caller supplies 1..CAPACITY initialized records with finite scores 0..100. */
double average_score(const struct Student students[], int count)
{
    double total = 0.0;
    for (int i = 0; i < count; ++i) {
        total += students[i].score;
    }
    return total / count;
}
 
/* Return the first maximum in the current order, or -1 for no records. */
int index_of_top(const struct Student students[], int count)
{
    if (count == 0) {
        return -1;
    }
    int top = 0;
    for (int i = 1; i < count; ++i) {
        if (students[i].score > students[top].score) {
            top = i;
        }
    }
    return top;
}
 
int find_by_id(const struct Student students[], int count, int target_id)
{
    for (int i = 0; i < count; ++i) {
        if (students[i].id == target_id) {
            return i;
        }
    }
    return -1;
}
 
/* Sort whole records: descending score, then ascending unique id. */
void sort_by_score_desc(struct Student students[], int count)
{
    for (int i = 0; i < count - 1; ++i) {
        int best = i;
        for (int j = i + 1; j < count; ++j) {
            if (students[j].score > students[best].score ||
                (students[j].score == students[best].score &&
                 students[j].id < students[best].id)) {
                best = j;
            }
        }
        if (best != i) {
            struct Student temporary = students[i];
            students[i] = students[best];
            students[best] = temporary;
        }
    }
}
 
void print_all(const struct Student students[], int count)
{
    if (count == 0) {
        puts("No data");
        return;
    }
    for (int i = 0; i < count; ++i) {
        printf("%d %s %.2f\n",
               students[i].id, students[i].name, students[i].score);
    }
}
 
int nearly_equal(double actual, double expected)
{
    double difference = actual - expected;
    if (difference < 0.0) {
        difference = -difference;
    }
    return difference <= 0.000000001;
}
 
int same_records(const struct Student left[], const struct Student right[],
                 int count)
{
    for (int i = 0; i < count; ++i) {
        if (left[i].id != right[i].id ||
            left[i].score != right[i].score ||
            strcmp(left[i].name, right[i].name) != 0) {
            return 0;
        }
    }
    return 1;
}
 
void check(const char *label, int ok, int *failed_count)
{
    printf("%s: %s\n", label, ok ? "PASS" : "FAIL");
    if (!ok) {
        ++*failed_count;
    }
}
 
int main(void)
{
    int failed_count = 0;
    struct Student empty[1] = {{"Keep", 9, 9.5}};
    check("empty top", index_of_top(empty, 0) == -1, &failed_count);
    check("empty search", find_by_id(empty, 0, 9) == -1, &failed_count);
    sort_by_score_desc(empty, 0);
    check("empty sort", empty[0].id == 9 && empty[0].score == 9.5 &&
          strcmp(empty[0].name, "Keep") == 0, &failed_count);
    check("single", nearly_equal(average_score(empty, 1), 9.5) &&
          index_of_top(empty, 1) == 0, &failed_count);
 
    const struct Student original[CAPACITY] = {
        {"Sample-A", 1, 0.0},
        {"Sample-B", 2, 100.0},
        {"Sample-C", 3, 50.0},
        {"Sample-D", 4, 50.0},
        {"Sample-E", 5, 25.0}
    };
    const struct Student expected[CAPACITY] = {
        {"Sample-B", 2, 100.0},
        {"Sample-C", 3, 50.0},
        {"Sample-D", 4, 50.0},
        {"Sample-E", 5, 25.0},
        {"Sample-A", 1, 0.0}
    };
    struct Student ranked[CAPACITY];
    struct Student saved[CAPACITY];
    for (int i = 0; i < CAPACITY; ++i) {
        ranked[i] = original[i];
        saved[i] = original[i];
    }
    check("average", nearly_equal(average_score(original, CAPACITY), 45.0),
          &failed_count);
    check("top", index_of_top(original, CAPACITY) == 1, &failed_count);
    check("find", find_by_id(original, CAPACITY, 5) == 4, &failed_count);
    check("missing", find_by_id(original, CAPACITY, 99) == -1, &failed_count);
    sort_by_score_desc(ranked, CAPACITY);
    check("sorted records", same_records(ranked, expected, CAPACITY),
          &failed_count);
    check("original kept", same_records(original, saved, CAPACITY),
          &failed_count);
    check("mean preserved", nearly_equal(average_score(ranked, CAPACITY), 45.0),
          &failed_count);
 
    struct Student tied[3] = {
        {"Sample-C", 3, 80.0},
        {"Sample-A", 1, 80.0},
        {"Sample-B", 2, 80.0}
    };
    const int first_top = index_of_top(tied, 3);
    sort_by_score_desc(tied, 3);
    check("tie rule", first_top == 0 && tied[0].id == 1 &&
          tied[1].id == 2 && tied[2].id == 3, &failed_count);
    printf("failed=%d\n", failed_count);
    return failed_count == 0 ? 0 : 1;
}

การตรวจ 12 รายการนี้ใช้เงื่อนไขจริงและสถานะจบ ไม่พึ่ง assert จึงไม่หายเมื่อกำหนด NDEBUG การเทียบระเบียนดูชื่อ รหัส และคะแนน ไม่เทียบเพียงผลรวม เพราะข้อมูลอาจสลับคู่ทั้งที่ผลรวมเท่าเดิม

nearly_equal ใช้ค่าคลาดเคลื่อนที่กำหนดสำหรับชุดคะแนนขนาดเล็กนี้ ไม่ใช่ค่ามาตรฐานที่เหมาะกับการคำนวณทศนิยมทุกงาน กรณีคะแนนเท่ากันใช้ค่าที่จัดเก็บตรงกัน ไม่ตีความคะแนนที่ใกล้กันว่าเท่ากันโดยไม่มีกติกา

ใบงาน 4: เลือกกรณีตรวจจากความเสี่ยง

บันทึกผลที่คาดก่อนรัน ระบุฉบับซอร์ส เครื่องมือ และผลจริงแยกกัน จำนวนกรณีมากไม่ได้รับรองว่าครอบคลุม ถ้าไม่มีชุดว่าง คะแนนเท่ากัน หรือคู่รหัสกับคะแนน ก็ยังพลาดข้อผิดพลาดสำคัญได้

กรณีข้อมูลสมมติสิ่งที่ต้องตรวจผลคาดผลจริง
ชุดว่างcount=0ไม่หารศูนย์และไม่อ่านดัชนี -1
หนึ่งรายการคะแนน 9.5ค่าเฉลี่ยและดัชนีสูงสุด
ขอบช่วง0 และ 100สูตรไม่ปัดเศษทิ้ง
เท่ากัน80,80,80 ต่างรหัสกติกาเสมอคะแนน
เต็มความจุcount=5จำนวนและคู่ระเบียนคงเดิม
ไม่พบรหัสที่ไม่มีในชุดตรวจ -1 ก่อนอ่าน

ข้อมูล count เกินพื้นที่ คะแนนนอกช่วง หรือชื่อไม่ปิดท้าย NUL ละเมิดข้อตกลงฟังก์ชัน ไม่เรียกฟังก์ชันด้วยข้อมูลผิดแล้วคาดว่าจะป้องกันให้เอง หากขยายรับอินพุต ต้องมีชั้นตรวจข้อมูลก่อนเรียกและกรณีตรวจเพิ่ม

15.7 เวิร์กช็อปขยาย: พัฒนาทีละชิ้นและส่งหลักฐานที่ทำซ้ำได้

15.7.1 ทำเส้นทางเล็กให้ครบก่อนเพิ่มความสามารถ

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

เมื่อเพิ่มข้อมูล ให้เตรียมระเบียนชั่วคราว ตรวจทุกช่อง แล้วค่อยเก็บใน students[count] และเพิ่ม count หนึ่งครั้ง ถ้าตรวจไม่ผ่านต้องคงข้อมูลเดิมไว้ การออกแบบนี้ทำให้ทดสอบคำขอเพิ่มรายการที่หกได้โดยไม่เขียนนอกขอบเขต

15.7.2 ตารางทดลองที่ครอบคลุมมากกว่าตัวอย่างปกติ

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

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

15.7.3 การสาธิตที่ช่วยให้ผู้รับเข้าใจงาน

เริ่มสาธิตจากข้อมูลปกติหนึ่งชุด แล้วเลือกกรณีขอบเขตที่แสดงการป้องกันของโปรแกรม อธิบายฟังก์ชันหลักและข้อตกลงที่รักษาไว้ บอกส่วนที่ยังไม่รองรับ เช่น ข้อมูลหายเมื่อจบโปรแกรม แทนการเรียกว่า “ระบบสมบูรณ์” โดยไม่มีขอบเขต

จัดชุดส่งมอบตามที่งานเดิมกำหนด พร้อมซอร์สที่รันได้ ข้อกำหนดที่ใช้ ชุดข้อมูลสมมติ ผลทดสอบจริง และบันทึกแหล่งที่มาหรือความช่วยเหลือตามกติกาผู้สอน การทดลอง Git เป็นทางเลือกเช่นบทก่อน ไม่เพิ่มบัญชีหรือเครื่องมือเป็นเงื่อนไขใหม่ในการส่งงาน

ท้ายกิจกรรมให้ทบทวนหนึ่งการตัดสินใจที่เปลี่ยนระหว่างพัฒนา เช่น เปลี่ยนรหัสจากจำนวนเป็นสตริง หรือแยกการรับข้อมูลออกจากสูตร ระบุปัญหาก่อนเปลี่ยน หลักฐานที่ทำให้ตัดสินใจ และผลหลังเปลี่ยน การอธิบายกระบวนการนี้แสดงการบูรณาการความรู้ได้มากกว่าการเปิดโปรแกรมให้เห็นเพียงหน้าจอสำเร็จ

ใบงาน 5: อธิบายประสิทธิภาพโดยไม่แต่งเวลา

selection sort แบบลูปสองชั้นในตัวอย่างเหมาะใช้ฝึกกับข้อมูลเล็ก แต่จำนวนการเปรียบเทียบเพิ่มตามขนาดกำลังสองตามแนวคิด NIST: Selection Sort ไม่สรุปว่าเหมาะกับข้อมูลทุกขนาด

งานขนาดข้อมูลที่ใช้หน่วยที่กำลังนับเหตุผลที่เลือกวิธีนี้ข้อจำกัด
หาค่าสูงสุด
ค้นหารหัส
เรียงคะแนน
สร้างสำเนารายงาน

การคัดลอกช่วยรักษาข้อมูลหลักแต่ใช้พื้นที่เพิ่ม การเรียงสำเนาไม่ใช่ทำให้การเรียงเร็วขึ้นอัตโนมัติ หากรายงานเวลา ให้ใช้วิธีบันทึกจากบทที่ 12 พร้อมสภาพแวดล้อมและผลจริง ไม่เติมตัวเลข benchmark เพื่อให้รายงานดูครบ

ใบงาน 6: ทำให้ผู้รับคอมไพล์และตรวจซ้ำได้

เก็บไฟล์ตัวอย่างแยกจากงานของตน และตั้งชื่อไฟล์ปฏิบัติการให้ตรงฉบับที่คอมไพล์ ใช้คำสั่งต่อไปนี้กับสองไฟล์ครบชุดด้านบน ไม่คอมไพล์ไฟล์ที่ไม่มี main แล้วอ้างว่าตัวอย่างใช้ไม่ได้

BASH
gcc -std=c11 -Wall -Wextra -Wpedantic capstone_report.c -o capstone_report
gcc -std=c11 -Wall -Wextra -Wpedantic capstone_regression.c -o capstone_regression
./capstone_report
./capstone_regression

คำสั่งรันเป็นแบบ Linux/macOS หากใช้ PowerShell กับ GCC ให้สร้างชื่อที่ลงท้าย .exe แล้วรันด้วย .\capstone_report.exe และ .\capstone_regression.exe ตามไฟล์ที่สร้าง

-Wall เปิดชุดคำเตือนหนึ่ง ไม่ได้เปิดคำเตือนทั้งหมดของ GCC และไม่มีคำเตือนไม่ใช่หลักฐานว่าโปรแกรมถูกทุกกรณี ดูขอบเขตตัวเลือกใน GCC: Warning Options

รายการบันทึกสิ่งที่ใช้จริง
ชื่อฉบับซอร์สหรือเวอร์ชัน
ระบบและรุ่นคอมไพเลอร์
คำสั่งคอมไพล์
กรณีที่รันและผล
สถานะจบเมื่อการตรวจล้มเหลว
สิ่งที่ยังไม่ได้ตรวจ

15.8 ซ้อมรับคำขอเปลี่ยนโดยไม่ทำสิ่งเดิมเสีย

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

ก่อนเปลี่ยน เขียนพฤติกรรมที่ต้องคงเดิม ได้แก่จำนวนรายการ คู่รหัสกับคะแนน ค่าเฉลี่ย และการปฏิเสธข้อมูลผิด จากนั้นเพิ่มข้อกำหนดใหม่ว่ารายงานอันดับต้องไม่เปลี่ยนลำดับข้อมูลหลัก ทดสอบแสดงรายงานเดิมอีกครั้งหลังแสดงอันดับเพื่อจับผลข้างเคียง ไม่ตรวจเพียงหน้ารายงานใหม่

ขั้นทดลองสิ่งที่ตรวจ
แสดงข้อมูลหลักก่อนเรียงจดลำดับรหัสและคะแนน
สร้างสำเนาและเรียงสำเนาลำดับคะแนนในรายงานลดลง
แสดงข้อมูลหลักซ้ำลำดับเดิมยังอยู่
ค้นหารหัสแรกและท้ายพบรายการถูกพร้อมคะแนนเดิม
ค้นหารหัสที่ไม่มีไม่อ่านนอกอาร์เรย์และแจ้งไม่พบ

เมื่อได้รับคำขอให้รองรับชื่อยาวขึ้น อย่าแก้เพียงขนาดใน struct แล้วถือว่าเสร็จ ต้องตามตรวจบัฟเฟอร์รับข้อมูล รูปแบบแสดง ข้อกำหนดความยาว และกรณีทดสอบที่เคยอยู่ตรงขอบเขต การเปลี่ยนหนึ่งค่ามักมีผลต่อสัญญาระหว่างหลายส่วน

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

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

ใบงาน 7: รับคำขอเปลี่ยนโดยรักษางานเดิม

สมมติขอเปลี่ยนจากรายงานที่แก้อาร์เรย์หลักเป็นรายงานจากสำเนา ก่อนแก้ให้จดลำดับข้อมูล ค่าเฉลี่ย และคู่ชื่อ–รหัส–คะแนน แล้วตรวจซ้ำหลังรายงาน อย่าดูเพียงแถวแรกว่าคะแนนสูงสุดถูกแล้วถือว่าทั้งงานผ่าน

คำขอส่วนที่ต้องแก้ผลเดิมที่ต้องคงกรณีตรวจใหม่ขอบเขตที่ยังไม่ทำ
รักษาลำดับข้อมูลหลัก
กำหนดกติกาคะแนนเท่ากัน
เพิ่มค้นหาตามรหัส
รับข้อมูลหรือเก็บแฟ้มในอนาคต

การเพิ่มรับข้อมูลต้องคิดเรื่องความจุ รหัสซ้ำ ชื่อยาว และการปฏิเสธก่อนเปลี่ยนข้อมูล ส่วนการเก็บข้ามการรันต้องมีข้อกำหนดแฟ้มเพิ่มเติม ระบุว่าเป็นส่วนขยายที่ยังไม่ทำเมื่ออยู่นอกฉบับส่ง

15.9 อ่านโค้ดที่ผิดและทำความเข้าใจข้อความวินิจฉัย

ตัวอย่างต่อไปนี้เป็นโค้ดฝึกแยกจากงานที่ต้องส่ง แสดงข้อผิดพลาดที่สัมพันธ์กับเนื้อหาบท พร้อมข้อความวินิจฉัยจริง วิธีอ่าน และฉบับแก้ คำว่า diagnostic ใช้รวมข้อความ error, warning และ note จึงไม่ควรเรียกทุกบรรทัดว่า error หรือถือว่าการไม่มี error แปลว่าโปรแกรมถูกตามโจทย์

ที่มาของ log: ผู้เขียนทดสอบด้วย GCC 11.4.0 บน Ubuntu ผ่าน Linux environment เมื่อ 24 ตุลาคม 2569 ใช้ C11 และตัวเลือกดังนี้ ชื่อไฟล์ เลขบรรทัด คอลัมน์ ถ้อยคำ และคำเตือนที่เปิดใช้อาจต่างจากแพลตฟอร์มการเรียน นักศึกษาไม่จำเป็นต้องเปลี่ยนเครื่องมือหรือการติดตั้งตามชุดทดสอบของผู้เขียน

TEXT
-std=c11 -O0 -Wall -Wextra -Wpedantic -Wformat=2 -fdiagnostics-color=never -fmessage-length=0

ข้อความรูปแบบ bad.c:5:17 อ่านว่าไฟล์ bad.c บรรทัด 5 คอลัมน์ 17 ส่วน ^ และ ~ ช่วยชี้บริเวณที่ตรวจพบ จุดนี้อาจเป็นปลายเหตุจากบรรทัดก่อนหน้า error ทำให้การสร้างโปรแกรมล้มในกรณีนั้น warning เตือนความเสี่ยงแม้คำสั่งสร้างอาจจบสำเร็จ และ note ให้บริบท เช่น declaration เดิม ต้องอ่านคู่กัน (GCC: Diagnostics)

ตัวอย่างที่ผิด 1: ชื่อสมาชิกในโมดูลรายงานไม่ตรงแบบจำลอง

ประเภท: ข้อผิดพลาดขณะคอมไพล์

เมื่อรวมโมดูล ชื่อสมาชิกที่แต่ละส่วนใช้ต้องตรงกับแบบจำลองเดียวกัน โค้ดรายงานเรียก scores แต่ระเบียนมี score ทำให้โครงงานยังสร้าง executable ไม่ได้ ข้อมูล id=1 ในตัวอย่างเป็นข้อมูลสังเคราะห์ ไม่ใช่รหัสนักศึกษาจริง

โค้ดที่ผิด — ไม่ใช้แทนฉบับแก้

C
#include <stdio.h>
struct Student { int id; double score; };
void print_student(struct Student student)
{
    printf("id=%d score=%.1f\n", student.id, student.scores);
}
int main(void)
{
    struct Student sample = {1, 85.0};
    print_student(sample);
    return 0;
}

คำสั่งสร้างและข้อความที่ได้จริง

TEXT
gcc -std=c11 -O0 -Wall -Wextra -Wpedantic -Wformat=2 -fdiagnostics-color=never -fmessage-length=0 bad.c -o bad
TEXT
bad.c: In function 'print_student':
bad.c:5:54: error: 'struct Student' has no member named 'scores'; did you mean 'score'?
    5 |     printf("id=%d score=%.1f\n", student.id, student.scores);
      |                                                      ^~~~~~
      |                                                      score

สถานะจบของคำสั่งสร้างที่ตัวเก็บผลอ่านได้: 1 คำสั่งล้มและไม่ได้รัน executable ของตัวอย่างนี้ การรันไฟล์เก่าที่อาจค้างจาก build ครั้งก่อนจะไม่ใช่หลักฐานของโค้ดปัจจุบัน

อ่านข้อความว่าแจ้งอะไรและผิดอย่างไร

อ่านข้อความในบริบทฟังก์ชัน print_student และนิยาม struct Student ก่อนแก้ หากพบ error เดียวกันหลายจุดต้องตรวจว่ามีการเปลี่ยนชื่อใน interface โดยไม่ปรับผู้ใช้ครบหรือไม่ ไม่เพิ่มสมาชิกซ้ำที่เก็บความหมายเดียวกันเพื่อให้แต่ละโมดูลผ่าน

โค้ดที่แก้แล้ว

C
#include <stdio.h>
struct Student { int id; double score; };
void print_student(struct Student student)
{
    printf("id=%d score=%.1f\n", student.id, student.score);
}
int main(void)
{
    struct Student sample = {1, 85.0};
    print_student(sample);
    return 0;
}

เหตุผลของการแก้และสิ่งที่ต้องตรวจซ้ำ

ทำให้ชื่อเดียวกันตรงทั้งระเบียน ฟังก์ชันรายงาน และชุดทดสอบ แล้วคอมไพล์ทั้งชุด ไม่ทดสอบเฉพาะไฟล์ย่อยที่ยังไม่ถูกเรียกผ่าน main

ผลตรวจฉบับแก้จริง: คอมไพล์ด้วยตัวเลือกเดิมและเพิ่ม -Werror เพื่อให้ชุดทดสอบผู้เขียนไม่ยอมรับ warning ไม่มีข้อความวินิจฉัยและคำสั่งสร้างจบด้วยสถานะ 0 จากนั้นรันได้ stdout ดังนี้ และ stderr ว่าง

TEXT
id=1 score=85.0

ตัวอย่างที่ผิด 2: เพิ่มรายการโดยไม่ตรวจความจุก่อนเขียน

ประเภท: การละเมิดที่ sanitizer ตรวจพบขณะรัน

ข้อมูลเต็มสองช่องแล้ว count เท่ากับ 2 การใช้ count เป็นดัชนีถัดไปโดยไม่ตรวจจะเขียนเกินอาร์เรย์ การเพิ่ม count ภายหลังไม่แก้การเขียนที่ผิดไปแล้ว ตัวอย่างผิดรันเฉพาะสำเนาที่เปิด sanitizer เพื่อหยุดที่การละเมิด ไม่ใช้โปรแกรมธรรมดาคาดเดาว่าจะ crash หรือไม่

การตรวจเพิ่มเติม: โค้ดผิดรันเฉพาะสำเนาที่สร้างด้วย -fsanitize=undefined -fno-sanitize-recover=all จึงหยุดเมื่อพบการละเมิด ข้อความ runtime error ด้านล่างเป็นของ sanitizer ไม่ใช่ error จาก compiler และการไม่มีข้อความจาก build ธรรมดาไม่ยืนยันว่าเข้าถึงหน่วยความจำถูกต้อง (GCC: Instrumentation Options)

โค้ดที่ผิด — ไม่ใช้แทนฉบับแก้

C
#include <stdio.h>
int main(void)
{
    double scores[2] = {70.0, 80.0};
    int count = 2;
    scores[count] = 90.0;
    ++count;
    printf("count=%d\n", count);
    return 0;
}

คำสั่งสร้างและข้อความที่ได้จริง

TEXT
gcc -std=c11 -O0 -Wall -Wextra -Wpedantic -Wformat=2 -fdiagnostics-color=never -fmessage-length=0 -fsanitize=undefined -fno-sanitize-recover=all bad.c -o bad
TEXT
bad.c: In function 'main':
bad.c:4:12: warning: variable 'scores' set but not used [-Wunused-but-set-variable]
    4 |     double scores[2] = {70.0, 80.0};
      |            ^~~~~~

สถานะจบของคำสั่งสร้างที่ตัวเก็บผลอ่านได้: 0 ขั้นสร้างสำเร็จ จากนั้นจึงตรวจพฤติกรรมขณะรันหรือใช้ชุดทดสอบตามประเภทข้างต้น

ผลจากการรันฉบับผิด

stdout ว่างในการทดลองนี้ หากโปรแกรมหยุดก่อน flush ข้อมูล อาจไม่เห็นข้อความที่คาดว่าจะพิมพ์ก่อนหน้า

stderr:

TEXT
bad.c:6:11: runtime error: index 2 out of bounds for type 'double [2]'

สถานะจบที่ตัวเก็บผลอ่านได้: 1

อ่านข้อความว่าแจ้งอะไรและผิดอย่างไร

runtime error ... index 2 out of bounds for type double [2] ชี้ที่การเขียนก่อน printf จึงอาจไม่มีข้อความ count ออกมาเลย การไม่มี output สุดท้ายไม่ได้หมายความว่าโปรแกรมยังรอ input ต้องตรวจ stderr และสถานะกระบวนการด้วย

โค้ดที่แก้แล้ว

C
#include <stdio.h>
int main(void)
{
    enum { CAPACITY = 2 };
    double scores[CAPACITY] = {70.0, 80.0};
    int count = 2;
    if (count >= CAPACITY) {
        puts("Capacity reached");
    } else {
        scores[count] = 90.0;
        ++count;
    }
    printf("count=%d first=%.1f\n", count, scores[0]);
    return 0;
}

เหตุผลของการแก้และสิ่งที่ต้องตรวจซ้ำ

ตรวจ count เทียบ CAPACITY ก่อนเขียน และไม่เพิ่มจำนวนเมื่อเต็ม ทดสอบจำนวนก่อนเต็ม เต็ม และคำขอที่เกินความจุ พร้อมตรวจว่ารายการเดิมยังอยู่ตามเดิม ข้อความ Capacity reached มาจากการจัดการของแอป ไม่ใช่ compiler error

ผลตรวจฉบับแก้จริง: คอมไพล์ด้วยตัวเลือกเดิมและเพิ่ม -Werror เพื่อให้ชุดทดสอบผู้เขียนไม่ยอมรับ warning ไม่มีข้อความวินิจฉัยและคำสั่งสร้างจบด้วยสถานะ 0 จากนั้นรันได้ stdout ดังนี้ และ stderr ว่าง

TEXT
Capacity reached
count=2 first=70.0

ลำดับการอ่าน log เมื่อลองโปรแกรมของบทนี้

  1. ระบุว่าเกิดในขั้น compile, link, run หรือ test และแยกข้อความจากระบบออกจากข้อความที่โปรแกรมพิมพ์เอง
  2. อ่าน diagnostic แรกกับบริบทบรรทัดก่อนหน้า แล้วใช้ note เพื่อเทียบ declaration หรือชนิดข้อมูล อย่าแก้ทุกข้อความตามลำดับโดยไม่หาสาเหตุร่วม
  3. แก้ทีละสาเหตุ สร้างโปรแกรมใหม่ และตรวจว่ากำลังรันผล build ใหม่ การเติมวงเล็บ ปิด warning หรือลบ test อาจทำให้ log เงียบโดยยังผิดโจทย์
  4. ใช้ข้อมูลเดิมที่ทำให้ผิดทดสอบซ้ำ แล้วเพิ่มกรณีขอบเขตที่สัมพันธ์กับบท การรันจบสถานะศูนย์เพียงครั้งเดียวไม่แทนการตรวจข้อกำหนดครบถ้วน

ความหมายตามภาษาอ้างจากร่างมาตรฐาน C11 โดยข้อความ diagnostic เฉพาะถ้อยคำเป็นของเครื่องมือที่ใช้ ไม่ได้กำหนดให้ compiler ทุกตัวพิมพ์เหมือนกัน (WG14 N1570, GCC: Warning Options)

ปฏิบัติการที่ 15 โครงงานบูรณาการระบบจัดการคะแนนนักศึกษา

ปฏิบัติการนี้เป็นโครงงานบูรณาการที่แบ่งเป็นสองส่วน ส่วนแรกคือ Lab A พัฒนาระบบจัดการคะแนนนักศึกษาด้วยอาร์เรย์ของ struct ฟังก์ชัน การค้นหา และการเรียงลำดับ ส่วนที่สองคือ Lab B เพิ่มการทดสอบระดับหน่วย ตรวจมาตรฐานโค้ด และส่งมอบตามมาตรฐานการเขียนโค้ดพร้อมบันทึกการเปลี่ยนแปลงและแหล่งที่ศึกษา ทั้งสองส่วนทำต่อเนื่องกันบนโปรแกรมเดียว เพื่อให้เห็นการพัฒนาตั้งแต่ออกแบบไปจนถึงการส่งมอบที่มีคุณภาพ

ℹ️Note

ไฟล์ซอร์สโค้ดในปฏิบัติการนี้ให้ตั้งชื่อเป็นภาษาอังกฤษแบบ snake_case และบันทึกไว้ในโฟลเดอร์ของผู้เรียนเอง โปรแกรมอ้างอิงในเฉลยทดสอบด้วย gcc 13.3.0 ส่วนรหัส commit ของ Git ที่อาจแสดงเป็นเพียงตัวอย่าง จะต่างกันทุกครั้งที่ทำซ้ำ

Lab A: พัฒนาระบบจัดการคะแนนนักศึกษา

สถานการณ์ สร้างโปรแกรมภาษา C ที่เก็บข้อมูลนักศึกษาสามคน (ชื่อ รหัส คะแนน) ด้วยอาร์เรย์ของ struct แล้วทำงานสี่อย่างคือ แสดงรายชื่อทั้งหมด คำนวณค่าเฉลี่ยของคะแนน หานักศึกษาที่ได้คะแนนสูงสุด และเรียงลำดับนักศึกษาตามคะแนนจากมากไปน้อยแล้วแสดงรายชื่อซ้ำ

เป้าหมาย ฝึก LLO15.1 และ LLO15.2 คือวิเคราะห์โจทย์และออกแบบโปรแกรมบูรณาการ แล้วพัฒนาให้คอมไพล์ถูกต้องและทำงานได้ผลที่ถูกต้อง

ขั้นปฏิบัติ

  1. วิเคราะห์โจทย์แบบ input-process-output ว่าโปรแกรมรับข้อมูลใด ต้องประมวลผลอะไร และต้องแสดงผลอะไร
  2. ออกแบบ struct Student ให้เก็บชื่อ (อาร์เรย์ char) รหัส (int) และคะแนน (double) แล้วออกแบบฟังก์ชันสี่ฟังก์ชันคือ average_score, index_of_top, sort_by_score_desc และ print_all โดยแต่ละฟังก์ชันรับอาร์เรย์ของ struct กับจำนวนสมาชิกเป็นพารามิเตอร์
  3. สร้างไฟล์ชื่อ ch15_capstone.c แล้วพัฒนาทีละฟังก์ชัน โดยจัดย่อหน้า 4 ช่องว่างและใส่คอมเมนต์ส่วนหัว
  4. กำหนดข้อมูลนักศึกษาสามคนในฟังก์ชัน main แล้วเรียกใช้ฟังก์ชันให้ครบตามงานทั้งสี่อย่าง
  5. คอมไพล์และรันด้วยคำสั่ง
BASH
gcc -std=c11 -Wall ch15_capstone.c -o ch15_capstone
./ch15_capstone

เมื่อใช้ข้อมูลนักศึกษาสามคนคือ Anong (รหัส 65001 คะแนน 78.5), Decha (รหัส 65002 คะแนน 92.0) และ Suda (รหัส 65003 คะแนน 85.5) โปรแกรมควรแสดงรายชื่อก่อนเรียง แสดงค่าเฉลี่ยเท่ากับ 85.33 ระบุว่าคะแนนสูงสุดคือ Decha (92.0) แล้วแสดงรายชื่อหลังเรียงจากมากไปน้อยเรียงเป็น Decha, Suda และ Anong ตามลำดับ (ผลรันจริงของโปรแกรมอ้างอิงแสดงในเฉลย Lab A)

ตัดสินใจเอง A.1 เพิ่มฟังก์ชันของตนเองอีกหนึ่งฟังก์ชันตามที่ออกแบบเอง เช่น ฟังก์ชันค้นหานักศึกษาตามรหัสแล้วคืนดัชนีที่พบ หรือฟังก์ชันนับจำนวนนักศึกษาที่ได้คะแนนผ่านเกณฑ์ที่กำหนด แล้วเรียกใช้ในโปรแกรมและแสดงผลเพิ่มเติม

ผลลัพธ์ที่คาดหวัง A.1 ฟังก์ชันที่เพิ่มขึ้นทำงานถูกต้องตามที่ออกแบบและโปรแกรมยังคอมไพล์ผ่านไม่มีการแจ้งเตือน (ผลที่คาดว่าจะเห็นสำหรับฟังก์ชันที่ผู้เรียนออกแบบเอง ยังไม่ได้ทดสอบในเอกสารนี้ เพราะขึ้นกับการออกแบบของแต่ละคน)

จุดตรวจ A ออกแบบ struct และฟังก์ชันครบตามโจทย์ โปรแกรมคอมไพล์ผ่านด้วย -Wall โดยไม่มีการแจ้งเตือน ผลการทำงานถูกต้องคือค่าเฉลี่ย 85.33 คะแนนสูงสุด Decha และเรียงจากมากไปน้อยถูกต้อง และโค้ดมีการจัดย่อหน้ากับคอมเมนต์ส่วนหัว

ปัญหาที่พบบ่อย

อาการสาเหตุวิธีแก้
ค่าเฉลี่ยได้เป็นจำนวนเต็มหรือปัดทิ้งเศษตัวตั้งและตัวหารเป็นจำนวนเต็มทั้งคู่ให้ตัวสะสมผลรวมเป็นชนิด double หรือแปลงชนิดก่อนหาร
โปรแกรมอ่านข้อมูลเกินขอบเขตอาร์เรย์หรือหยุดผิดปกติวนซ้ำด้วยเงื่อนไขที่เกินจำนวนสมาชิกจริงใช้เงื่อนไข i < n โดยส่งจำนวนสมาชิกจริงเข้าฟังก์ชันทุกครั้ง
หลังเรียงข้อมูลชื่อกับคะแนนไม่ตรงคู่กันสลับเฉพาะบางฟิลด์แทนที่จะสลับทั้งระเบียนสลับทั้ง struct Student ด้วยตัวแปรชั่วคราวชนิดเดียวกัน

เปิดหลังผู้เรียนพัฒนาด้วยตนเองแล้ว

Lab B: ทดสอบ ตรวจมาตรฐาน และส่งมอบตามมาตรฐานการเขียนโค้ด

สถานการณ์ นำโปรแกรมจาก Lab A มาเพิ่มการทดสอบระดับหน่วยตรวจค่าเฉลี่ย ปรับให้เป็นไปตามมาตรฐานโค้ด แล้วบันทึกการเปลี่ยนแปลงของโปรแกรมด้วยข้อความ commit ที่สื่อความหมายอย่างน้อยสองรายการ พร้อมบันทึกแหล่งที่ศึกษาและเครื่องมือช่วย

เป้าหมาย ฝึก LLO15.2, LLO15.3 และ LLO15.4 คือเพิ่มการทดสอบ พัฒนาด้วยความเข้าใจของตนเองอย่างซื่อสัตย์ และส่งมอบตามมาตรฐานการเขียนโค้ดพร้อมบันทึกการเปลี่ยนแปลงที่สื่อความหมาย

ขั้นปฏิบัติ

  1. ตรวจว่าโปรแกรมมีเงื่อนไขทดสอบค่าเฉลี่ยที่รายงาน PASS หรือ FAIL ตามแนวทางการทดสอบระดับหน่วยจากบทที่ 11 ดังตัวอย่างในเนื้อหา 15.3 แล้วคอมไพล์ด้วย -Wall ยืนยันว่าไม่มีการแจ้งเตือน
  2. ตรวจมาตรฐานโค้ดตามบทที่ 13 ว่าจัดย่อหน้า 4 ช่องว่างสม่ำเสมอ ตั้งชื่อฟังก์ชันและตัวแปรให้สื่อความหมาย และมีคอมเมนต์ส่วนหัวอธิบายโปรแกรม
  3. บันทึกการพัฒนาเป็นเวอร์ชัน โดยระบุการเปลี่ยนแปลงแต่ละครั้งพร้อมเขียนข้อความ commit ที่สื่อความหมายตามแนวคิดการจัดการเวอร์ชันจากบทที่ 13 เริ่มจากเวอร์ชันแรกของโปรแกรม ด้วยข้อความเช่น "เพิ่มระบบจัดการคะแนนนักศึกษาเวอร์ชันแรก"
  4. เพิ่มการเปลี่ยนแปลงหนึ่งอย่าง เช่น เพิ่มฟังก์ชันของงานตัดสินใจเอง A.1 แล้วบันทึกเป็นเวอร์ชันที่สองพร้อมข้อความที่สื่อความหมาย เช่น "เพิ่มฟังก์ชันค้นหานักศึกษาตามรหัส"
  5. จัดทำบันทึกการเปลี่ยนแปลงโดยเรียงลำดับเวอร์ชันพร้อมข้อความ commit และบันทึกแหล่งที่ศึกษากับเครื่องมือช่วยที่ใช้ระหว่างพัฒนาลงในไฟล์บันทึกหรือไฟล์ README

บันทึกการเปลี่ยนแปลงที่ได้ควรแสดงสองรายการ โดยรายการล่าสุดอยู่บนสุด แต่ละรายการมีข้อความ commit ที่สื่อความหมายของการเปลี่ยนแปลง ทำให้อ่านแล้วเข้าใจการพัฒนาได้

หากต้องการ ผู้เรียนใช้เครื่องมือจัดการเวอร์ชันอย่าง Git บันทึกการเปลี่ยนแปลงจริงได้เป็นทางเลือก (เป็นการศึกษาเสริม ไม่ใช่ส่วนที่บังคับส่ง) โดยลำดับคำสั่งพื้นฐานอยู่ในบทที่ 13

ตัดสินใจเอง B.1 เขียนไฟล์ README สั้น ๆ อธิบายว่าโปรแกรมทำอะไร มีฟังก์ชันใดบ้าง และวิธีคอมไพล์กับรัน แล้วระบุแหล่งที่ศึกษาหรือเครื่องมือช่วยที่ใช้ระหว่างพัฒนาอย่างซื่อสัตย์ จากนั้นบันทึกไฟล์นี้เป็นอีกหนึ่งการเปลี่ยนแปลงพร้อมข้อความ commit ที่สื่อความหมาย

ผลลัพธ์ที่คาดหวัง B.1 บันทึกการเปลี่ยนแปลงมีรายการเพิ่มขึ้นหนึ่งรายการสำหรับไฟล์ README ที่อธิบายโปรแกรมและระบุแหล่งที่ศึกษา พร้อมข้อความ commit ที่สื่อความหมาย (ผลที่คาดว่าจะเห็นสำหรับไฟล์ที่ผู้เรียนเขียนเอง ยังไม่ได้ทดสอบในเอกสารนี้ เพราะขึ้นกับเนื้อหาที่แต่ละคนเขียน)

จุดตรวจ B โปรแกรมมีการทดสอบที่รายงาน PASS และคอมไพล์ผ่านไม่มีการแจ้งเตือน โค้ดเป็นไปตามมาตรฐานทั้งการจัดย่อหน้า การตั้งชื่อ และคอมเมนต์ มีบันทึกการเปลี่ยนแปลงอย่างน้อยสองรายการพร้อมข้อความ commit ที่สื่อความหมาย และมีการบันทึกแหล่งที่ศึกษาหรือเครื่องมือช่วยอย่างซื่อสัตย์

ปัญหาที่พบบ่อย

อาการสาเหตุวิธีแก้
ไม่ได้แยกการแก้ไขเป็นรายการพร้อมข้อความบันทึกการเปลี่ยนแปลงรวมเป็นก้อนเดียวบันทึกการเปลี่ยนแปลงแต่ละครั้งเป็นรายการพร้อมข้อความ commit สื่อความหมาย
ประวัติอ่านแล้วไม่ทราบว่าแต่ละครั้งทำอะไรใช้ข้อความ commit กำกวมเช่น update หรือ fixเขียนข้อความระบุการกระทำและสิ่งที่เปลี่ยนให้ชัด
ไม่สามารถอธิบายที่มาของโค้ดบางส่วนได้คัดลอกจากแหล่งอื่นโดยไม่ศึกษาและไม่ได้บันทึกแหล่งที่ใช้ศึกษาจนเข้าใจแล้วเขียนเอง พร้อมบันทึกแหล่งที่ศึกษาอย่างซื่อสัตย์

เปิดหลังผู้เรียนส่งงานแล้ว

ใบงานขยาย Lab B: ชุดส่งมอบที่อ้างหลักฐานเดียวกัน

README ควรบอกงานที่ทำได้ ข้อมูลและข้อจำกัด วิธีคอมไพล์/รัน ชุดตรวจ และที่มา ตาม บทที่ 14 ไม่คัดคำว่าเขียนเองทั้งหมดหรือทดสอบครบจากตัวอย่างไปใช้ถ้าไม่ตรงการทำงานจริง

หลักฐานงานสิ่งที่เตรียมฉบับหรือไฟล์ที่อยู่สิ่งที่ยังต้องแก้
ก วิเคราะห์และออกแบบIPO, struct, ข้อตกลงฟังก์ชัน
ข พัฒนาและตรวจซอร์ส ผลคอมไพล์ ผลรัน
ค ความเข้าใจและที่มาบันทึกแหล่ง/บทบาทความช่วยเหลือ
ง มาตรฐานและส่งมอบรายการตรวจและบันทึกเปลี่ยนแปลง

ให้เพื่อนทำตาม README จากไฟล์ที่ส่งโดยไม่ต้องบอกด้วยวาจา ถ้าทำซ้ำไม่ได้ ให้จดจุดติดและแก้เอกสารหรือไฟล์ตามปัญหาจริง บันทึกอย่างน้อยสองการเปลี่ยนตาม Lab B เดิม Git ยังเป็นทางเลือก

ใบงานปิดโครงงาน: สาธิตและสะท้อนการตัดสินใจ

ซ้อมสาธิตตามลำดับ: ขอบเขตงาน ข้อมูลสมมติ กรณีปกติ กรณีขอบเขต ฟังก์ชันสำคัญ และข้อจำกัด ใช้ซอร์สฉบับเดียวกับผลตรวจ ไม่เพิ่มความสามารถที่ยังไม่ทำด้วยการกล่าวอ้างหรือจัดภาพให้ดูเหมือนเสร็จ

คำถามสะท้อนคำตอบจากงานของตนหลักฐาน
เหตุใดเลือก struct และฟังก์ชันเช่นนี้
บั๊กสำคัญที่พบและวิธีตรวจคืออะไร
คะแนนเท่ากันและชุดว่างมีข้อตกลงใด
ความช่วยเหลือใดใช้และเปิดเผยอย่างไร
ถ้ามีเวลาต่อจะทำอะไร เพราะอะไร

ตรวจออกหกข้อ: count ต่างจากความจุอย่างไร, ทำไมต้องสลับทั้งระเบียน, ค่าเฉลี่ยชุดว่างควรรายงานอย่างไร, เมื่อใดจึงใช้ดัชนีค้นหาได้, ผลตรวจใดตรงฉบับส่ง และสิ่งใดยังต้องประกาศโดยผู้สอนก่อนใช้ประเมิน

เมื่อจบให้กลับไปที่ สารบัญรายวิชา 10306103 เพื่อทบทวนทักษะที่นำมาใช้ การจบชุดสอน 15 บทไม่ได้แปลว่างานผู้เรียนผ่านแล้ว ต้องประเมินจากผลงานและเกณฑ์ที่ประกาศจริง

เก็บกวาด

เมื่อจบปฏิบัติการ ให้ลบไฟล์ปฏิบัติการที่สร้างจากการคอมไพล์ (เช่น ch15_capstone) และเก็บเฉพาะไฟล์ซอร์สโค้ด .c ไฟล์ README บันทึกผลการรัน และบันทึกการเปลี่ยนแปลง (พร้อมข้อความ commit) ไว้ เพื่อส่งเป็นหลักฐานของงานข้อ ข ถึงงานข้อ ง เพื่อให้โฟลเดอร์งานสะอาดและมีเฉพาะสิ่งที่ต้องส่ง

สรุป

บทที่ 15 เป็นบทสุดท้ายและเป็นโครงงานบูรณาการที่ปิดเล่มรายวิชา โดยนำทักษะทั้งหมดที่ฝึกสะสมมาตั้งแต่บทที่ 1 ถึงบทที่ 14 มาประกอบเป็นงานพัฒนาโปรแกรมชิ้นเดียวที่มีคุณภาพแบบครบวงจร โจทย์ระบบจัดการคะแนนนักศึกษาเริ่มจากการวิเคราะห์แบบ input-process-output แล้วออกแบบด้วย struct Student และฟังก์ชันสี่ฟังก์ชันคือ average_score, index_of_top, sort_by_score_desc และ print_all ซึ่งบูรณาการ struct อาร์เรย์ ฟังก์ชัน เงื่อนไข การวนซ้ำ การเรียงลำดับ และการค้นหา จากนั้นพัฒนาทีละฟังก์ชัน คอมไพล์ด้วย -Wall และเพิ่มการทดสอบตรวจค่าเฉลี่ยที่รายงาน PASS หรือ FAIL โปรแกรมอ้างอิงรันจริงได้ค่าเฉลี่ย 85.33 คะแนนสูงสุด Decha (92.0) เรียงจากมากไปน้อยถูกต้อง และการทดสอบรายงาน PASS สุดท้ายคือการส่งมอบที่ครบวงจร ได้แก่ การจัดรูปแบบตามมาตรฐานโค้ด การบันทึกการเปลี่ยนแปลงเป็นเวอร์ชันพร้อมข้อความ commit ที่สื่อความหมาย และการพัฒนาด้วยความเข้าใจของตนเองอย่างซื่อสัตย์พร้อมอ้างอิงแหล่งที่ศึกษา โครงงานนี้จึงเป็นหลักฐานรวมที่แสดงว่าทักษะทั้งหมดทำงานร่วมกันได้ในงานจริง และบูรณาการ CLO ทั้งห้าข้อของรายวิชา คือ CLO1 และ CLO2 ในการวิเคราะห์และเลือกโครงสร้างควบคุม CLO3 ในความถูกต้องและประสิทธิภาพของโค้ด CLO4 ในความซื่อสัตย์ และ CLO5 ในมาตรฐานโค้ดและการส่งมอบ การจบบทนี้จึงเป็นการปิดรายวิชาหลักการเขียนโปรแกรมเบื้องต้นอย่างสมบูรณ์

สิ่งที่ส่งต่อไปยังบทถัดไป

บทที่ 15 เป็นบทสุดท้ายของรายวิชา จึงไม่มีทักษะหรือแนวคิดใหม่ที่ส่งต่อไปยังบทถัดไป โครงงานบูรณาการเป็นการประมวลและรวบรวมทักษะทั้งหมดที่เรียนมา ไม่ได้แนะนำสิ่งใหม่ ตารางด้านล่างสรุปว่าทักษะใดถูกนำมาบูรณาการจากบทใด

ทักษะที่นำมาบูรณาการมาจากบท
เงื่อนไขและการวนซ้ำบท 4, 5
ฟังก์ชันและการออกแบบแบบโมดูลบท 6
อาร์เรย์และการส่งอาร์เรย์เข้าฟังก์ชันบท 7
struct และอาร์เรย์ของ structบท 9
การเรียงลำดับและการค้นหาบท 10
การทดสอบระดับหน่วยบท 11
มาตรฐานโค้ดและแนวคิดการจัดการเวอร์ชันบท 13
การพัฒนาด้วยความเข้าใจของตนเองอย่างซื่อสัตย์บท 14

สรุปคำสั่ง

คำสั่ง Git ในตารางเป็นทางเลือกประกอบแนวคิดการจัดการเวอร์ชัน ไม่ใช่ส่วนที่บังคับส่งในโครงงาน

คำสั่งหน้าที่
gcc -std=c11 -Wall ch15_capstone.c -o ch15_capstoneคอมไพล์โปรแกรมโครงงานพร้อมเปิดการแจ้งเตือนทั้งหมด
./ch15_capstoneรันโปรแกรมที่คอมไพล์แล้ว
git init -b mainสร้างคลังเก็บ Git ในโฟลเดอร์งาน (ทางเลือก ตามแนวคิดบทที่ 13)
git add ch15_capstone.cเตรียมไฟล์ที่แก้ไขให้พร้อมบันทึก (ทางเลือก)
git commit -m "ข้อความ"บันทึกเวอร์ชันหนึ่งพร้อมข้อความอธิบาย (ทางเลือก)
git log --onelineแสดงประวัติ commit บรรทัดละรายการ (ทางเลือก)

แบบฝึกหัดท้ายบท

ตอนที่ 1 ฝึกทบทวน (15 ข้อ ตรวจคำตอบให้ทันที)

ตอนที่ 2 แบบฝึกหัดเขียนตอบ (5 ข้อ)

กลุ่ม ก (เข้าใจ)

  • ก1 (LLO15.1) จงวิเคราะห์โจทย์ระบบจัดการคะแนนนักศึกษาแบบ input-process-output แล้วระบุว่าควรออกแบบเป็นฟังก์ชันใดบ้างและแต่ละฟังก์ชันทำหน้าที่อะไร
  • ก2 (LLO15.4) จงอธิบายว่าการส่งมอบโปรแกรมที่มีคุณภาพประกอบด้วยองค์ประกอบใดบ้าง และแต่ละองค์ประกอบเชื่อมกับ CLO ข้อใด

กลุ่ม ข (ประยุกต์)

  • ข1 (LLO15.2) จงพัฒนาโปรแกรมระบบจัดการคะแนนนักศึกษาตามโจทย์ใน Lab A ให้คอมไพล์ผ่านด้วย -Wall คำนวณค่าเฉลี่ย หาคะแนนสูงสุด และเรียงลำดับจากมากไปน้อยได้ถูกต้อง แล้วเพิ่มการทดสอบตรวจค่าเฉลี่ย
  • ข2 (LLO15.4) จงนำโปรแกรมจากข้อ ข1 มาปรับให้เป็นไปตามมาตรฐานโค้ด แล้วบันทึกการเปลี่ยนแปลงอย่างน้อยสองเวอร์ชันพร้อมข้อความ commit ที่สื่อความหมาย และจัดทำบันทึกการเปลี่ยนแปลงเรียงตามลำดับเวอร์ชัน

กลุ่ม ค (วิเคราะห์)

  • ค1 (LLO15.3) จงอธิบายว่าการพัฒนาโค้ดด้วยความเข้าใจของตนเองและการบันทึกแหล่งที่ศึกษาอย่างซื่อสัตย์ต่างจากการคัดลอกโดยไม่ศึกษาอย่างไร และส่งผลต่อคุณภาพและความรับผิดชอบของงานอย่างไร

การศึกษาค้นคว้าเพิ่มเติม

  1. ทบทวนแนวทางการออกแบบโปรแกรมแบบแบ่งเป็นฟังก์ชันย่อยและการตั้งชื่อที่สื่อความหมายจาก Kernighan และ Ritchie (1988) แล้วนำมาปรับปรุงโครงสร้างฟังก์ชันของโครงงานของตนเองให้ชัดเจนยิ่งขึ้น
  2. ทบทวนข้อกำหนดด้านไวยากรณ์และชนิดข้อมูลของภาษา C ตามมาตรฐาน ISO/IEC 9899:2011 เพื่อยืนยันว่าการคำนวณค่าเฉลี่ยแบบทศนิยมและการใช้ struct ในโครงงานเป็นไปตามมาตรฐานภาษา
  3. ต่อยอดโครงงานด้วยการเพิ่มฟังก์ชันของตนเอง เช่น การค้นหานักศึกษาตามรหัสหรือการนับผู้ที่ได้คะแนนผ่านเกณฑ์ แล้วเพิ่มการทดสอบระดับหน่วยและบันทึกการเปลี่ยนแปลงเป็นเวอร์ชัน (ใช้ Git เป็นทางเลือก) เพื่อฝึกการพัฒนาแบบครบวงจร

เอกสารอ้างอิง

International Organization for Standardization. (2011). ISO/IEC 9899:2011 — Information technology — Programming languages — C. ISO.

Kernighan, B. W., & Ritchie, D. M. (1988). The C programming language (2nd ed.). Prentice Hall.

ตำราหลักภาษาไทยของรายวิชา. (ผู้สอนเติมรายละเอียดบรรณานุกรม — เพิ่มตามมติผู้สอน 21 ต.ค. 2569).

แหล่งปฐมภูมิที่ตรวจประกอบการปรับปรุง 23 ตุลาคม 2569

ใช้เอกสารข้างต้นตรวจหลักการก่อนเรียบเรียงส่วนขยายและสร้างข้อมูลสมมติ ตัวอย่างใหม่ที่เป็นโปรแกรม C ครบไฟล์ตรวจด้วย GCC 12.2.0 (Debian Linux) และ GCC 6.3.0 (MinGW-w64), C11 และคำเตือน -Wall -Wextra -Wpedantic เมื่อ 23 ตุลาคม 2569 แยกจากผลทดสอบ GCC 13.3.0 ของตัวอย่างเดิม ค่ารูปแบบและผลคำนวณตรวจจากการรัน ไม่สร้างผล benchmark หรือรหัสเวอร์ชันแทนผลที่รันจริง

By Watcharin Sarachai