วันศุกร์ที่ 25 กันยายน พ.ศ. 2563

การเขียนโปรแกรมเครือข่ายด้วยซ็อกเก็ตตอนที่ 12 เขียนโปรแกรม TCP Concurrent Server ด้วยภาษา Python

ในบทความวันนี้จะมาปรับปรุงโปรแกรม TCP Iterative Server ที่เขียนด้วยภาษา Python ให้เป็น Concurrent Server ครับ ซึ่งหลักการก็เหมือนกับการเขียนโปรแกรมด้วยภาษา Java นั่นคือใช้ เธรด (thread) มาให้บริการไคลเอนต์ที่ติดต่อเข้ามา โปรแกรมที่ปรับปรุงแล้วเป็นดังนี้ครับ 

01: # tcpserver.py

02: import socket

03: import threading

04: def echo_thread_function(connectionSocket):

05:     clientSentenceBytes = connectionSocket.recv(4096)

06:     clientSentence = clientSentenceBytes.decode("utf-8")

07:     connectionSocket.send(str.encode(clientSentence.upper()))

08:     connectionSocket.close()

09: 

10: welcomeSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

11: welcomeSocket.bind(("localhost", 6789))

12: welcomeSocket.listen(5)

13: while True:

14:     print("The server is waiting")

15:     connectionSocket, address = welcomeSocket.accept()

16:     echoThread = threading.Thread(target=echo_thread_function, args=(connectionSocket,))

17:     echoThread.start()


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

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

สำหรับโค้ดโปรแกรมโหลดได้จากลิงก์นี้ครับ  

วันพฤหัสบดีที่ 3 กันยายน พ.ศ. 2563

การเขียนโปรแกรมเครือข่ายด้วยซ็อกเก็ตตอนที่ 11 เขียนโปรแกรม TCP Iterative Server และ TCP Client ด้วยภาษา Python

บทความวันนี้จะนำเสนอการเขียนโปรแกรมเครือข่ายด้วยซ็อกเก็ต โดยใช้ภาษายอดนิยมแห่งยุคนี้คือ python ครับ สำหรับใครที่เพิ่งมาอ่านบทความในซีรีย์นี้เป็นครั้งแรก แนะนำว่าให้ไปอ่านตอนที่ 1 ตอนที่ 2 ก่อนนะครับ เพื่อให้ได้พื้นฐานความเข้าใจเรื่องซ็อกเก็ตก่อน 

สำหรับบทความนี้ผมจะแสดงตัวอย่างโปรแกรมที่ทำงานเหมือนกับโปรแกรมภาษา Java ในตอนที่ 3 และตอนที่ 4 ที่มีการทำงานโดยการที่ผู้ใช้จะพิมพ์ข้อความเป็นภาษาอังกฤษทางฝั่งไคลเอนต์ (client) จากนั้นส่งข้อมูลมาให้เซิร์ฟเวอร์ (server) ซึ่งจะแปลงตัวอักษรดังกล่าวให้เป็นตัวพิมพ์ใหญ่ทั้งหมด แล้วส่งกลับไปให้ฝั่งไคลเอนต์แสดงผล 

โปรแกรมฝั่งเซิร์ฟเวอร์แสดงได้ดังนี้ครับ 

01: # tcpserver.py

02: import socket

03: welcomeSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

04: welcomeSocket.bind(("localhost", 6789))

05: welcomeSocket.listen(5)

06: while True:

07:     print("The server is waiting")

08:     connectionSocket, address = welcomeSocket.accept()

09:     clientSentenceBytes = connectionSocket.recv(4096)

10:     clientSentence = clientSentenceBytes.decode("utf-8")

11:     connectionSocket.send(str.encode(clientSentence.upper()))

12:     connectionSocket.close()

บรรทัดที่ 2 คือการอิมพอร์ต socket โมดูล บรรทัดที่ 3 สร้างเซิร์ฟเวอร์ซ็อกเก็ตเพื่อรอรับการติดต่อ โดยพารามิเตอร์ที่ใช้ในการสร้างมี 2 ตัว ตัวแรกคือ socket.AF_INET ซึ่งหมายถึงเราระบุว่าจะใช้ IP เวอร์ชัน 4 ตัวที่สอง socket.SOCK_STREAM หมายถึงโปรโตคอล TCP 

บรรทัดที่ 4 ระบุชื่อโฮสต์และพอร์ตที่จะรอบรับการติดต่อ ใช้ localhost เป็นชื่อโฮสต์ เพราะต้องการจะให้การติดต่ออยู่ภายในเครื่องเดียวกันเท่านั้น ถ้าต้องการให้เครื่องภายนอกติดต่อเข้ามาได้ให้ใช้ สตริงว่าง ("") แทน ส่วนพอร์ตที่จะรอรับการติดต่อคือ 6789   

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

จากนั้นเซิร์ฟเวอร์ก็จะเข้าสู่ลูปไม่รู้จบตามการทำงานของเซิร์ฟเวอร์ทั่ว ๆ ไป โดยในลูปนี้ บรรทัดที่ 8 เซิร์ฟเวอร์จะบล็อกรอรับการติดต่อจากไคลเอนต์  โดยเมื่อมีไคลเอนต์ติดต่อเข้ามา จะมีการสร้างซ็อกเก็ตเชื่อมต่อ (connection socket) อ้างถึงโดยตัวแปร connectionSocket และหมายเลขไอพี (IP Address) ของเครื่องไคลเอนต์ที่ติดต่อเข้ามา อ้างถึงโดยตัวแปร address 

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

บรรทัดที่ 10 เป็นการ decode ข้อมูลที่อ่านมาจากซ็อคเก็ตที่อยู่ในรูปสตรีมของไบต์ (stream of bytes) ให้เป็นสตริง เพื่อที่จะได้เรียกใช้ฟังก์ชัน upper() เพื่อแปลงข้อมูลที่รับมาให้เป็นตัวอักษรภาษาอังกฤษตัวใหญ่ 

บรรทัดที่ 11 จะซับซ้อนนิดหนึ่งนะครับ เพราะทำหลายอย่าง ในส่วน clientSentence.upper() ก็คือการแปลงข้อมูลเป็นตัวอักษรตัวใหญ่ ในส่วน str.encode(clientSentence.upper()) ก็คือการทำให้สตริงที่จะส่งไป (ในที่นี้คือสตริงที่เราแปลงเป็นตัวใหญ่) อยู่ในรูปสายธารของไบต์ จากนั้นก็ใช้ฟังก์ชัน send() ส่งข้อมูลผ่านซ็อกเก็ตกลับไปให้ไคลเอนต์ 

