Quantcast
Channel: cc :: somkiat
Viewing all articles
Browse latest Browse all 1997

[Part 3] สรุป 50 เรื่องสำหรับผู้เริ่มต้นพัฒนาระบบด้วยภาษา Go

$
0
0

ใน Part  3 นี้จะเป็นเรื่องที่ลึกไปอีกขั้น แต่ยังเป็นสำหรับมือใหม่อยู่ !!
ประกอบไปด้วย

  • การใช้งาน log.Panic() และ log.Fatal()
  • การทำงานของ data structure ต่าง ๆ 
  • การใช้งาน for-range
  • การทำงานของ switch-case
  • เริ่มต้นกับ Goroutine และ channel

มาเริ่มกันเลย

อ่าน Part 1 และ Part 2 ก่อนได้

เรื่องที่ 21 ว่าด้วยเรื่องของ log.Fatal และ log.Panic

โดยที่ log package ในภาษา go นั้นจะแตกต่างจากภาษาอื่น ๆ
ซึ่งมีความสามารถมากกว่าเรื่องของ logging เท่านั้น
เนื่องจากถ้าเรียกใช้งาน function ต่าง ๆ จาก log package แล้ว เช่น log.Fatalln() 
ผลที่ตามมาคือ จะออกจาก program หรือ terminate ทันที
ดังนั้นนักพัฒนาต้องรู้และใช้งานให้เหมาะสมด้วย

[gist id="34a6e4cde27d29960b064c496749eb0a" file="21.go"]

เรื่องที่ 22  การทำงานต่าง ๆ ของ build-in data structure จะไม่ทำงานแบบ synchronous

เนื่องจาก feature จำนวนมากในภาษา go จะสนับสนุน concurency
ดังนั้นเรื่องความถูกต้องของข้อมูลจึงเป็นเรื่องยาก
แต่ถ้าเราต้องการให้ข้อมูลหรือการทำงานถูกต้องแล้ว
แนะนำให้ใช้งาน goroutine และ  channel
แต่ถ้าต้องการจัดารเองก็สามารถใช้งาน sync package

เรื่องที่ 23 ข้อควรระวังในการใช้งาน for-range กับข้อมูลชนิด string

การวน loop เพื่อดึงข้อมูลของ string ในแต่ละตัวนั้น
จะทำการ return กลับมา 2 ค่าเสมอ
ค่าที่ 1 คือ index หรือตำแหน่ง
ค่าที่ 2 คือ byte แรกของ character ใน index นั้น ๆ ไม่ใช้ค่าของ character นะ

ดังนั้นถ้า character เก็บค่าที่เป็น multiple rune แล้ว ผลที่ได้จะผิดทันที

ปล. สามารถใช้งาน package norm สำหรับการทำงานกับ character 

มาดูตัวอย่างการใช้งาน for-range กัน ซึ่งจะพบว่า byte แรกที่ไม่รู้จักหรือแปลงได้
จะทำการ return ค่า  0xfffd ออกมา

[gist id="34a6e4cde27d29960b064c496749eb0a" file="23.go"]

การแก้ไขคือ แปลงข้อมูล string มาเป็น byte slice ก่อนดังนี้

[gist id="34a6e4cde27d29960b064c496749eb0a" file="232.go"]

เรื่องที่ 24 การใช้งาน for-range กับข้อมูลชนิด Map ต้องเข้าใจ

สิ่งที่ต้องพึงระวังก็คือ ทุกครั้งที่ใช้ for-range สำหรับดึงข้อมูลจาก Map นั้น
ในแต่ละครั้งจะไม่ได้ข้อมูลที่เรียงลำดับเหมือนกัน นั่นคือจะ random นั่นเอง !!

[gist id="34a6e4cde27d29960b064c496749eb0a" file="24.go"]

ปล. แต่ถ้านำ code ชุดนี้ไป run ใน Go Playground แล้วจะได้ผลการทำงานเหมือนกัน เนื่องจากใน Go Playground จะไม่ทำการ recompile ถ้า code ไม่เปลี่ยน

เรื่องที่ 25 ทำความเข้ากับ case ใน switch

ในแต่ละ case ของ switch นั้นจะทำการ break โดย default อยู่แล้ว
ดังนั้นไม่ต้องกลัวปัญหาเรื่องการลืม break อีกต่อไป
แต่ถ้าเราไม่เข้าใจอาจจะทำให้ใช้งานผิดได้
ยกตัวอย่างเช่น

[gist id="34a6e4cde27d29960b064c496749eb0a" file="25.go"]

ซึ่งผลที่เราคาดหวังคือ true ทั้งคู่ แต่ผลที่ได้จริง ๆ ไม่ตรง
ซึ่งนี่คือความแตกต่างของภาษา go ที่ต้องเข้าใจ

แต่ถ้าต้องการตรวจสอบทั้ง 2 ค่าจริง ๆ ก็ให้ทำแบบนี้
โดยในแต่ละ case ใส่ได้มากกว่า 1 ค่า

[gist id="34a6e4cde27d29960b064c496749eb0a" file="252.go"]

