วิธีคิดแบบนักวิทยาการคอมพิวเตอร์/Classes and functions
บทที่ 13 Classes and functions
แก้ไข13.1 Time
แก้ไขขณะที่ตัวตัวอย่างอื่นๆ เป็นแบบ user-defined type นั้น คราวนี้เราจะ define ( กำหนด ) class ที่เรียกว่า Time ที่ใช้บันทึกเวลา
class Time:
pass
เราสามารถสร้าง Time object ขึ้นมาใหม่ได้ และกำหนด attribute สำหรับบอกชั่วโมง นาที และวินาที
time = Time()
time.hours = 11
time.minutes = 59
time.seconds = 30
แผนภาพแสดงของ Time object
13.2 Pure functions
แก้ไขในส่วนต่อมา เราจะทำการเขียน function ชุดที่สอง หรือที่เรียกว่า addTime โดยที่สามารถคำนวณผลรวมของ 2 เวลาได้ โดยจะพิสูจน์ให้เห็นว่า function 2 ชนิดนี้ คือ pure functions and modifiers.
ด่านล่างเป็นตัวอย่างคร่าวๆ ขอชุดคำสั่ง addTime :
def addTime(t1, t2):
sum = Time()
sum.hours = t1.hours + t2.hours
sum.minutes = t1.minutes + t2.minutes
sum.seconds = t1.seconds + t2.seconds
return sum
function นี้เป็นการสร้าง Time object ขึ้นมาใหม่ ตอนแรกมันมี attribute ในตัวของมันเอง และกลับคืนค่าอ้างอิงไปที่ object อันใหม่ เรียกว่า pure function เพราะว่า ไม่มีการแก้ไขในส่วนใดของ object แล้วผ่านค่าไปเป็น parameters และมันไม่มีผลกระทบ เช่น ค่าที่แสดงออกมา หรือค่าที่นำใส่เข้าไป ในตัวอย่างนี้แสดงให้เห็นถึงการใช้งาน function ซึ่งเราจะต้องสร้าง Time object ขึ้นมา 2 ตัว โดยให้ตัว currentTime นั้นใส่ค่าเวลาในปัจจุบัน และตัว breadTime นั้นใส่จำนวนเวลาที่เพิ่มขึ้นมา จากนั้นเราจะใช้ addTime ในการคำนวณ ถ้าคุณยังทำในส่วนของ printTime ไม่ได้ ให้ไปดูใน section 14.2 ก่อนที่จะมาทำในส่วนนี้ใหม่
>>> currentTime = Time()
>>> currentTime.hours = 9
>>> currentTime.minutes = 14
>>> currentTime.seconds = 30
>>> breadTime = Time()
>>> breadTime.hours = 3
>>> breadTime.minutes = 35
>>> breadTime.seconds = 0
>>> doneTime = addTime(currentTime, breadTime)
>>> printTime(doneTime)
ผลลัพธ์ของโปรแกรมที่คำนวณได้ถูกต้อง คือ 12:49:30 แต่ในอีกแง่หนึ่งในกรณีนี้จะมีผลลัพธ์บางแห่งที่ไม่ถูกต้อง
ปัญหาที่ว่านี้ ใน Function จะไม่ถูกต้องถ้าจำนวนค่าในส่วนของวินาทีแสดงออกมามากกว่า 60 เมื่อเกิดเหตุการณ์เช่นนี้เราจะต้อง “carry” ในส่วนของวินาทีที่เกินมา ไปไว้ในส่วนของนาที หรือ ในส่วนของนาทีที่เกินมาไปไว้ในส่วนของชั่วโมง
Function การแสดงผลของวินาทีที่ถูกต้อง
def addTime(t1, t2):
sum = Time()
sum.hours = t1.hours + t2.hours
sum.minutes = t1.minutes + t2.minutes
sum.seconds = t1.seconds + t2.seconds
if sum.seconds >= 60:
sum.seconds = sum.seconds - 60
sum.minutes = sum.minutes + 1
if sum.minutes >= 60:
sum.minutes = sum.minutes - 60
sum.hours = sum.hours + 1
return sum
แม้ว่าใน function นี้จะมีความถูกต้องแล้ว แต่มันมีขนาดที่ใหญ่ หลังจากนี้เราจะแนะนำทางเลือกที่ใกล้เคียงที่มี code สั้นกว่า
13.3 Modifiers
แก้ไขเวลานั้นจะเป็นประโยชน์สำหรับ function เพื่อใช้ในการแก้ไขในส่วนต่างๆ ของ objects ที่มันกลายเป็นเหมือน parameters โดยปกติแล้วชื่อที่ใช้เรียกจะถูกเก็บไว้อ้างอิงไปยัง objects ที่มันเปลี่ยนแปลง ฉะนั้นการเปลี่ยนแปลง function จะเห็นชัดเจนในการเรียกใช้งาน functions ที่ทำงานอยู่นี้จะเรียกว่า modifiers
increment ไว้สำหรับการเพิ่มจำนวนของเวลาในส่วนของ Time object และสามารถนำมาเขียนให้มีความเรียบง่ายกว่าตามตัวอย่างที่ได้รับการแก้ไขนี้
def increment(time, seconds):
time.seconds = time.seconds + seconds
if time.seconds >= 60:
time.seconds = time.seconds - 60
time.minutes = time.minutes + 1
if time.minutes >= 60:
time.minutes = time.minutes - 60
time.hours = time.hours + 1
ในบรรทัดแรกที่แสดงนั้นเป็นการคำสั่งดำเนินงานพื้นฐานทั่วไป ส่วนที่เหลือนั้นจะเกี่ยวข้องถึงกรณีเฉพาะที่ได้กล่าวมาแล้ว
Function นี้ถูกต้องแล้วหรือ? จะเกิดอะไร ถ้า parameter seconds มีค่าเกินกว่า 60 แล้ว ในกรณีนี้ มันไม่สามารถที่จะทำการ carry ได้ภายในครั้งเดียว ดังนั้นจึงควรรักษาการทำงานนี้ไว้จนกว่า seconds จะมีค่าน้อยกว่า 60 ทางออกอย่างหนึ่งคือ การเปลี่ยนจาก if statement ด้วย
While statements
def increment(time, seconds):
time.seconds = time.seconds + seconds
while time.seconds >= 60:
time.seconds = time.seconds - 60
time.minutes = time.minutes + 1
while time.minutes >= 60:
time.minutes = time.minutes - 60
time.hours = time.hours + 1
Function นี้มีความถูกต้องสมบูรณ์แล้ว แต่มันก็ยังไม่ใช่ทางออกที่มีประสิทธิภาพมากนัก
13.4 Which is better?
แก้ไขอะไรก็ตามที่สามารถทำการแก้ไขด้วยการ modifiersได้ ก็สามารถที่จะใช้ pure function ด้วยเช่นเดียวกัน แต่ในความเป็นจริงแล้วในการเขียนโปรแกรมบางภาษาเท่านั้นที่ทำงานได้ ซึ่งนี่เป็นจุดเด่นของภาษาที่สามารถใช้ pure function ได้ และมีข้อบกพร่องน้อยกว่าการ modifiers แต่ กระนั้น การ modifiers เองก็ยังมีความเหมาะสมกว่าในเวลาที่การทำงานนั้นขาดประสิทธิภาพ
ในกรณีทั่วไป จะแนะนำให้ใช้ pure functions แต่เมื่อใดที่มีเหตุสมควร หรือไม่มีทางเลือกที่ดีกว่าก็ใช้การ modifiers แทน ซึ่งลักษณะเหล่านี้คือ functional programming style
13.5 Prototype development versus planning
แก้ไขในบทนี้แสดงให้เห็นถึงการเริ่มพัฒนาโปรแกรมที่เรียกว่า prototype development โดยในแต่ละกรณี ( case ) จะมีตัวอย่างต้นแบบ ( prototype ) ที่นำไปปฏิบัติได้ และหลังจากที่ได้ทำการทดสอบในกรณีต่างๆ แล้ว ก็จะพบช่องโหว่และจุดบกพร่องเอง
แม้ว่าการทำงานจะมีความสมบูรณ์ มันก็สามารถก่อให้เกิด code ที่ไม่จำเป็น หรือมีความซับซ้อน ดังที่ได้ปฏิบัติในหลายๆ case ที่ผ่านมา การเขียนโปรแกรมที่พบข้อผิดพลาดบ่อยๆนั้นทำให้ขาดความน่าเชื่อถือด้วย
ทางเลือกคือ planned development การใช้ความเข้าใจที่ดีในการแก้ปัญหานั้นสามารถทำให้การเขียนโปรแกรมมีความง่ายยิ่งขึ้น ใน case นี้ สิ่งที่ต้องทำความเข้าใจนั่นคือ Time object ซึ่งจริงๆแล้วมันมี อยู่ 3 จำนวน และอยู่ในเลขฐาน 60 ในส่วนของวินาทีจะประกอบไปด้วย 1 คอลัมน์ ส่วนของนาทีจะประกอบไปด้วย 60 คอลัมน์ และชั่วโมงประกอบไปด้วย 3600 คอลัมน์
เมื่อทำการเขียน addTime และ increment แล้ว
คำแนะนำอีกอย่างในการจัดการกับปัญหาทั้งหมดนั้น เราสามารถเปลี่ยน Time object ไปเป็น single number และทำให้รูปแบบมีความง่ายต่อการคำนวนตัวเลขของคอมพิวเตอร์
ด้านล่างเป็น function ที่เปลี่ยนค่า Time object ไปเป็น integer
def convertToSeconds(t):
minutes = t.hours * 60 + t.minutes
seconds = minutes * 60 + t.seconds
return seconds
ขณะนี้ เราต้องการวิธีที่จะเปลี่ยน integer ไปเป็น Time object
def makeTime(seconds):
time = Time()
time.hours = seconds // 3600
time.minutes = (seconds%3600) // 60
time.seconds = seconds%60
return time
คุณจะต้องใช้ความคิดซักเล็กน้อยในการทำให้ตัวเองแน่ใจว่า function นี้มันถูกต้อง ถ้าแน่ใจแล้ว คุณสามารถใช้มันและ convertToSeconds ไปแก้ไขในส่วนของ addTime
def addTime(t1, t2):
seconds = convertToSeconds(t1) + convertToSeconds(t2)
return makeTime(seconds)
ในรูปแบบนี้มีความสั้นกว่าแบบเดิมมาก และง่ายต่อการตรวจสอบว่ามันถูกต้องด้วย
13.6 Generalization
แก้ไขในบางครั้งการเปลี่ยนจากฐาน 60 เป็น ฐาน 10 แล้วแปลงกลับมานั้นยากกว่าที่จะต้องติดต่อ ( dealing ) กับเวลาให้พอดี การเปลี่ยนฐานจะค่อนข้างเป็นนามธรรมมากกว่า แต่จากความรู้สึกของสัญชาตญาณนั้น การติดต่อร่วมกับเวลาจะมีความเหมาะสมมากกว่า
แต่ถ้าเรามีความเข้าใจเกี่ยวกับเวลาด้วยเลขฐาน 60 และเขียนการเปลี่ยน function (convertToSeconds and makeTime) เราจะพบว่าโปรแกรมนี้มันสั้น ง่ายต่อการอ่านและแก้ไข ข้อบกพร่อง และมีความน่าเชื่อถือ
เช่นเดียวกันนั้น การเพิ่ม features ในภายหลังก็ทำได้ง่ายขึ้นด้วย ในตัวอย่าง ลองคิดถึงการลบกันของ 2 ช่วงเวลา จะพบช่วงเวลาระหว่างพวกมันอยู่ การจะให้ใกล้จุดมุ่งหมายนั้นควรจะใช้เครื่องมือสำหรับการลบด้วยการยืม ( borrowing ) การใช้การแปลง function จะมีความง่ายและโอกาสถูกต้องสูง
น่าแปลกใจที่บางครั้งการสร้างปัญหานั้นก็ยากกว่าการจะทำให้มันถูกต้อง ( เพราะมีแค่บางกรณีและบางโอกาสเท่านั้นที่จะเกิดข้อผิดพลาด )
13.7 Algorithms
แก้ไขเมื่อคุณเขียนการแก้ปัญหาทั่วไปสำหรับ class ของปัญหา โดยไม่ยอมรับชนิดของทางออกที่เป็นปัญหาอย่างหนึ่ง คุณต้องเขียนเป็นขั้นตอนวิธีการ เรากล่าวถึงคำนี้ก่อนแต่อย่าเพิ่งไปกำหนดอะไรแน่ชัด มันไม่ง่ายที่จะกำหนด ดังนั้นควรจะพยายามเชื่อมกันในสิ่งที่คล้ายกัน
ครั้งแรก พิจารณาว่าบางสิ่งนั้นไม่มีวิธีคิดที่แน่นอน เมื่อคุณเรียนรู้เพื่อคูณตัวเลขหลักเดี่ยว เชื่อแน่ว่าคุณจะต้องนึกถึงตารางการคูณตัวเลข ในสิ่งที่เกิดขึ้นนี้ คุณจะต้องจดจำชนิดของทางออกนับร้อย แต่นั้นเป็นเพียงชนิดของความรู้ ไม่ใช่วิธีคิด
แต่ถ้าคุณขี้เกียจแล้ว คุณน่าจะหลีกเลี่ยงมันโดยการศึกษากลเม็ดเหล่านี้ซักเล็กน้อย เช่น การหาผลที่เกิดขึ้นของ n และ 9 ซึ่งคุณสามารถเขียน n-1 เป็นตัวแรกและ 10-n เป็นตัวที่สอง และเทคนิคนี้คือการแก้ปัญหาทั่วไปสำหรับคูณตัวเลขหลักเดี่ยวใดๆ หรือเรียกว่า algorithms
ในทำนองเดียวกัน ความชำนาญนั้นสอนให้คุณเรียนรู้การเพิ่ม (addition) ด้วยการ carrying การลบ (subtraction) ด้วยการ borrowing และการหารยาวก็เป็น algorithms ด้วยทั้งหมด ลักษณะพิเศษอย่างหนึ่งของ algorithme คือ มันไม่ต้องการความสามารถในการคิดและเรียนรู้เพื่อทำให้สมบูรณ์ มีระบบการทำงานอัตโนมัติเป็นขั้นตอนตามข้อกำหนดพื้นฐาน
ในความเห็นส่วนตัวนั้นเห็นว่า มันดูน่าอึดอัดใจ ที่คนส่วนใหญ่จะใช้เวลาจำนวนมากที่โรงเรียนในการเรียนรู้ที่จะจัดการกับ algorithms แต่ที่จริงแล้วนั้นมันไม่มีประโยชน์เลย
ในทางกลับกัน เทคนิคกระบวนการในการออกแบบ algorithms นั้นมีความน่าสนใจ มันท้าท้ายความสามารถของสติปัญญา และมันยังเป็นส่วนสำคัญ หรือที่เรียกว่า programming
การกระทำในบางครั้งนั้น ผู้คนโดยทั่วไปมักจะทำไปตามธรรมชาติ โดยปราศจากการฝึกฝนหรือขาดการตระหนักในความคิดนั้นต้องอาศัยความพยายามอย่างสูงในการแสดงออกทางด้าน algorithmically การเข้าใจในพื้นฐานภาษาเป็นแบบอย่างที่ดี ทุกๆคนทำได้แต่จนถึงทุกวันนี้ก็ไม่มีใครสามารถอธิบายได้ว่า เราต้องทำอย่างไร อย่างน้อยที่สุดมันก็ไม่ใช่ในรูปของ algorithm
13.8 Glossary
แก้ไขpure function: A function that does not modify any of the objects it receives as parameters. Most pure functions are fruitful.
modifier: A function that changes one or more of the objects it receives as parameters. Most modifiers are fruitless.
functional programming style: A style of program design in which the majority of functions are pure.
prototype development: A way of developing programs starting with a prototype and gradually testing and improving it.
planned development: A way of developing programs that involves high-level insight into the problem and more planning than incremental development or prototype development.
algorithm: A set of instructions for solving a class of problems by a mechanical, unintelligent process.