บรรทัดที่ 12 ก็คือการปิดซ็อกเก็ตเชื่อมต่อกับไคลเอนต์ หลังจากบรรทัดนี้โปรแกรมก็จะวนกลับไปต้นลูปเพื่อรอรับการติดต่อและให้บริการไคลเอนต์ตัวต่อไป  

คราวนี้มาดูโปรแกรมฝั่งไคลเอนต์กันครับ 

01: # tcpclient.py

02: import socket

03: sentence = input("Please enter words: ")

04: clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

05: clientSocket.connect(("localhost", 6789))

06: clientSocket.send(str.encode(sentence))

07: modifiedSentenceBytes = clientSocket.recv(4096)

08: modifiedSentence = modifiedSentenceBytes.decode("utf-8")

09: clientSocket.close()

10: print(modifiedSentence)


บรรทัดที่ 2 คือการอิมพอร์ต socket โมดูล บรรทัดที่ 3 รอรับข้อมูลที่ผู้ใช้ป้อน โดยแสดงข้อความ Please enter words เพื่อบอกผู้ใช้ ค่าที่ผู้ใช้ป้อนจะเก็บในตัวแปร sentence 

บรรทัดที่ 4 สร้างซ็อกเก็ตเชื่อมต่อ โดยพารามิเตอร์ที่ใช้มีความหมายเหมือนกับที่อธิบายไว้แล้วในโปรแกรมเซิร์ฟเวอร์ 

บรรทัดที่ 5 สร้างการเชื่อมต่อกับโปรแกรมเซิร์ฟเวอร์โดยใช้หมายเลขพอร์ต 6789 ตามที่โปรแกรมเซิร์ฟเวอร์ใช้ 

บรรทัดที่ 6 ส่งข้อมูลผ่านซ็อกเก็ต โดยแปลงข้อมูลให้อยู่ในรูปสายธารของไบต์ 

บรรทัดที่ 7 รออ่านข้อมูลที่เซิร์ฟเวอร์จะตอบกลับมา บรรทัดที่ 8 แปลงข้อมูลจากสายธารของไบต์เป็นสตริง 

บรรทัดที่ 9 ปิดซ็อกเก็ตเชื่อมต่อ และบรรทัดที่ 10 พิมพ์ผลลัพธ์ออกมางจอภาพ จากนั้นโปรแกรมไคลเอนต์จะจบการทำงาน 

ครางนี้มาลองรันโปรแกรมกันดูครับ ผมจะรันโปรแกรมจากเทอร์มินอลที่เปิดในโปรแกรม VS Code นะครับ แต่ใครจะรันผ่านเทอร์มินัลที่ตัวเองใช้อย่าง command prompt ของ Windows หรือเทอร์มินัลของ Mac หรือ Linux ก็ได้ครับ คำสั่งที่ใช้รันโปรแกรมเซิร์ฟเวอร์ก็คือ python tcpserver.py ดังรูปครับ 


เมื่อรันเสร็จจะเห็นว่าเซิร์ฟเวอร์จะพิมพ์คำว่า  The server is waiting และหยุดรอรับการติดต่อจากไคลเอนต์

ต่อไปไปรันไคลเอนต์กันครับ ก็ให้เปิดเทอร์มินัลขึ้นมาอีกหน้าต่างหนึ่งครับ และใช้คำสั่ง python tcpclient.py ดังรูปครับ 


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

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

สำหรับโค้ดของโปรแกรมโหลดได้จากที่นี่ครับ 

วันศุกร์ที่ 28 สิงหาคม พ.ศ. 2563

นิยามของค่าสัมบูรณ์เรื่องง่าย ๆ ที่หลายคนยังงง ๆ อยู่

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

1. |x| = x เมื่อ x >= 0 
2. |x| = -x เมื่อ x < 0 

ในวิชาที่ผมสอนวิชาหนึ่งคือ Automata Theory ผมก็จะทบทวนการพิสูจน์ทางคณิตศาสตร์ให้นักศึกษานิดหน่อย และก็ให้นิยามนี้ เพื่อให้นักศึกษาได้ใช้ในการทบทวนทำแบบฝึกหัดเรื่องการพิสูจน์ ซึ่งแทบทุกครั้งที่สอนก็มักจะมีคำถามจากนักศึกษาว่า นิยามผิดหรือเปล่า เพราะเคยเรียนมาแต่ว่าค่าสัมบูรณ์ต้องมีค่าเป็นบวก ทำไม |x| = -x ซึ่งผมก็มักจะบอกให้ดูนิยามดี ๆ แต่หลายคนดูแล้วก็ยังไม่เข้าใจอยู่ดี ก็เลยต้องอธิบายให้ฟัง แล้วก็ทำซ้ำ ๆ มาแบบนี้หลายปีแล้ว ก็เลยคิดว่ามาเขียนบล็อกไว้หน่อยแล้วกัน ต่อไปถ้าถามอีกก็จะให้มาอ่านในบล็อก และก็คิดว่าน่าจะเป็นประโยชน์กับคนที่ยังไม่เข้าใจด้วย 
 
ผมขออธิบายง่าย ๆ ด้วยการยกตัวอย่างครับ คือทุกคนรู้ว่า |3| = 3 และ |-3| = 3 

มาดูกรณี  |3|  ในที่นี้ x = 3 ดังนั้น x > 0 
เพราะฉะนั้น |x| = x จากนิยามในข้อ 1 
นั่นคือ  |3| = 3 (เพราะ  x = 3) 

ในกรณี |-3| ในที่นี้ x = -3 ดังนั้น x < 0 
เพราะฉะนั้น |x| = -x จากนิยามในข้อ 2 
นั่นคือ  |-3| = -(-3) (เพราะ  x = -3) 
และ -(-3) = 3 
ดังนั้น |-3| = 3

ก็หวังว่าจะเข้าใจกันถึงนิยามของค่าสัมบูรณ์กันมากขึ้นแล้วนะครับ
 
 

วันศุกร์ที่ 31 กรกฎาคม พ.ศ. 2563

แนะนำวิชาบล็อกเชนสำหรับคนที่ไม่ใช่นักพัฒนาบน Coursera

