ใน reddit ของ r/golang มีการนำเอา Writing Better Go: Lessons from 10 Code Reviews ของ Konrad Reiche มาสรุปโดย Asleep-Actuary-4428 ที่นี่ ซึ่งสรุปได้เข้าใจง่าย เลยอยากเอามาเล่าต่อ
สามารถเปิดดู slide ต้นทางไปพร้อมๆกับการอธิบายแต่ละข้อได้เลย
- Handle Errors
- อย่าทำเป็นเมิน error ไปแบบเงียบๆ (เช่นเอา _ ไปรับค่า error)
- อย่าคิดว่า error นี้รับได้เช่น if err != nil { return nil }
- ถ้าเจอ error ต้องตรวจสอบก่อนอย่างอื่นและจัดการมันเลย เช่น log ตอนนั้นเลย แล้ว error นั้นก็ถือซะว่าถูกจัดการไปแล้ว ก็ไม่ต้องส่งต่อให้คนอื่น
- อย่าโวยซ้ำ ถ้า log ไปรอบนึงแล้วยัง return ต่อให้คนอื่นไปโวยต่อ เขาก็เอาไป log อีก
| bad | good |
|---|---|
if err != nil { |
if err != nil { |
- ลดภาระให้ฝั่งที่เรียกใช้
- return result, nil แบบนี้ดีเพราะคืนผลลัพธ์และไม่มี error ให้สับสน
- return nil, err แบบนี้ก็ดีเพราะมี error ให้จัดการในขณะที่ไม่มีผลลัพธ์
- return nil, nil แบบนี้แย่ ถึงแม่ว่าจะไม่มี error แต่การไม่มีผลลัพธ์ส่งออกไป มันทำให้คนเรียกต้องไปตีความต่อ
- _return result, err _อันนี้ก็แย่ ไม่รู้ว่ามันจะต้อง error หรือว่าใช้ได้กันแน่ มันต้องไปตีความต่ออีกว่าจะเชื่อค่าไหน
-
ใส่ interface เร็วไป
- การใช้ interface แบบผิดๆเป็นภาพที่เห็นบ่อย เพราะมักจะเห็นการสร้าง interface ก่อนเลย (ซึ่งเป็นท่าที่อาจจะเอามาจากบางภาษาเช่น จาวา) หรือทำเพื่อให้ test เช่นทำเพื่อให้ mock ใน test เนื่องจากพอมันมาเร็วไป มันจะทำให้โค้ดอ่านยากขึ้นทันที
- อย่าเริ่มด้วย interface
- ให้ใช่ท่า accept interfaces, return concrete types หรือก็คือ สื่อสารกันผ่าน interface แต่ตอนคืนให้คืนด้วย type จริง
- ทุกอย่างให้เริ่มจากการใช้ type จริงไปก่อน จนกว่าโค้ดมันจะพาเราไปถึงจุดที่จะต้องใช้ interface จริงๆแล้วค่อยใช้
- Litmus Test: ถ้าเราไม่ใช้ interface แล้ว test ได้ ก็ไม่เห็นต้องใช้เลยนี่นา
- อย่าสร้าง interface เพียงเพื่อให้เทสได้เพียงอย่างเดียว อยากให้ลองเทสโดยไม่ต้องมีดูก่อน
-
Mutexes Before Channels
- เมื่อเริ่มใช้ channel โค้ดจะเริ่มซับซ้อนมาก ซึ่งมันสร้างโอกาสเสี่ยงที่จะเกิด panic ง่ายขึ้น เช่นเมื่อไปเผลอ close channel หรือส่งของเข้า channel ที่ closed ไปแล้ว หรืออาจเกิด deadlocks
- เริ่มด้วยวิธีง่ายๆ ไปทีละขั้น
- เริ่มจาก sync ก่อน อย่าเพิ่งรีบไป async
- ใส่ goroutine เมื่อ profiling แสดงให้เห็นคอขวดจริงๆก่อน
- ใช้
sync.Mutexและsync.WaitGroupจัดการกับ shared state ดูก่อน - Channel จะฉายแสงเมื่อต้องเล่นกับการจัดการที่มีความซับซ้อนจริงๆ ไม่ใช่เอามาใช้กับเรื่องเบบี๋ธรรมดา ไม่คุ้ม
-
Declare ทุกอย่างให้ใกล้กับจุดที่จะใช้ให้มากที่สุด
- นี่เป็นท่ามาตรฐานสำหรับการประกาศ constants, variables, functions and types
- ประกาศของเหล่านี้ไว้ในไฟล์ที่มีการใช้งานมันจริงๆ และ Export (ชื่อขึ้นด้วย CApital Letter) เมื่อมีความต้องการใช้งานจากนอก package จริงๆก่อน
- ในฟังก์ชั่นก็เช่นกัน ประกาศตัวแปรให้ใกล้จุดที่จะถูกใช้ให้มากที่สุด
- จำกันขอบเขตมันให้เล็กที่สุดเช่นทำ 1 statement ใน if
-
อย่าให้เกิด runtime panics
- ท่าง่ายสุดก็คือให้ตรวจสอบ input ที่เข้ามาจากภายนอกก่อนเอาไปใช้
- อย่าใช้
if x == nilก็ในเมื่อคุณเป็นคนควบคุมการไหลของข้อมูลมาแล้ว ก็ให้เชื่อในการจัดการ error ในแต่ละจุดไปเลย - ถ้าจะ dreference ค่า pointer เช่น
*p = 10จะต้อง check nil ก่อนเสมอ - และการใช้ pointer ให้ปลอดภัยที่สุดก็คือ อย่าใช้มัน หมายถึงพยายามออกแบบให้ไม่ต้องใช้ pointer ให้ได้ก่อน
-
ใช้เว้นวรรคให้น้อย
- ก็คืออย่าห่อลอจิกหลายๆชั้นเช่น if ซ้อน if ซ้อน for
- อยากชวนให้ใช้ท่า Return Early, Flatter Structure ด้วยการจัดการ error ก่อน แล้วค่อยไปทำอย่างอื่น หรือกำจัดเงื่อนไขที่จะไม่ทำต่อก่อนเลย
-
หลีกเลี่ยงการตั้งชื่อไฟล์และแพ็กเก็จด้วยชื่อคลุมเครือ
- เช่น util.go, misc.go, constants.go หรือ interfaces/interfaces.go
- อยากให้สื่อความว่ามันมีไว้ทำไม มากกว่าจะบอกว่ามันอยู่ลำดับชั้นไหน
- โค้ดจะอ่านง่ายเมื่อมันอยู่ใกล้ๆกันกับอะไรที่มันเกี่ยวข้องกัน ไม่ใช่แยกกันไปคนละทิศ
- ตั้งชื่อ package ตามโดเมน หรืองานของมัน
- จัดกลุ่มตามความหมาย ไม่ใช่ตาม type เช่น ไปจัดกลุ่มของ controller แบบนี้ไม่เอา
-
เรียงลำดับ declarations ตามลำดับความสำคัญ
- ใน Go มันช่วยให้อ่านง่ายขึ้น
- โค้ดไหนสำคัญ เอาขึ้นด้านบนเลย
- เช่นพวกที่ต้อง exported หรือ ฟังก์ชั่นที่เป็น API-facing เอาขึ้นข้างบนก่อน
- ตามด้วยพวก helper function พวกนี้มันจะช่วยอธิบายของข้างบน
- เรียงตามความสำคัญ ไม่ใช่ตามความสัมพันธ์ เพื่อให้คนที่มาอ่าน ลำดับความสำคัญจากมากลงมา
ตั้งชื่อให้ดี
หลีกเลี่ยงการเอา type มาเติมท้ายชื่อ (เช่น userMap, idStr, injectFn) ชื่อตัวแปรควรบอกว่ามันเก็บอะไร ไม่ใช่บอกว่ามัน type อะไร
ความยาวของชื่อควรสอดคล้องกับ scope เช่นถ้าตั้งชื่อด้วยอักษรเดียว scope มันควรจะสั้น ไม่ใช่ประกาศ global ด้วยชื่อตัวเดียว อายเค้านะ
-
ทำ Document เพื่อตอบคำถามว่ามีไว้ทำไม ไม่ใช่แค่ว่ามันทำอะไรได้
- ให้เหตุผลว่ามันมีไว้ทำไม ทำไมต้องใช้มัน
- เวลาเขียน comment ควรสื่อถึงจุดประสงค์หรือประโยชน์ ไม่งั้นก็เหมือนเขียนโค้ดซ้ำธรรมดา
- Document คือเจตนา ไม่ใช่วิธีการ
- คนที่มาอ่านภายหลัง ควรที่จะเข้าใจแรงจูงใจที่คุณทำสิ่งนี้ออกมา เพราะเขาอ่านโค้ดก็รู้ว่ามันทำอะไร แต่ไม่รู้ว่าทำไมต้องทำแบบนี้
Top comments (0)