Notes & software courses · Free to learn
Aph's Blog
On this page

ข้อผิดพลาด & การแก้บั๊ก (Errors & Debugging)

👋 อ่านฟรีทั้งหมดบน Aph's Blog — เนื้อหาภาษาไทย ทำตามทีละหน้าใน sidebar ได้เลย หากมีข้อเสนอแนะหรืออยากให้เพิ่มหัวข้อไหน บอกได้เสมอ

ขอบเขตตัวแปร (scope), อ่าน error ให้เป็น, จัดการด้วย try/except และเทคนิคแก้บั๊กที่ใช้จริง

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

ขอบเขตของตัวแปร (Scope)

ตัวแปรที่สร้างในฟังก์ชัน (local) ใช้ได้แค่ในฟังก์ชันนั้น ส่วนตัวแปรนอกฟังก์ชัน (global) เข้าถึงได้ทั่วไป เข้าใจเรื่องนี้ช่วยแก้บั๊ก "ทำไมตัวแปรหาไม่เจอ" ได้มาก

python
total = 100          # global — ใช้ได้ทั้งไฟล์

def calc():
    x = 10           # local — มีอยู่แค่ในฟังก์ชันนี้
    return total + x # อ่าน global ได้

print(calc())        # 110
# print(x)           # NameError! x ไม่มีอยู่นอกฟังก์ชัน
ทำไมต้องมี scope

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

ประเภทของ error ที่เจอบ่อย

Errorสาเหตุตัวอย่าง
SyntaxErrorพิมพ์ผิดไวยากรณ์ลืม : ท้าย if, วงเล็บไม่ครบ
NameErrorใช้ชื่อที่ยังไม่มี/พิมพ์ผิดเรียก prnt() แทน print()
TypeErrorใช้ชนิดข้อมูลผิด"3" + 5 (str บวก int)
ValueErrorชนิดถูกแต่ค่าผิดint("abc")
IndexErrorเข้าถึง index เกินขนาดlist[10] บนลิสต์ 3 ตัว
KeyErrorเข้าถึง key ที่ไม่มีใน dictd["missing"]
ZeroDivisionErrorหารด้วยศูนย์10 / 0
IndentationErrorย่อหน้าผิดย่อหน้าไม่ตรงกัน

อ่าน error message ให้เป็น

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

Traceback (most recent call last):
  File "main.py", line 3, in <module>
    print(10 / 0)
ZeroDivisionError: division by zero

# อ่านว่า: บรรทัด 3 ในไฟล์ main.py หารด้วยศูนย์

จัดการ error ด้วย try / except

บาง error เราคาดเดาได้ว่าจะเกิด (เช่นผู้ใช้พิมพ์ตัวอักษรแทนตัวเลข) ใช้ try/except ดักไว้ ไม่ให้โปรแกรมพังกลางคัน

python
try:
    num = int(input("ใส่ตัวเลข: "))
    print(f"100 หาร {num} = {100 / num}")
except ValueError:
    print("กรุณาใส่ตัวเลขเท่านั้น")
except ZeroDivisionError:
    print("หารด้วยศูนย์ไม่ได้")
else:
    print("คำนวณสำเร็จ")     # ทำเมื่อไม่มี error
finally:
    print("จบการทำงาน")      # ทำเสมอ ไม่ว่าจะ error หรือไม่
อย่า except เปล่า ๆ แบบกว้างเกินไป

การเขียน except: เฉย ๆ (จับทุก error) ทำให้บั๊กถูกซ่อนและหายาก ควรระบุชนิด error ที่คาดไว้ เช่น except ValueError: เพื่อให้รู้ว่ากำลังจัดการอะไรอยู่

โยน error เองด้วย raise

นอกจากดัก error เรายัง "โยน" error เองได้เมื่อเจอสถานการณ์ที่ไม่ควรดำเนินต่อ — ดีกว่าคืนค่าแปลก ๆ (เช่น -1) ที่ผู้เรียกอาจลืมเช็ค ทำให้ bug ปรากฏทันทีตรงจุดเกิดเหตุ

python
def set_age(age):
    if age < 0:
        raise ValueError(f"อายุติดลบไม่ได้: {age}")   # หยุดและบอกสาเหตุชัด
    return age

# set_age(-5)   ->  ValueError: อายุติดลบไม่ได้: -5

# ใช้คู่กับ try/except ของผู้เรียกได้
try:
    set_age(-5)
except ValueError as e:
    print(f"ผิดพลาด: {e}")
fail fast — พังเร็ว พังชัด

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

เทคนิคแก้บั๊กที่ใช้จริงทุกวัน

  1. อ่าน error message ให้จบ — มันบอกบรรทัดและสาเหตุตรง ๆ
  2. แทรก print() ดูค่าตัวแปรในจุดต่าง ๆ ว่าตรงกับที่คิดไหม (วิธีที่ง่ายและทรงพลังที่สุด)
  3. ตัดโค้ดให้เหลือน้อยที่สุดที่ยังเกิดบั๊ก เพื่อจำกัดวงหาสาเหตุ
  4. ตรวจ assumption: ชนิดข้อมูลถูกไหม (ใช้ type()), ค่าว่างไหม, index เกินไหม
  5. อธิบายโค้ดให้คนอื่น (หรือเป็ดยาง) ฟังทีละบรรทัด มักเจอจุดผิดเอง
python
# ตัวอย่างใช้ print debug: ทำไมผลรวมผิด?
def sum_list(nums):
    total = 0
    for n in nums:
        print(f"กำลังบวก {n}, total ตอนนี้ = {total}")  # debug
        total += n
    return total

sum_list([1, 2, 3])
# เห็นค่าทีละขั้น ช่วยจับจุดที่ผิดได้

สรุปหัวข้อนี้ & จบบทที่ 2

  • ตัวแปร local อยู่แค่ในฟังก์ชัน, global เข้าถึงได้ทั่วไป
  • รู้จัก error ยอดฮิต: NameError, TypeError, ValueError, IndexError, KeyError
  • อ่าน error จากล่างขึ้นบน บรรทัดสุดท้ายบอกชนิดและสาเหตุ
  • try/except ดัก error ที่คาดได้ ไม่ให้โปรแกรมพัง — ระบุชนิดให้ชัด
  • เทคนิคแก้บั๊ก: อ่าน error, print ดูค่า, ตัดโค้ด, ตรวจ assumption
แบบฝึกหัด

1) เขียนโปรแกรมหารเลข 2 ตัวที่ดักทั้ง ValueError และ ZeroDivisionError 2) ลองสร้าง error แต่ละชนิดในตารางดูว่าหน้าตา message เป็นอย่างไร 3) แก้บั๊กโปรแกรมที่ให้มาด้วยการแทรก print ดูค่า 4) เขียนฟังก์ชันรับ input จนกว่าผู้ใช้จะใส่ตัวเลขถูกต้อง (ใช้ while + try/except)

จบบทที่ 2 — เขียนโปรแกรมเป็นแล้ว!

ตอนนี้คุณมีเครื่องมือครบสำหรับเขียนโปรแกรมเชิงตรรกะ: ตัวแปร, operator, เงื่อนไข, loop, ฟังก์ชัน และจัดการ error ได้ บทถัดไปจะเรียนวิธีเก็บข้อมูลหลายค่าอย่างเป็นระบบ