วันนี้ผมจะมาแนะนำวิชาเรียนออนไลน์บน coursera.org ในวิชาที่เกี่ยวกับบล็อกเชน (blockchain) แต่ไม่ได้เกี่ยวกับการพัฒนาโปรแกรมนะครับ แต่จะเป็นเนื้อหาบล็อกเชนที่ให้คนทั่ว ๆ ไป เข้าใจ โดยเฉพาะคนที่อยู่ในภาคธุรกิจที่อาจเคยได้ยินคำว่าบล็อกเชนมา แต่ไม่รู้ว่ามันจะมาใช้ในทางธุรกิจยังไงบ้าง หรือคนที่อาจจะรู้จักแต่บิตคอยน์ (bitcoin) และอาจเข้าใจว่าบล็อกเชนกับบิตคอยน์เป็นเรื่องเดียวกัน ถ้าได้เรียนวิชาพวกนี้ก็จะเข้าใจบล็อกเชนมากขึ้น ถึงหลายคนที่ตอนนี้อาจรู้อยู่แล้ว หรืออาจบอกว่าหาอ่านเอาบนอินเทอร์เน็ต ดูบน YouTube ก็ได้ แต่ถ้าได้เรียนฟรี ๆ และอาจมีประกาศนียบัตรติดตัวด้วย อันนี้ก็น่าสนใจใช่ไหมครับ 

Coursera | Build Skills with Online Courses from Top Institutions
Coursera Logo

สำหรับคนที่ยังไม่รู้ว่า Coursera คืออะไร ก็ขอสรุปง่าย ๆ ว่า Coursera เป็นแพลตฟอร์มเรียนออนไลน์รายใหญ่ของโลก มีวิชาที่น่าสนใจจากมหาวิทยาลัยชั้นนำ และบริษัทชั้นนำอย่างเช่น Google มาเปิดให้เลือกเรียนมากมายครับ โดยในช่วง COVID-19 นี้ เขาเปิดโอกาสให้บุคคลากรและนักศึกษาของมหาวิทยาลัยที่เข้าร่วมโครงการ เรียนได้ฟรีและได้ประกาศนียบัตรด้วย โดยขยายขอบเขตให้สมัครเข้าเรียนได้ถึง 30 กันยายน 2563 นี้ครับ ตามปกติ  Coursera ก็มีวิชาให้เรียนฟรีอยู่แล้ว แต่จะไม่ได้ประกาศนียบัตร ถ้าอยากได้ประกาศนียบัตรต้องจ่ายเงิน แต่เขาก็ยังให้ทางเลือกนะครับคือถ้าเราสมัครเป็นครั้งแรกเขา จะให้ทดลองเรียนฟรี  7 วัน คือเราต้องให้ข้อมูลบัตรเครดิตเขาไปก่อน และเราสามารถยกเลิกได้ภายใน 7 วันนี้ นั่นหมายความว่าถ้าภายใน 7 วันนี้เราเรียนจบวิชาอะไรก็จะได้ประกาศนียบัตรด้วยครับ และจะเข้ามาดูเนื้อหาวิชาที่เราเคยลงเรียนไว้แล้วได้ตลอด     

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

วิชาที่ผมจะนำมาแนะนำก็เป็นวิชาที่สอนโดยวิทยากรสองคน ที่เป็นคนเขียนหนังสือบล็อกเชนที่ขายดีระดับโลกคือ Don Tapscott และ Alex Tapscott หนังสือที่เขาเขียนก็คือ Blockchain Revolution ครับ 

[ภาพปกหนังสือ Blockchain Revolution. Credited: amazon.com]

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

วิชาที่ผมจะแนะนำ เพราะเป็นวิชาที่ให้ความรู้ทั่ว ๆ ไป  มี 2 วิชาครับคือ Introduction to Blockchain for Financial Services กับ Cryptoassets, and Decentralized Finance

[Introduction to Blockchain for Financial Services course; Coursera]


มาเริ่มที่วิชาแรกคือ Introduction to Blockchain for Financial Services กันครับ วิชานี้แบ่งเป็น 5 โมดูล โดยเขาจะให้เรียนหนึ่งโมดูลต่อหนึ่งสัปดาห์ นั่นคือถ้าเราเรียนตามโปรแกรมเขาก็จะใช้เวลาเดือนเศษ ๆ ครับ โดย 5 โมดูลมีดังนี้ครับ

  • The Second Era of the Internet 
  • Blockchain Design Principles
  • Public and Private Ledger
  • The Blockchain Ecosystem
  • Blockchain Implementation Challenges
ในโมดูลแรก The Second Era of the Internet จะเป็นการเปรียบเทียบให้เห็นว่าบล็อกเชนกับอินเทอร์เน็ตที่เราใช้กันมีความสัมพันธ์กัน และมีความแตกต่างกันอย่างไร 

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

โมดูลที่ 3 Public and Private Ledger ในโมดูลนี้จะพูดถึงประเภทของบล็อกเชน พวกเราอาจเคยได้ยินแต่บิตคอยน์ ซึ่งจริง ๆ แล้ว เป็น public blockchain แต่จริง ๆ แล้ว มันยังมีประเภทอื่น ๆ ของบล็อกเชนอีก และในโมดูลนี้ยังได้พูดถึงการรักษาความเป็นส่วนตัวบนบล็อกเชนด้วยครับ

โมดูลที่ 4 The Blockchain Ecosystem จะพูดถึงประเภทของผู้ที่เกี่ยวข้อง และบทบาทของผู้เกี่ยวข้องในบล็อกเชน โดยเขาได้แบ่งผู้เกี่ยวข้องออกเป็น 9 ฝ่ายครับ ซึ่งแน่นอนว่าพวกเราส่วนใหญ่จะอยู่ในส่วนของ user ส่วนตัวผมเองก็จะอยู่ในส่วนของภาคการศึกษา (education) ด้วย แต่มันจะมีฝ่ายอะไรอีกก็ลองไปดูกันครับ 

โมดูลที่ 5 Blockchain Implementation Challenges ในโมดูลนี้จะพูดถึงปัญหาและอุปสรรคที่เกิดกับการพัฒนาการของบล็อกเชนครับ ซึ่งก็เหมือนกับเทคโนโลยีที่เพิ่งเปิดตัวแหละครับ ก็มักจะมีปัญหา และมีความไม่เข้าใจจากฝ่ายที่อยู่บนเทคโนโลยีเดิม ๆ อย่างที่พวกเราคงได้ข่าวกันว่ารัฐบาลหลาย ๆ ประเทศยังไม่รองรับการใช้เงินคริปโตเป็นต้น ในโมดูลนี้ก็จะแสดงให้เห็นถึงปัญหา และแนวทางการแก้ปัญหาครับ 

