พื้นฐานการเขียนโปรแกรม:การเขียนโปรแกรมแบบปลอดบัก

จาก Theory Wiki
ไปยังการนำทาง ไปยังการค้นหา

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

อะไรคือบัก

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

บัก ในภาษาอังกฤษ เขียนว่า bug แปลว่า แมลง โดยในสมัยก่อน ช่วงที่คอมพิวเตอร์ยังเป็นเครื่องใหญ่ขนาดเท่าห้องอยู่นั้นอุปกรณ์แต่ละส่วนของคอมพิวเตอร์ก็ใหญ่กว่าในปัจจุบันมาก ใหญ่พอที่จะให้แมลงลงไปอยู่ระหว่างอุปกรณ์ต่างๆภายในเครื่องได้ โดยแมลงตัวแรกที่ถูกพบว่าไปติดอยู่ในเครื่องคอมพิวเตอร์ซึ่งเป็นเหตุให้เกิดข้อผิดพลาดขึ้นนั้น เป็นผีเสื้อกลางคืน ซึ่งถูกพบโดย Grace Murray Hopper ในเครื่อง Mark IIมหาวิทยาลัยฮาร์วาร์ด Grace Murray Hopperได้นำเอาซากของผีเสื้อกลางคืนออกจากเครื่อง Mark II และนำไปแปะไว้ในสมุดบันทึก(log book) ซึ่งการกระทำนี้เอง เป็นต้นกำเนิดของคำว่า ดีบัก(debug)

อ่านเพิ่มเติมได้ที่ประวัติของ"บัก"

บักเกิดขึ้นได้อย่างไรบ้าง

สาเหตุหลักๆที่ทำให้เกินบักได้แก่

Divide by zero:หารด้วยศูนย์

ก่อนอื่นลองดูตัวอย่างโปรแกรมข้างล่างกันก่อน

...
double a,b,c;
...
a=b/c;
...

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

Infinite loops:ลูปอนันต์

ข้อกำหนดหนึ่งของการตัดสินว่าวิธีการทำงานใดๆเป็น Algorithm หรือเปล่านั้นคือ การทำงานจะต้องยืนยันได้ว่ามีจุดสิ้นสุด

...
i=10;
do
  printf("%d\n",i--);
until (i=0);
...

ถ้าโดยปกติแล้ว โปรแกรมข้างต้นนี้ก็จะทำงาน 10 รอบ โดยแต่ละรอบจะพิมพ์ตัวเลขตั้งแต่ 10 จนถึง 1 อย่างละ 1 ตัว แต่ในความจริงแล้ว โปรแกรมจะพิมพ์ 10 หนึ่งครั้งแล้ว ตามด้วย 0 ไปเรื่อยๆ ไม่รู้จบ (ดูที่เงื่อนไขในวงเล็บ) ซึ่งอันนี้เป็นข้อผิดพลาดอีกอันนึงที่เกิดขึ้นได้บ่อยๆคือ การที่โปรแกรมทำงานแบบไม่รู้จบ หรือที่บางคนเรียกกันว่า "ติดลูป" ซึ่งทำให้โปรแกรมพบกับข้อผิดพลาดได้

Arithmetic overflow or underflow:ค่าเกินขอบเขตตัวแปร

int a,b,c;
...
a=b+c;

โปรแกรมง่ายๆข้างต้นนี้ จะมีปัญหาใหญ่ ถ้า b+c นั้นมีค่าเกินขอบเขตของตัวแปร int ซึ่งจะทำให้การคำนวนได้ค่าที่ผิดไปจากค่าที่ต้องการ

Exceeding array bounds:ค่าตัวชี้เกินขนาดของตัวแปรArray

int a[SIZE],index,data;
...
a[index]=data;

นี่ก็เป็นอีกตัวอย่างนึงที่แสดงให้เห็นได้ว่าการเขียนโปรแกรมสามารถมีบักได้ง่ายมาก โปรแกรมข้างต้นจะทำงานผิดพลาดถ้าindexมีค่ามากกว่าหรือเท่ากับSIZE หรือน้อยกว่า 0 ซึ่งถ้าไม่มีการตรวจสอบที่ดี ก็จะทำให้มี บัก ในโปรแกรมได้

Using an uninitialized variable:ใช้ตัวแปรที่ไม่ได้มีการกำหนดค่า

int a,b,c;
a=b+c;

ถ้าไม่ได้กำหนดค่าเริ่มต้นให้ แน่นอนว่าไม่มีใครบอกได้ว่าค่าใน a, b และ c จะเป็นค่าอะไร ซึ่งทำให้มีปัญหาได้ค่าที่ไม่ถูกต้องเวลานำไปใช้ หลายๆท่านอาจจะเถียงว่า compiler เป็นผู้ initial ค่าให้ตัวแปรอยู่แล้ว แต่ก็ไม่ใช่ทุก compiler ดังนั้น programmer จึงควรที่จะ initial ค่าของตัวแปรทุกตัวดัวยตัวเอง

Accessing memory not owned (Access violation):การอ้างค่าจากหน่วยความจำที่ไม่มีสิทธิ์เข้าถึง

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

Memory leak or Handle leak:หน่วยความจำไม่พอ

Stack overflow or underflow:ปัญหาสแตกล้น และปัญหาข้อมูลในสแตกไม่พอ

Buffer overflow:

int strcpy(char *source,char *target){
  int count = 0;
  while (*source) do{
    *(target++)=*(source++);
    count++;
  }
  *target=0;
  return count;
}

โปรแกรมนี้จะมีปัญหาคือ ถ้าจำนวนตัวอักษร ของ source มีมากกว่าจำนวนตัวอักษรที่ target เก็บได้ก็จะทำให้เกิดการ overflow ได้

Deadlock:การแย่งทรัพยากร

Off by one error:

สำหรับข้อมูลแบบArray ปกติแล้วจะมี

Race hazard:

ปัญหาเรื่องเวลา ส่วนใหญ่ปัญหานี้ จะเกิดเมื่อมีการทำงานพร้อมๆกันหลายๆprocessหรือหลายๆthread ตัวอย่างง่ายๆเช่น

for (int i = 0;i<10;i++){
  j=i;
  printf("Copy from i = %d\n",j);
}
for (int k = 0;k<10;k++){
  j=k;
  printf("Copy from k = %d\n",j);
}

Loss of precision in type conversion:

หลักการลดจำนวนบัก

ถึงแม้ว่าการเีขียนโปรแกรมที่ไม่ให้มีข้อผิดพลาดเลยนั้น เป็นไปได้ยาก แต่การที่จะเขียนโปรแกรมที่สามารถจะหาข้อผิดพลาดที่มีในโปรแกรมได้ง่ายนั้นไม่เป็นสิ่งที่เกินความสามารถ

Layout

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

long power(int x,int y){
long z=1;
for (;y--;)z*=x;
return z;
} 

อ่านยากกว่า

long power(int x,int y){
  long z=1;
  for (null;y--;null)
    z*=x;
  return z;
} 

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

Name

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

Copy&Paste

Preprocesser

  • #define
  • #if
  • #endif
  • #else
  • #ifdef
  • #ifndef

Trap

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