เรื่องที่ 26 การเพิ่มและลดค่าของตัวเลข 

ในภาษาอื่น ๆ จะมี ++ และ -- ซึ่งเป็น unary operation
สำหรับการเพิ่มและลดค่าของตัวเลข ใส่ได้ทั้งด้านหน้าและหลัง เช่น ++x และ x++
แต่ในภาษา go นั้นใส่ได้แต่ด้านหลัง
ที่สำคัญไม่สามารถใช้ใน expression ได้อีก ต้องใช้แบบโดด ๆ ดังนี้

[gist id="34a6e4cde27d29960b064c496749eb0a" file="26.go"]

เรื่องที่ 27 Bitwise NOT operator จะใช้ ^ นะ ไม่ใช่ ~ (bitwise complement)

ในภาษา go นั้นจะใช้ XOR (^) มาแทน NOT (~) ยกตัวอย่างเช่น

[gist id="34a6e4cde27d29960b064c496749eb0a" file="27.go"]

เรื่องที่ 28 Unexported Structure Fields Are Not Encoded

สำหรับ field/property ใน struct ของภาษา go นั้น
ถ้าขึ้นต้นด้วยอักษรตัวเล็กแล้ว (unexport field)
จะไม่สามารถ encode เป็น XML/JSON/GOB หรืออย่างอื่นได้
ดังนั้นหลังจาก encode แล้วมาทำการ decode 
จะพบว่าข้อมูลของ field/property ที่ขึ้นต้นด้วยตัวเล็ก จะมีค่าเป็น zero value ดังนี้

[gist id="34a6e4cde27d29960b064c496749eb0a" file="28.go"]

เรื่องที่ 29 App Exits With Active Goroutines

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

[gist id="34a6e4cde27d29960b064c496749eb0a" file="29.go"]

ผลที่ออกมาคือ
[0] is running
[1] is running
all done!

ผลที่ออกมานั้น ไม่เป้นไปตามที่เราคาดหวัง
เนื่องจากในแต่ละงานที่เรียก function doit() ต้องแสดงคำว่า it done ด้วย

วิธีการแก้ไขปัญหาที่นิยมใช้คือ  WaitGroup จาก sync package
ช่วยทำให้ main grouting รอ worker goroutine ต่าง ๆ ไปจนกว่าจะทำงานเสร็จ
ถ้ามีบาง worker ที่ทำงานนานมาก ๆ ก็มีช่องทางในการสงสัญญาณไปหา 
เพื่อสั่ง kill หรือหยุดทำงานได้เช่นกัน
หรือจะเปิดทุก ๆ  channel ที่เปิดไปยัง worker ก็ได้เช่นกัน
สามารถเขียน code ใหม่ด้วยการใช้งาน WaitGroup ได้ดังนี้ 

[gist id="34a6e4cde27d29960b064c496749eb0a" file="292.go"]

ปัญหาที่เกิดขึ้นคือ deadlock !! แต่ว่าเกิดจากอะไร ?
จาก code จะพบว่า ในแต่ละ worker อ้างอิงไปยังตัวแปรชนิด WaitGroup ต่างกัน
หรือทำการ copy value ของ wg ต้นฉบับไปเท่านั้นเอง
ดังนั้นเรียกใช้งาน wg.Done() เมื่อทำงานเสร็จแล้ว จึงไม่ส่งผลต่อ main goroutine
ทำให้ main routine ไม่รู้ว่า worker นั้น ๆ ทำงานเสร็จหรือยัง
จึงทำให้ต่างฝ่ายต่างรอ จนเกิด deadlock ขึ้นมา !!

ดังนั้นสามารถแก้ไขปัญหาด้วยการสร้าง channel ไปยัง worker แต่ละตัว

[gist id="34a6e4cde27d29960b064c496749eb0a" file="293.go"]

เรื่องที่ 30  การส่งข้อมูลไปยัง unbuffer channel

ในการใช้งาน unbuffer channel นั้น
ผู้ส่งสามารถส่งข้อมูลไปยังผู้รับได้เรื่อย ๆไปจนกว่า ผู้รับจะเริ่มทำการ process ข้อมูล
นั่นหมายความว่าเมื่อผู้รับทำการ process ข้อมูลแล้ว
จะทำการ block ข้อมูลจากผู้ส่งทันที
ดังนั้นทำให้มีข้อมูลบางตัวที่ไปไม่ถึงผู้รับนั่นเอง
โดยตรงนี้ก็ขึ้นอยู่กับเครื่องที่ทำการ run ระบบงานด้วย !!!

[gist id="34a6e4cde27d29960b064c496749eb0a" file="30.go"]

ผลที่ออกมาในแต่ละครั้งจะไม่เหมือนกันเช่น

[code] $go run 30.go processed: cmd.1 $go run 30.go processed: cmd.1 processed: cmd.2 $go run 30.go processed: cmd.1 processed: cmd.2 processed: cmd.3 [/code]

Viewing all articles
Browse latest Browse all 1997

Trending Articles