เอาจริง ๆ แล้ว เรียนแค่วิชานี้ ก็เข้าใจบล็อกเชนได้ในระดับหนึ่งแล้วครับ แต่ถ้าใครยังอยากรู้เรื่องแอปพลิเคชันของบล็อกเชน อย่างเช่นพวกเงินคริปโต อยากรู้จักว่า Smart Contracts มันคืออะไร หรืออาจเคยได้ยินคำว่า DApps แต่ไม่รู้ว่ามันคืออะไร ก็เรียนวิชานี้ต่อเลยครับ  Blockchain, Cryptoassets, and Decentralized Finance โดยวิชานี้แบ่งเป็นแค่ 4 โมดูล ดังนี้ครับ 

  • Cryptoassets
  • Smart Contracts
  • Identity
  • DApps and Distributed Business Models
[Blockchain, Cryptoassets, and Decentralized Finance; Coursera]



โมดูลแรก Cryptoassests บล็อกเชนนั้นถูกทำให้เป็นที่รู้จักจากแอปพลิเคชันของมันคือบิตคอยน์ (bitcoin) ซึ่งเป็นสิ่งที่เรียกว่า สกุลเงินเข้ารหัส (Crypto Currency) และต่อมาเราอาจได้ยินคำว่า โทเค็น (Token) ซึ่งในโมดูลนี้จะแบ่างประเภทให้เราเห็นครับว่านอกจากสองตัวนี้แล้วมันยังมีอะไรอีก  

โมดูลที่ 2 พูดถึง Smart Contracts ซึ่งเป็นส่วนสำคัญที่ทำให้เราสามารถใช้บล็อกเชนในการพัฒนาแอปพลิเคชันที่ไม่ใช่แค่แอปพลิเคชันด้านการโอนเงินกันอย่างเดียว ถ้าพูดในฐานะนักเขียนโปรแกรม Smart Conracts ก็คือโปรแกรมคอมพิวเตอร์ แต่อย่างที่บอกไว้นะครับว่าวิชาที่ผมแนะนำวันนี้ ไม่ใช่สำหรับนักพัฒนา ดังนั้น เขาก็จะไม่ได้สอนเราเขียน Smart Contracts แต่จะพูดให้เห็นประโยชน์ และการใช้งานในภาพรวม 

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

โมดูลที่ 4 DApps and Distributed Business Models จะพูดถึงแอปพลิเคชันที่ต่อไปเราจะใช้กัน ซึ่งเบื้องหลังการทำงานของแอปพลิเคชันเหลานี้ก็คือบล็อกเชนครับ โดยเขายกตัวอย่างใกล้ตัวที่เรารู้จักกันดีอย่าง Airbnb ซึ่งตอนนี้ไม่ได้ทำงานบนบล็อกเชน ถ้าต่อไปเรามี bAirbnb ซึ่งทำงานบนบล็อกเชนจะมีข้อดีกว่ายังไง และก็ยังพูดถึงการใช้บล็อกเชนในเรื่องการป้องกันทรัพย์สินทางปัญญา ซึ่งเป็นปัญหาใหญ่ของอินเทอร์เน็ตในปัจจุบัน 

ในส่วนตัวถ้าเรียนสองวิชานี้จบ ผมเชื่อว่าน่าจะเข้าใจภาพรวมของบล็อกเชนในมุมมองของผู้ใช้ทั่วไปได้ค่อนข้างดีมากเลยครับ แต่ถ้าใครอยู่ในวงการการเงินและบัญชี ผมก็ขอเสริมแนะนำวิชานี้ Blockchain Transformation of Financial Services  อีกวิชาครับ 

[Blockchain Transformation of Financial Services; Coursera]

 ต่อไปผมจะมาแนะนำวิธีเรียนสำหรับคนที่อยากได้ประกาศนียบัตรในช่วงทดลองเรียน 7 วันกันครับ หลายคนอาจคิดว่าสองวิชารวมกันตั้ง 9 โมดูล แล้วจะเรียนทันยังไง ผมบอกเลยครับว่า แต่ละโมดูลเราสามารถเรียนให้ผ่านได้ภายในตอนเย็นหลังจากเราเลิกงานกลับบ้านแล้วนี่แหละครับ ในแต่ละโมดูล เขาจะมีวีด๊โอให้เราดู โดยแต่ละวีดีโอจะเป็นคลิปสั้น ๆ โดยแต่ละโมดูลก็จะมีวีดีโอรวมกันความยาวรวมประมาณ 50 นาที  และก็จะมีลิงก์ให้เราไปอ่านเพิ่มเติม ซึ่งผมแนะนำว่าในช่วงที่เราเรียนนี้เรายังไม่ต้องไปอ่านก็ได้ครับ เพราะคำถามที่เขาถามจะมาจากสิ่งที่อยู่ในวีดีโอ แต่ผมแนะนำนะครับว่าเมื่อเราเรียนจบได้ประกาศนียบัตรแล้ว ให้กลับมาอ่านครับจะมีประโยชน์มาก ไม่ต้องกังวลว่ายกเลิกการจ่ายเงินไปแล้ว วิชาที่เราลงทะเบียนไปจะหายไปครับ ดังนั้นไปเอาประกาศนียบัตรมาก่อน แล้วค่อยกลับมาอ่านเมื่อมีเวลาได้ตามสบายครับ

[ตัวอย่างประกาศนียบัตรจาก Coursera]


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

สำหรับคนที่เรียนสองวิชาที่วิทยากรสองคนนี้สอนแล้วรู้สึกอย่างเรียนเพิ่มกับสองคนนี้ เท่าที่ผมสำรวจดู
วิชาด้านบล็อกเชนที่สอนโดยสองคนนี้ มี 8 วิชาครับ โดยเขาจัดกลุ่มวิชาออกเป็นสิ่งที่ Coursera เรียกว่า Specialization โดย Specialization ที่เก่ากว่าชื่อว่า Blockchain Revolution เหมือนชื่อหนังสือของเขาเลยเลยครับ  โดย Specialization นี้จะมี  4 วิชาคือ  

  • Introduction to Blockchain Technologies  
  • Transacting on the Blockchain
  • Blockchain and Business: Applications and Implications 
  • Blockchain Opportunity Analysis  

Specialization ที่สองชื่อว่า  Blockchain Revolution in Financial  Services ครับ อันนี้จะใหม่กว่า มีการปรับปรุง และพูดถึงหนังสือเล่มใหม่ที่เขียนด้วย ใน Specialization นี้ก็มี 4 วิชาครับคือ 

  • Introduction to Blockchain for Financial Services
  • Blockchain, Cryptoassets, and Decentralized Finance
  • Blockchain Transformations of Financial Services 
  • Blockchain in Financial Services: Strategic Action Plan 

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

