วิธีนี้ต้องใช้ประสบการณ์อันยาวนานและความสามารถเฉพาะตัวในการ "เดา" อย่างมีหลักการตลอดการทำงานของโปรแกรม เช่น
การจัดการโดยการ "ดัก" เหตุการณ์ที่อาจเกิดขึ้นได้ และแจ้งให้ผู้ใช้งานทราบถึงความผิดพลาดดังกล่าวก่อนที่โปรแกรมจะเดี้ยง ยิ่งรองรับมาก ผู้ใช้งานใช้ง่าย ภาระก็จะตกของคนเขียนโปรแกรมเต็ม ๆ
ข้อดีที่โปรแกรมสามารถ "ดัก" ความผิดพลาดเหล่านี้ได้นั้นคือ โปรแกรมจะสามารถทำงานต่อหรือหยุดทำงานในแบบที่หาสาเหตุของปัญหา เพื่อหาทางลงแบบนิ่ม ๆ ได้ และทำให้คนเขียนโปรแกรมสามารถแนะนำวิธีการแก้ไขปัญหากับผู้ใช้งานได้อย่างถูกต้องตรงจุด แต่ก็แลกด้วยการใช้พลังมหาศาลในการเขียนในส่วนนี้ขึ้นมาแต่...
ในชีวิตจริงไม่มีใครที่จะเขียนหรือคิดได้ครอบคลุมทุกเงื่อนไขเพราะทรัพยากรมีจำกัด (เวลา พลังชีวิต และ "ค่าจ้าง")
ลองมาดูกันสักตัวอย่างเพื่อให้เห็นภาพว่าถ้าจะทำโปรแกรมให้มันเมพ ๆ สักโปรแกรม ผู้ใช้งานแสนสบาย จะใส่ข้อมูล จะปู้ยี่ปูยำยังไงโปรแกรมก็สามารถรับมือ โดยลองทำเป็นตารางให้ผู้ใช้งานกรอกเพื่อคำนวณราคาสินค้าขายยกลัง
ผลลัพธ์ที่ต้องการคือสินค้าที่ราคาต่อหน่วยถูกที่สุด
|
ทำตาราง 3 คอลัมน์ให้กรอกข้อมูลสินค้า จำนวน และราคา
กำหนดชื่อว่า table_product เพื่อใช้อ้างอิงในมาโคร |
ลำดับการทำงานของมาโครก็ง่าย ๆ ดังนี้
- ทำการอ่านค่าสินค้าใน table_product ไปที่ละแถว จนครบทุกแถว
- คำนวณราคาต่อหน่วย ราคา / จำนวน
- แสดงชื่อสินค้าที่ราคาต่อหน่วยถูกที่สุด
ลองเขียนเป็น VBA ตามขั้นตอนที่ต้องการ โดยตั้งชื่อว่า Sub Demo1()
Sub Demo1()
Dim rTable As Range
Set rTable = Names("table_product").RefersToRange
'หาจำนวนแถวที่ใช้งาน
Dim maxRow As Long
maxRow = rTable.Parent.UsedRange.Rows.Count
Dim nRow As Long, nRowResult As Long
Dim nLowPrice As Long, nPrice As Long
nLowPrice = 2147483647 'กำหนดค่าเริ่มต้นของราคาที่ใช้เปรียบเทียบไว้สูงที่สุด
'อ่านข้อมูลตั้งแต่แถวที่ 2 จนถึงแถวสุดท้าย
For nRow = 2 To maxRow
With rTable.Rows(nRow)
'คำนวณเฉพาะแถวที่มีชื่อสินค้า
If .Cells(1).Value <> "" Then
nPrice = .Cells(3).Value / .Cells(2).Value 'คำนวนราคาต่อหน่วยจากคอลัมน์ที่ #3 หาร #2
If nPrice < nLowPrice Then
nLowPrice = nPrice
nRowResult = nRow
End If
End If
End With
Next
'แสดงข้อความผลที่ได้
MsgBox "ราคาสินค้าต่อหน่วยต่ำที่สุดคือ " & rTable.Cells(nRowResult, 1).Value
End Sub
ลองมาปู้ยี่ปู้ยำโปรแกรมกันโดยลอง
ใส่ข้อมูลไม่ครบ แถวที่ 4 ไม่ใส่จำนวนชิ้น เมื่อ Run มาโคร ก็จะเจอ
|
ผ่าง!!! Division by zero |
หรือป้อน
ข้อมูลที่ไม่ใช่ตัวเลข ซึ่งอาจจะเกิดจากความผิดพลาดที่ไม่ตั้งใจของผู้ใช้ เมื่อ Run มาโคร ก็จะเจอ
|
Type mismatch อะไร mismatch จะรู้ไหม ( ゚ Д゚) |
หรือถ้าผู้ใช้งานดันทะลึ่งไป
ลบ Name ที่ตั้งเอาไว้ ไม่ว่าจะตั้งใจหรือเพราะรู้เท่าไม่ถึงการณ์ก็แล้วแต่ พอ Run มาโครก็จะ Error อีกแล้ว
|
อ๊ากกก...ลบ Name ทิ้งทำม๊ายยยย (+_+) |
เป็นไงบ้างครับ อ่านถึงตรงนี้แล้วแทบลมจับ ขนาดโปรแกรมไม่กี่บรรทัด หากไม่มีการเขียนในส่วนของการตรวจสอบความผิดพลาดที่อาจเกิดขึ้น (ทั้งที่ไม่น่าจะเกิดขึ้น) พอเกิดปัญหาขึ้นมา สุดท้ายเราก็ต้องมาเหนื่อยตามแก้ไขกันอีกหลายตลบเลยทีเดียว
การแก้ไขปัญหาดังกล่าว สามารถใช้วิธีเขียนคำสั่ง If เพื่อตรวจสอบค่าก่อนนำไปใช้งาน เพื่อให้แน่ใจว่าจะไม่เกิด Run-time error มาดูคำสั่งที่เขียนเพิ่มเติมเพื่อดักปัญหาต่าง ๆ ที่ลองยกตัวอย่างมา
Sub Demo2()
Dim rTable As Range
On Error Resume Next
Set rTable = Names("table_product").RefersToRange
On Error GoTo 0
If rTable Is Nothing Then
MsgBox "ไม่พบ Name 'table_product' ในแผ่นงาน โปรดตรวจสอบ Name Manager", vbCritical
Exit Sub
End If
'หาจำนวนแถวที่ใช้งาน
Dim maxRow As Long
maxRow = rTable.Parent.UsedRange.Rows.Count
Dim nRow As Long, nRowResult As Long
Dim nLowPrice As Long, nPrice As Long
nLowPrice = 2147483647 'กำหนดค่าเริ่มต้นของราคาที่ใช้เปรียบเทียบไว้สูงที่สุด
'อ่านข้อมูลตั้งแต่แถวที่ 2 จนถึงแถวสุดท้าย
For nRow = 2 To maxRow
With rTable.Rows(nRow)
'คำนวณเฉพาะแถวที่มีชื่อสินค้า
If .Cells(1).Value <> "" Then
If TypeName(.Cells(2).Value) <> "Double" Or TypeName(.Cells(3).Value) <> "Double" Then
MsgBox "โปรดระบุค่าเป็นตัวเลขเท่านั้น โปรดตรวจสอบแถวที่ " & nRow, vbCritical
Exit Sub
End If
If .Cells(2).Value <= 0 Then
MsgBox "จำนวนชิ้นมีค่าน้อยกว่าหรือเท่ากับ 0 ในแถวที่ " & nRow, vbCritical
Exit Sub
End If
nPrice = .Cells(3).Value / .Cells(2).Value 'คำนวนราคาต่อหน่วยจากคอลัมน์ที่ #3 หาร #2
If nPrice < nLowPrice Then
nLowPrice = nPrice
nRowResult = nRow
End If
End If
End With
Next
'แสดงข้อความผลที่ได้
MsgBox "ราคาสินค้าต่อหน่วยต่ำที่สุดคือ " & rTable.Cells(nRowResult, 1).Value
End Sub
จาก Sub Demo2() มีการปรับปรุงโดยการเพิ่มส่วนในการตรวจสอบค่าต่าง ๆ ก่อนนำไปใช้งาน หรือข้อมูลที่ได้อาจมีปัญหาจะทำการขึ้นข้อความเตือนและหยุดการทำงาน เพื่อให้ผู้ใช้กลับไปแก้ไขให้ถูกต้องก่อน จะเห็นว่าจำนวนบรรทัดของเดิม 30 เพิ่มเป็น 50 ก็กะคร่าว ๆ ว่าในส่วนของตรวจสอบความผิดพลาดเกือบ 30-50% ของงานที่ทำเลยก็ได้ ความจำเป็นในการเพิ่มและความถี่ในการตรวจสอบก็อยู่ที่ว่าต้องการให้โปรแกรมมันทนทานต่อความผิดพลาดได้แค่ไหน ถ้าขณะที่ใช้งานโปรแกรมอยู่ใกล้ ๆ สามารถเดินไปดูได้ ก็ไม่จำเป็นต้องลงทุนอะไรมาก แต่ถ้าหากเกิดปัญหาจะแก้ไขหรือตามดูแลลำบากก็ต้องจัดกันเต็ม ๆ
|
คราวนี้ไม่มึนล่ะ เพราะบอกเป็นภาษามนุษย์ |
แม้ว่าจะเขียนคำสั่งเพิ่มเข้าไปกันโน้นกันนี่แล้วแต่ก็ใช่ว่าจะกันได้ทุกเงื่อนไข อาจมีเงื่อนไขที่คาดไม่ถึงเกิดขึ้นได้อีกใครจะไปรู้ ดังนั้นจึงต้องมีการรับมือกับปัญหาที่เกินที่จะคาดเดาเอาไว้ด้วย มิฉะนั้นก็จะต้องเจอ Run-time error ที่ผู้ใช้งานอ่านแล้วไม่รู้เรื่องอยู่ดี
ในกรณีที่เกิดความผิดพลาดที่เกินจะคาดเดา
ใน VBA จะมีคำสั่ง On Error เพื่อช่วยในการจัดการกับ Error ต่าง ๆ ที่อาจเกิดขึ้น โดยหากเกิด Error ขึ้นแทนที่มันแสดง Run-time error ที่อ่านแล้วไม่เข้าใจ มันจะส่งต่อปัญหาดังกล่าวไปในส่วนที่เขียนขึ้นเพื่อจัดการปัญหาดังกล่าวเอง โครงสร้างโค้ดสำหรับจัดการ Error จะดังนี้
Sub Demo3()
On Error GoTo handle_error
'your code here
'your code here
line_x:
'your code here
Exit Sub
handle_error:
'handle error here
'.................
'go back to continue work
Resume Next
'or
Resume line_x
'if don't to continue then
Exit Sub
End Sub
จากตัวอย่างใน Demo3 หากโปรแกรมเกิด Run-time error ขึ้น มันจะวิ่งไปที่บรรทัดที่ 11 ตามคำสั่ง On Error GoTo handle_error ผู้เขียนโค้ดจำเป็นต้องจัดการกับ Error ที่เกิดขึ้นซึ่งจะระบุไว้ใน object Err เช่น
- ถ้าเกิด Division by zero จะได้ Err.Number เท่ากับ 11
- ถ้าเกิด Type mismatch จะได้ Err.Number เท่ากับ 13
ก็เขียนจัดการกันไปว่า Err.Number เป็นค่าอะไร แล้วจะทำอย่างไรกับมันดี จะแจ้งปัญหาให้ผู้ใช้ทราบว่าเกิดอะไร และหลังจากนั้นจะให้ทำอะไรต่อ ซึ่งตัวเลือกตรงนี้มี 3 ตัวเลือกคือ
- Resume Next กลับไปทำงานต่อที่คำสั่งที่อยู่ต่อไปจากที่ Error
- Resume [line] กลับไปทำงานต่อที่ตำแหน่งบรรทัด [line] ที่ระบุ
- Exit Sub จบการทำงาน ไม่สามารถทำงานต่อได้ (ถ้าอยู่ใน Function หรือ Property ก็ใช้ Exit Function หรือ Exit Property ให้ถูกต้อง)
สำหรับวิธีการใช้ On Error ในมาโคร สามารถดูรายละเอียดเพิ่มเติมที่เว็บไซต์
//support.microsoft.com/kb/141571/EN-US ครับ ส่วนข้อมูลอ้างอิงอื่น ๆ เกี่ยวกับ Error Code
สำหรับเทคนิคที่ผมแนะนำคือ ควรมีตัวแปรแบบ Global สำหรับ module นั้น ๆ เพื่อช่วยบันทึกชื่อ Sub หรือ Function ที่ทำงานอยู่แล้วเกิด Error ขึ้น เพราะว่าใน VBA ค่า Err.Source จะระบุไว้หยาบมากคือบอกที่ระดับ Project ไหนที่เกิด Error
หากผู้ใช้งานเป็นระดับธรรมดา จับภาพหน้าจอไม่เป็น เจอภาษาอังกฤษแล้วรีบปิดหนี เจอแบบนี้แนะนำว่าให้เขียน log file เอาไว้ว่าเกิด Error อะไร แล้วให้ผู้ใช้ส่งไฟล์ดังกล่าวกลับมาให้ดูเพื่อวิเคราะห์ปัญหาที่เกิดขึ้น ตัวอย่างโปรแกรมแสดงใน Sub Demo4()
Sub Demo4()
Dim currentModule As String
currentModule = "Demo4()"
On Error GoTo handle_error
Debug.Print 1 / 0 'make some error
Exit Sub
handle_error:
Dim fileNumber As Long
fileNumber = FreeFile
Open ThisWorkbook.Path & "\error_log.txt" For Append As #fileNumber
Print #fileNumber, "Date: ", Now()
Print #fileNumber, "Module: ", currentModule
Print #fileNumber, "Err.Source: ", Err.Source
Print #fileNumber, "Err.Number: ", Err.Number
Print #fileNumber, "Err.Description: ", Err.Description
Print #fileNumber, ""
Close #fileNumber
MsgBox "พบความผิดพลาดในโปรแกรม " & _
"โปรดส่งข้อมูลในไฟล์ error_log.txt ให้กับผู้พัฒนาโปรแกรม", vbCritical
Shell "notepad.exe """ & ThisWorkbook.Path & "\error_log.txt" & """"
Exit Sub
End Sub
|
เมื่อ Run โปรแกรม จะเกิด Error โปรแกรมจะแจ้งปัญหาและเปิดไฟล์ error_log.txt |
อีกเทคนิคคือ ในกรณีที่โปรแกรมมีการเรียก Sub หลัก แล้ว Sub หลักไปเรียก Sub ย่อย ๆ ก็ให้วางคำสั่ง On Error ไว้ที่ Sub หลักเท่านั้น เมื่อเกิด Error ที่ Sub ย่อย ๆ ก็จะเด้งกลับมาที่ Sub หลักเพื่อจัดการ Error ทั้งหมดก็ได้ แต่วิธีนี้มีข้อจำกัดคือ พอกลับมาที่ Sub หลักแล้วจะไม่กลับไปทำที่ Sub ย่อยต่อ ดั้งนั้นไม่มีวิธีการตายตัวอยู่ที่การออกแบบ แล้วแต่ทรัพยากรจะอำนวยครับ
|
เฉพาะ Sub Main เท่านั้นที่มี On Error เมื่อเกิด Error ที่ Sub X จะไม่กลับไปที่ Sub X ต่อ |
สุดท้ายก่อนจบประเด็นนี้ หากใช้
On Error Resume Next อย่าลืมยกเลิกด้วย
On Error Goto 0 ด้วยเสมอ มิฉะนั้นหากโปรแกรมผิดพลาดขึ้นก็จะเงียบ และหาสาเหตุของปัญหาไม่เจอ อันตรายมากครับ
ปล.ตอนแรกตั้งใจจะเขียนให้สั้น ๆ แต่คงลดได้สุด ๆ แค่นี้ นี่ก็ยาวมาเกินที่คิดไว้เยอะเลย
ไม่มีความคิดเห็น:
แสดงความคิดเห็น