และจากที่ผมสำรวจมาอีกก็พบครับว่า วิชา Introduction to Blockchain Technologies กับ Introduction to Blockchain for Financial Services  นั้นเนื้อหาแทบจะเหมือนกันเลย และข้อสอบก็เหมือนกัน ดังนั้นถ้าเรียน Introduction to Blockchain for Financial Services ผ่านแล้ว เราสามารถสมัครเรียนวิชา Introduction to Blockchain Technologies แล้วไปทำข้อสอบได้เลยครับ ไม่ต้องไปเสียเวลาดูวีดีโออีก เราก็จะได้ประกาศนียบัตรมาอีกใบหนึ่งเลยครับ ถ้าใครชอบล่าประกาศนียบัตรเท่ากับเรียนหนึ่งได้ถึงสอง และเช่นเดียวกันครับวิชา Transacting on the Blockchain กับวิชา Blockchain, Cryptoassets, and Decentralized Finance ก็แบบเดียวกันเลยครับ นั่นคือถ้าวางแผนกันดี ๆ นี่เราจะได้ประกาศนียบัตร 4 ใบเลยนะครับ เรียน 2 แถม 2 :) 

ส่วนวิชาที่เหลืออีก 3 วิชา ผมยังไม่ได้ลองเรียนดูนะครับ เหตุผลหลัก ๆ คือยังมีวิชาอื่นที่ผมสนใจมากกว่า และมันมีส่วนที่ผมไม่ชอบที่สุดใน Coursera คือข้อสอบที่เป็น Peer Review ครับ ข้อสอบแบบ Peer Review ก็คือ เราทำแล้วคนที่เรียนกับเราเป็นคนตรวจ โดยเราก็จะต้องไปตรวจคนอื่นด้วย 2-3 คน ดูตามแนวคิดมันก็ดีใช่ไหมครับ แต่จากประสบการณ์ที่ผมเจอเมื่อผมไปตรวจคนอื่นคือ หลายคนไม่รู้เรื่องที่เรียนเลย บางคนตอบไม่ตรงคำถาม บางคนไป copy อะไรไม่รู้ ไม่เกี่ยวกับเรื่องที่เรียนด้วยซ้ำมาตอบ บางคนตอบ Good คำเดียว แล้วคนแบบนี้ต้องมารีวิวเรา บางคนตอบมั่ว ๆ มา เพื่ออะไรรู้ไหมครับ เพราะใน Coursera บอกว่าเราต้องตอบก่อน ถึงจะไปรีวิวคนอื่นได้ บางคนตอบมั่วเพื่อที่จะได้เห็นคำตอบคนอื่น แล้วก็ copy คำตอบคนอื่นมาเป็นของตัวเอง ตอนผมตรวจให้คนอื่นนี่เห็นเลยครับว่าลอกต่อ ๆ กันมา ซึ่งผมว่าถ้า Coursera ยังใช้โมเดลนี้ต่อไป มันจะทำให้ประกาศนียบัตรลดความน่าเชื่อถือลงในวิชาที่เป็นแบบนี้ แต่เท่าที่เห็นตอนนี้ Coursera เริ่มมีระบบเชิญคนที่เรียนผ่านไปแล้วและมีความรู้มาช่วยเป็น mentor แล้ว แต่ไม่รู้ว่าเขาจะเอามาช่วยตรวจไหมนะครับ

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

วันพฤหัสบดีที่ 23 กรกฎาคม พ.ศ. 2563

ปัญหาเบสคลาสแตกง่ายเชิงความหมาย (Semantic Fragile Base Class Problem)

บล็อกนี้ก็เป็นบล็อกที่สามในชุดของปัญหาเบสคลาสแตกง่าย (Fragile Base Class Problem) โดยผมจะขอเรียกสั้น ๆ ว่า FBC ถ้าใครยังไม่ได้อ่านจะกลับไปอ่านก่อนก็ได้นะครับ FBCSyntactic FBC แต่ถ้าจะไม่อ่านก็ไม่เป็นไรนะครับอ่านบล็อกนี้ก่อนได้

สำหรับปัญหา Semantic FBC เป็นปัญหาที่มีผลกระทบมากกว่า Syntactic ครับ เพราะเป็นปัญหาระดับการออกแบบ ไม่ใช่ปัญหาระดับภาษาโปรแกรม นั่นหมายความว่าไม่ว่าเราจะเขียนโปรแกรมด้วยภาษา object-oriented ใด ๆ ถ้ามีการใช้ inheritance และ polymorphism มีโอกาสมีปัญหาได้หมด 

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

เพื่อให้เข้าใจปัญหานี้มาเริ่มดูโค้ดภาษา Java ต่อไปนี้กันครับ ถ้าใครไม่อยากพิมพ์โค้ดเองดาวน์โหลดได้จากที่นี่เลยครับ 

public class SemanticFBCSuperclass {
private int number;
public SemanticFBCSuperclass(int val) {
number = val;
}
public void f1() {
System.out.println("In f1() of SemanticFBCSuperClass");
//number++;
setNumber(number+1);
}
public void setNumber(int val) {
number = val;
}
}

คลาสนี้เป็นคลาสแม่นะครับ ให้สังเกตเมท็อด f1() นะครับมีการพิมพ์ข้อความหนึ่งบรรทัด และเพิ่มค่า number ซึ่งเป็น attribute ในคลาสนี้ขึ้น 1โดยใช้เมท็อด setNumber()

ต่อไปไปดูคลาสลูกของคลาสแม่ตัวนี้กันครับ

public class SemanticFBCSubclass extends SemanticFBCSuperclass {
public SemanticFBCSubclass(int val) {
super(val);
}
@Override
public void setNumber(int val) {
System.out.println("setNumber() overridden version");
super.setNumber(val);
}
}

จะเห็นว่าในคลาสลูกนี้ โอเวอร์รายด์ (override) เมท็อด setNumber() ของคลาสแม่ โดยพิมพ์ข้อความหนึ่งบรรทัด ก่อนที่จะเรียกเมท็อด setNumber() ของคลาสแม่ 

คราวนี้มาดู Main โปรแกรมกันครับ 

*
* @author sarun
*/
public class FBC {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
SemanticFBCSubclass obj = new SemanticFBCSubclass(5);
obj.f1();

}
}

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

sarunintakosum@Saruns-MacBook-Pro semanticfbc % javac *.java
sarunintakosum@Saruns-MacBook-Pro semanticfbc % java FBC    
In f1() of SemanticFBCSuperClass
setNumber() overridden version
sarunintakosum@Saruns-MacBook-Pro semanticfbc % 

มาดูที่ผลัพธ์กันครับ ผลลัพธ์สองบรรทัดมาจากเมื่อเรียกเมท็อด f1() ก็จะเริ่มต้นทำงานโดยพิมพ์คำว่า 
In f1() of SemanticFBCSuperClass

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

setNumber() overridden version

จากนั้นจะกลับไปเรียก setNumber() ของคลาสแม่ต่อไป ถ้าตอนนี้ยังไม่เข้าใจก็เอาเป็นแค่ว่ามีผลลัพธ์สองบรรทัดนะครับ 

คราวนี้สมมติว่ามีการทบทวนคลาสแม่กันใหม่ แล้วก็เห็นว่าใน f1() ถ้าจะเพิ่มค่า number ขึ้น 1 จะทำงานเร็วขึ้น ถ้าเพิ่มค่าตรง ๆ เลย ไม่ต้องไปเรียกเมท็อด setNumber() ดังนั้นคลาสแม่จึงถูกแก้เป็นดังนี้ครับ 

public class SemanticFBCSuperclass {
private int number;
public SemanticFBCSuperclass(int val) {
number = val;
}
public void f1() {
System.out.println("In f1() of SemanticFBCSuperClass");
number++;
//setNumber(number+1);
}
public void setNumber(int val) {
number = val;
}
}

จะเห็นนะครับว่าใน f1() ไม่เรียก setNumber() แล้ว 

จากนั้นคอมไพล์คลาสแม่และรันโปรแกรมครับ

sarunintakosum@Saruns-MacBook-Pro semanticfbc % javac SemanticFBCSuperclass.java
sarunintakosum@Saruns-MacBook-Pro semanticfbc % java FBC                        
In f1() of SemanticFBCSuperClass
sarunintakosum@Saruns-MacBook-Pro semanticfbc % 

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

ดังนั้นคงเห็นแล้วนะครับว่าปัญหา FBC โดยเฉพาะ Semantic FBC ทำให้เกิดปัญหาถ้าต้องมีการเปลี่ยนแปลงโปรแกรม เนื่องจากมีความขึ้นต่อกันระหว่างคลาสแม่และคลาสลูก ดังนั้นในการพัฒนาโปรแกรมโดยใช้แนวคิดของคอมโพเนนต์ จึงหลีกเลี่ยงการใช้งาน inheritance และ polymorphism และใช้การประกอบแทน 

ก็เป็นอันว่าจบชุดของปัญหา FBC กันไว้ตรงนี้นะครับ โอกาสหน้าจะเขียนเรื่องอะไร ถ้าสนใจก็คอยคิดตามได้นะครับ

วันพฤหัสบดีที่ 16 กรกฎาคม พ.ศ. 2563

ปัญหาเบสคลาสแตกง่ายเชิงไวยากรณ์ (Syntactic Fragile Base Class Problem)

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

สำหรับปัญหานี้แก้ได้ในระดับตัวภาษา หรือตัวลิงก์เกอร์ (linker) เอง แต่ก็มีภาษาบางภาษาอย่าง C++ ที่ยังไม่ได้แก้ปัญหานี้ แต่ภาษาอย่าง Java ซึ่งใช้  class loader โหลดคลาสขึ้นไปในตอนรัน จะมีการเช็คให้จะไม่มีปัญหานี้ ซึ่งในบล็อกนี้ผมจะแสดงตัวอย่างให้ดูโดยใช้สองภาษานี้ครับ 

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

 เริ่มจาก C++ ก่อนแล้วกันนะครับ 

สมมติมีคลาส foo แบบนี้นะครับ


//foo.hpp
class foo {
private:
int a;
public:
void setA(int val);
int getA();
void f1();
//void f3();
};

//foo.cpp
#include "foo.hpp"
#include <iostream>
using namespace std;
void foo::setA(int val) {
a = val;
}
int foo::getA() {
return a;
}
void foo::f1() {
cout << "I am f1" << endl;
}

/*void foo::f3() {
cout << "I am f3" << endl;
}*/

ให้สังเกตที่ผมคอมเมนต์ไว้นะครับ ตอนนี้คลาส foo ยังไม่มีเมท็อด f3() 

ต่อไปมาดูคลาส bar ซึ่งเป็นคลาสลูกของ foo ครับ

//bar.hpp
#include "foo.hpp"
class bar: public foo {
public:
void f2();
void f3();
};

//bar.cpp
#include "bar.hpp"
#include <iostream>
using namespace std;
void bar::f2() {
f1();
cout << "I am f2" << endl;
}
void bar::f3() {
cout << "I am f3" << endl;
}

จะเห็นนะครับว่า เมท็อด f3() ตอนนี้ถูกอิมพลีเมนต์ในคลาส bar

สมมติว่าสองคลาสนี้เราไม่ได้เขียนเอง มีคนส่ง object code มาให้เรา พร้อมทั้ง header file นั่นคือสองคลาสนี้ถูกคอมไพล์ด้วยคำสั่ง 

g++ -c foo.cpp 
g++ -c bar.cpp

ซึ่งจะทำให้ได้ objce file ชื่อ foo.o และ bar.o ตามลำดับ ดังตัวอย่างด้านล่าง และไฟล์ .o ถูกส่งมาให้เรา พร้อมทั้ง foo.hpp และ bar.hpp

sarunintakosum@Saruns-MacBook-Pro cpp % g++ -c bar.cpp
sarunintakosum@Saruns-MacBook-Pro cpp % g++ -c foo.cpp
sarunintakosum@Saruns-MacBook-Pro cpp % ls *.o
bar.o   foo.o
sarunintakosum@Saruns-MacBook-Pro cpp % 

ต่อไปสมมติว่าเราเขียนคลาส dar ให้เป็นลูกของคลาส bar ดังนี้ครับ

//dar.hpp
#include "bar.hpp"
class dar: public bar {
public:
void f4();
};


//dar.cpp
#include "dar.hpp"
#include <iostream>
using namespace std;
void dar::f4() {
f3();
cout << "I am f4" << endl;
}

ให้สังเกตว่าเมท็อด f4() เรียกใช้ เมท็อด f3() ซึ่งในเวอร์ชันนี้อยู่ในคลาส bar

และสมมติ main program เป็นดังนี้ 

#include <iostream>
#include "dar.hpp"
using namespace std;
int main() {
dar *obj = new dar();
obj->f4();
return 0;
}

จากนั้นใช้คำสั่งดังนี้ 

sarunintakosum@Saruns-MacBook-Pro cpp % g++ -c main.cpp      
sarunintakosum@Saruns-MacBook-Pro cpp % g++ -c dar.cpp
sarunintakosum@Saruns-MacBook-Pro cpp % g++ -o main main.o foo.o bar.o dar.o
และรันโปรแกรม
sarunintakosum@Saruns-MacBook-Pro cpp % ./main
I am f3
I am f4
sarunintakosum@Saruns-MacBook-Pro cpp % 

จะเห็นว่าคอมไพล์ผ่าน และรันได้ผลลัพธ์ถูกต้อง

สมมติว่ามีการแก้คลาส  foo และ bar โดยการเลื่อนเมท็อด f3() จาก bar ขึ้นไป foo ดังนี้ 

//foo.hpp
class foo {
private:
int a;
public:
void setA(int val);
int getA();
void f1();
void f3();
};

//foo.cpp
#include "foo.hpp"
#include <iostream>
using namespace std;
void foo::setA(int val) {
a = val;
}
int foo::getA() {
return a;
}
void foo::f1() {
cout << "I am f1" << endl;
}
void foo::f3() {
cout << "I am f3" << endl;
}

//bar.hpp
#include "foo.hpp"
class bar: public foo {
public:
void f2();
//void f3();
};

//bar.cpp
#include "bar.hpp"
#include <iostream>
using namespace std;
void bar::f2() {
f1();
cout << "I am f2" << endl;
}
/*void bar::f3() {
cout << "I am f3" << endl;
}*/

จากนั้นทั้งสองคลาสถูกคอมไพล์และ ไฟล์ foo.o และ bar.o และ foo.hpp กับ bar.hpp  ตัวใหม่ถูกส่งกลับมาที่เรา คราวนี้สมมติว่าเราเห็นว่าเราไม่ได้แก้โปรแกรม dar.cpp และ main.cpp เลย เราก็เลยใช้คำสั่งต่อไปนี้ลิงก์โปรแกรมเข้าด้วยกัน 
sarunintakosum@Saruns-MacBook-Pro cpp % g++ -o main main.o foo.o bar.o dar.o
Undefined symbols for architecture x86_64:
  "bar::f3()", referenced from:
      dar::f4() in dar.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
sarunintakosum@Saruns-MacBook-Pro cpp % 

ซึ่งจะเห็นนะครับว่ามีข้อผิดพลาดเกิดขึ้นจากการลิงก์ เพราะจากข้อมูลเดิมของ dar.o  f3() ต้องอยู่ใน bar ซึ่งจริง ๆ แล้วตามแนวคิดของ object-oriented คลาส dar ซึ่งเป็นคลาสลูก ไม่มีความจำเป็นต้องรู้นะครับว่า f3() อยู่ที่ไหนกันแน่จะอยู่ที่คลาสแม่ หรือคลาสยาย :) ก็ควรจะเรียกได้เหมือนกัน ตราบใดที่ยังอยู่ในลำดับชั้นของครอบครัวเดียวกัน แต่จากข้อผิดพลาดนี้ C++ บอกเราว่าเราต้องรู้ครับ  

ซึ่งข้อผิดพลาดนี้แก้ได้ด้วยการคอมไพล์คลาส dar แล้วก็ลิงก์ใหม่ครับ

sarunintakosum@Saruns-MacBook-Pro cpp % g++ -c dar.cpp
sarunintakosum@Saruns-MacBook-Pro cpp % g++ -o main main.o foo.o bar.o dar.o
sarunintakosum@Saruns-MacBook-Pro cpp % ./main                              
I am f3
I am f4
sarunintakosum@Saruns-MacBook-Pro cpp % 

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

แต่จะเรียกว่าข่าวดีได้หรือเปล่าไม่รู้นะครับ เพราะภาษา object-oriented ที่ยังมีปัญหานี้อยู่เท่าที่รู้ก็คือ C++ เท่านั้น ซึ่งเหตุผลที่ C++ ใช้ในการไม่แก้ก็คือเรื่องของประสิทธิภาพของโปรแกรมครับ คือถ้าใช้วิธีที่ใช้อยู่นี้จะได้โปรแกรมที่ทำงานเร็วกว่าการไปแก้ปัญหานี้ ดังนั้นถ้าใช้ C++ ต้องขยันคอมไพล์ครับ :) 

คราวนี้ลองมาดูภาษาที่ไม่มีปัญหานี้กันบ้างนะครับ ตัวอย่างที่จะมาดูกันก็คือภาษา Java ครับ ซึ่งก็ขอใช้โปรแกรมที่ทำงานเหมือนกับ C++ นะครับ เริ่ม จากคลาส Foo และ Bar 

public class Foo {
int a;
public void setA(int val) {
a = val;
}
public int getA() {
return a;
}
void f1() {
System.out.println("I am f1");
}
/*void f3() {
System.out.println("I am f3");
}*/
}


public class Bar extends Foo {
public void f2() {
System.out.println("I am f2");
}
public void f3() {
System.out.println("I am f3");
}
}
 
ซึ่งในเวอร์ชันแรกนี้ให้สังเกตว่าเมท็อด f3() อยู่ในคลาส Bar นะครับ และก็สมมติเหมือนเดิมว่าสองคลาสนี้เราไม่ได้เขียนเองแต่มีคนคอมไพล์แล้วส่งไฟล์ .class มาให้เรา 

sarunintakosum@Saruns-MacBook-Pro java % javac Foo.java
sarunintakosum@Saruns-MacBook-Pro java % javac Bar.java
sarunintakosum@Saruns-MacBook-Pro java % ls *.class
Bar.class       Foo.class
sarunintakosum@Saruns-MacBook-Pro java %


และเราก็เขียนคลาส Dar ขึ้นมาดังนี้ครับ 

public class Dar extends Bar {
public void f4() {
f3();
System.out.println("I am f4");
}
}
  
ก็เหมือนใน เวอร์ชัน C++ นะครับ คือเป็นลูกของ Bar มีเมท็อด f4() ซึ่งเรียกใช้เมท็อด f3() ส่วน Main โปรแกรมก็เป็นดังนี้ครับ 
public class Main {
public static void main(String[] args) {
Dar obj = new Dar();
obj.f4();
}
}
 
จากนั้นเราก็คอมไพล์ Main และ Dar และรันโปรแกรมครับ 

sarunintakosum@Saruns-MacBook-Pro java % javac Dar.java
sarunintakosum@Saruns-MacBook-Pro java % javac Main.java
sarunintakosum@Saruns-MacBook-Pro java % java Main
I am f3
I am f4
sarunintakosum@Saruns-MacBook-Pro java % 

ต่อไปก็จะสมมติเหมือนเดิมนะครับว่ามีการย้ายเมท็อด f3() จาก Bar ไป Foo และมีการส่ง .class ตัวใหม่มาให้เรา

public class Foo {
int a;
public void setA(int val) {
a = val;
}
public int getA() {
return a;
}
void f1() {
System.out.println("I am f1");
}
void f3() {
System.out.println("I am f3");
}
}

public class Bar extends Foo {
public void f2() {
System.out.println("I am f2");
}
/*public void f3() {
System.out.println("I am f3");
}*/
}


sarunintakosum@Saruns-MacBook-Pro java % javac Foo.java 
sarunintakosum@Saruns-MacBook-Pro java % javac Bar.java 
sarunintakosum@Saruns-MacBook-Pro java % 

แต่เมื่อได้ .class ใหม่มาแล้ว เนื่องจากไม่มีการแก้ไขใด ๆ ใน Dar และ Main ดังนั้นผมก็จะรันโปรแกรม Main เลยนะครับ 

sarunintakosum@Saruns-MacBook-Pro java % java Main
I am f3
I am f4
sarunintakosum@Saruns-MacBook-Pro java %


ซึ่งก็จะเห็นว่าโปรแกรมทำงานได้ถูกต้อง ก็แสดงว่า Java ไม่มีปัญหานี้นะครับ 

เมื่ออ่านมาถึงตรงนี้ ก็หวังว่าจะได้เข้าใจ Syntactic Fragile Base Class Problem กันแล้วนะครับ โดยสรุปปัญหานี้เป็นปัญหาระดับตัวภาษา ซึ่งภาษาที่ยังมีปัญหานี้ก็เช่น C++ 

ในบล็อกต่อไป จะเขียนถึงปัญหา FBC ที่ไม่ได้ขึ้นกับภาษาโปรแกรม แต่เป็นปัญหาในด้านการออกแบบของแนวคิด object-oriented เอง นั่นคือปัญหา Semantic Fragile Base Class Problem ครับ

วันศุกร์ที่ 10 กรกฎาคม พ.ศ. 2563

ปัญหาเบสคลาสแตกง่าย (Fragile Base Class Problem)

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

วันนี้มาเริ่มด้วยเรื่องทางด้าน object-oriented development ที่ผมได้สอนให้นักศึกษาฟังในวิชา  Component-Based Software Development มาหลายปีแล้ว ซึ่งเรื่องนี้เกี่ยวข้องกับคุณสมบัติที่ทำให้ object-oriented development เป็นที่นิยมก็คือคุณสมบัติ  inheritance และ polymorphism นั่นเอง 

โดยคุณสมบัตินี้ถ้าใช้อย่างไม่ระวังมันก็จะเกิดปัญหากับการพัฒนาโปรแกรมได้ และปัญหาหนึ่งที่มากับแนวคิดนี้ก็คือ ปัญหาที่ทำให้คลาสมีความขึ้นต่อกัน ซึ่งก็เป็นที่มาของปัญหา เบสคลาสแตกง่าย  (Fragile Base Class Problem) ซึ่งผมจะขอเรียกสั้น ๆ ว่า FBC

ปัญหา FBC เป็นปัญหาที่เกิดจากคำถามว่า  ถ้ามีการปรับปรุงเบสคลาส ซึ่งจะขอเรียกว่าคลาสแม่ แล้วจำเป็นต้องปรับปรุงดีไรว์คลาส (derived class) ซึ่งจะขอเรียกว่าคลาสลูก ของมันด้วยหรือไม่ ยกตัวอย่างเช่น สมมติ คลาส A เป็นคลาสแม่ มีคลาส B เป็นคลาสลูก และมี คลาส C เป็นลูกของ B อีกทีหนึ่ง สมมติคลาส C เรียกใช้เมท็อดที่อยู่ในคลาส B คราวนี้ลองสมมติต่อว่าถ้าในเวอร์ชันใหม่ของคลาส A กับ B มีการย้ายเมท็อดที่ C เรีกใช้จากคลาส B ขึ้นไปอยู่ในคลาส A  แน่นอนว่าคลาส A กับ B ต้องคอมไพล์ใหม่ แต่เราควรต้องมาคอมไพล์คลาส C ด้วยหรือไม่ ซึ่งคำตอบของคำถามนี้คือไม่ควร แต่ในภาษาเขียนโปรแกรมบางภาษาเราจำเป็นต้องคอมไพล์คลาส C ด้วย 

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

คงเข้าใจปัญหานี้กันแล้วนะครับ โดยสรุปแล้วปัญหา FBC แบ่งเป็น 2 ประเภทคือ
  • Syntatic FBC หรือเรียกอีกอย่างว่า Fragile Binary Interface Problem
  • Sematic FBC  
โดย Syntactic ก็คือไวยากรณ์ ตัวอย่างของคลาส A B C เป็นตัวอย่างของกลุ่มนี้ ส่วน Semantic ก็คือความหมาย ปัญหาของการแก้ไขโค้ดในคลาสแม่แล้ว ทำให้พฤติกรรมของคลาสลูก เปลี่ยนไปเป็นปัญหาในกลุ่มนี้ 

ถึงตรงนี้คงเห็นแล้วนะครับว่าปัญหา FBC แสดงให้เห็นว่าการใช้งาน inheritance และ polymorphism มีปัญหาคือทำให้คลาสขึ้นแก่กัน และจะเป็นปัญหาในการดูแล และบำรุงรักษาซอฟต์แวร์ซึ่งมีขนาดใหญ่ และนี่เป็นเหตุผลหลักเหตุผลหนึ่งที่ทำให้การพัฒนาโปรแกรมแบบคอมโพเนนต์หลีกเลี่ยงที่จะใช้ inheritance กับ polymorphism และมาใช้การประกอบ (compose) เป็นหลัก

สำหรับบล็อกนี้ขอจบแค่นี้นะครับ บล็อกต่อ ๆ ไปจะมาแสดงตัวอย่างของ Syntactic และ Semantic เพื่อให้เห็นภาพชัดเจนขึ้นครับ