DEV Community

Pallat Anchaleechamaikorn
Pallat Anchaleechamaikorn

Posted on

คุยกันเรื่อง Writing Better Go: Lessons from 10 Code Reviews

ใน reddit ของ r/golang มีการนำเอา Writing Better Go: Lessons from 10 Code Reviews ของ Konrad Reiche มาสรุปโดย Asleep-Actuary-4428 ที่นี่ ซึ่งสรุปได้เข้าใจง่าย เลยอยากเอามาเล่าต่อ
สามารถเปิดดู slide ต้นทางไปพร้อมๆกับการอธิบายแต่ละข้อได้เลย

  1. Handle Errors
    • อย่าทำเป็นเมิน error ไปแบบเงียบๆ (เช่นเอา _ ไปรับค่า error)
    • อย่าคิดว่า error นี้รับได้เช่น if err != nil { return nil }
    • ถ้าเจอ error ต้องตรวจสอบก่อนอย่างอื่นและจัดการมันเลย เช่น log ตอนนั้นเลย แล้ว error นั้นก็ถือซะว่าถูกจัดการไปแล้ว ก็ไม่ต้องส่งต่อให้คนอื่น
    • อย่าโวยซ้ำ ถ้า log ไปรอบนึงแล้วยัง return ต่อให้คนอื่นไปโวยต่อ เขาก็เอาไป log อีก
bad good
if err != nil {
    slog.Error(err)
    return err
}
if err != nil {
    slog.Error(err)
    return nil
}
  • ลดภาระให้ฝั่งที่เรียกใช้
    • return result, nil แบบนี้ดีเพราะคืนผลลัพธ์และไม่มี error ให้สับสน
    • return nil, err แบบนี้ก็ดีเพราะมี error ให้จัดการในขณะที่ไม่มีผลลัพธ์
    • return nil, nil แบบนี้แย่ ถึงแม่ว่าจะไม่มี error แต่การไม่มีผลลัพธ์ส่งออกไป มันทำให้คนเรียกต้องไปตีความต่อ
    • _return result, err _อันนี้ก็แย่ ไม่รู้ว่ามันจะต้อง error หรือว่าใช้ได้กันแน่ มันต้องไปตีความต่ออีกว่าจะเชื่อค่าไหน
  1. ใส่ interface เร็วไป

    • การใช้ interface แบบผิดๆเป็นภาพที่เห็นบ่อย เพราะมักจะเห็นการสร้าง interface ก่อนเลย (ซึ่งเป็นท่าที่อาจจะเอามาจากบางภาษาเช่น จาวา) หรือทำเพื่อให้ test เช่นทำเพื่อให้ mock ใน test เนื่องจากพอมันมาเร็วไป มันจะทำให้โค้ดอ่านยากขึ้นทันที
    • อย่าเริ่มด้วย interface
    • ให้ใช่ท่า accept interfaces, return concrete types หรือก็คือ สื่อสารกันผ่าน interface แต่ตอนคืนให้คืนด้วย type จริง
    • ทุกอย่างให้เริ่มจากการใช้ type จริงไปก่อน จนกว่าโค้ดมันจะพาเราไปถึงจุดที่จะต้องใช้ interface จริงๆแล้วค่อยใช้
    • Litmus Test: ถ้าเราไม่ใช้ interface แล้ว test ได้ ก็ไม่เห็นต้องใช้เลยนี่นา
    • อย่าสร้าง interface เพียงเพื่อให้เทสได้เพียงอย่างเดียว อยากให้ลองเทสโดยไม่ต้องมีดูก่อน
  2. Mutexes Before Channels

    • เมื่อเริ่มใช้ channel โค้ดจะเริ่มซับซ้อนมาก ซึ่งมันสร้างโอกาสเสี่ยงที่จะเกิด panic ง่ายขึ้น เช่นเมื่อไปเผลอ close channel หรือส่งของเข้า channel ที่ closed ไปแล้ว หรืออาจเกิด deadlocks
    • เริ่มด้วยวิธีง่ายๆ ไปทีละขั้น
    • เริ่มจาก sync ก่อน อย่าเพิ่งรีบไป async
    • ใส่ goroutine เมื่อ profiling แสดงให้เห็นคอขวดจริงๆก่อน
    • ใช้ sync.Mutex และ sync.WaitGroup จัดการกับ shared state ดูก่อน
    • Channel จะฉายแสงเมื่อต้องเล่นกับการจัดการที่มีความซับซ้อนจริงๆ ไม่ใช่เอามาใช้กับเรื่องเบบี๋ธรรมดา ไม่คุ้ม
  3. Declare ทุกอย่างให้ใกล้กับจุดที่จะใช้ให้มากที่สุด

    • นี่เป็นท่ามาตรฐานสำหรับการประกาศ constants, variables, functions and types
    • ประกาศของเหล่านี้ไว้ในไฟล์ที่มีการใช้งานมันจริงๆ และ Export (ชื่อขึ้นด้วย CApital Letter) เมื่อมีความต้องการใช้งานจากนอก package จริงๆก่อน
    • ในฟังก์ชั่นก็เช่นกัน ประกาศตัวแปรให้ใกล้จุดที่จะถูกใช้ให้มากที่สุด
    • จำกันขอบเขตมันให้เล็กที่สุดเช่นทำ 1 statement ใน if
  4. อย่าให้เกิด runtime panics

    • ท่าง่ายสุดก็คือให้ตรวจสอบ input ที่เข้ามาจากภายนอกก่อนเอาไปใช้
    • อย่าใช้ if x == nil ก็ในเมื่อคุณเป็นคนควบคุมการไหลของข้อมูลมาแล้ว ก็ให้เชื่อในการจัดการ error ในแต่ละจุดไปเลย
    • ถ้าจะ dreference ค่า pointer เช่น *p = 10 จะต้อง check nil ก่อนเสมอ
    • และการใช้ pointer ให้ปลอดภัยที่สุดก็คือ อย่าใช้มัน หมายถึงพยายามออกแบบให้ไม่ต้องใช้ pointer ให้ได้ก่อน
  5. ใช้เว้นวรรคให้น้อย

    • ก็คืออย่าห่อลอจิกหลายๆชั้นเช่น if ซ้อน if ซ้อน for
    • อยากชวนให้ใช้ท่า Return Early, Flatter Structure ด้วยการจัดการ error ก่อน แล้วค่อยไปทำอย่างอื่น หรือกำจัดเงื่อนไขที่จะไม่ทำต่อก่อนเลย
  6. หลีกเลี่ยงการตั้งชื่อไฟล์และแพ็กเก็จด้วยชื่อคลุมเครือ

    • เช่น util.go, misc.go, constants.go หรือ interfaces/interfaces.go
    • อยากให้สื่อความว่ามันมีไว้ทำไม มากกว่าจะบอกว่ามันอยู่ลำดับชั้นไหน
    • โค้ดจะอ่านง่ายเมื่อมันอยู่ใกล้ๆกันกับอะไรที่มันเกี่ยวข้องกัน ไม่ใช่แยกกันไปคนละทิศ
    • ตั้งชื่อ package ตามโดเมน หรืองานของมัน
    • จัดกลุ่มตามความหมาย ไม่ใช่ตาม type เช่น ไปจัดกลุ่มของ controller แบบนี้ไม่เอา
  7. เรียงลำดับ declarations ตามลำดับความสำคัญ

    • ใน Go มันช่วยให้อ่านง่ายขึ้น
    • โค้ดไหนสำคัญ เอาขึ้นด้านบนเลย
    • เช่นพวกที่ต้อง exported หรือ ฟังก์ชั่นที่เป็น API-facing เอาขึ้นข้างบนก่อน
    • ตามด้วยพวก helper function พวกนี้มันจะช่วยอธิบายของข้างบน
    • เรียงตามความสำคัญ ไม่ใช่ตามความสัมพันธ์ เพื่อให้คนที่มาอ่าน ลำดับความสำคัญจากมากลงมา
  8. ตั้งชื่อให้ดี

  9. หลีกเลี่ยงการเอา type มาเติมท้ายชื่อ (เช่น userMap, idStr, injectFn) ชื่อตัวแปรควรบอกว่ามันเก็บอะไร ไม่ใช่บอกว่ามัน type อะไร

  10. ความยาวของชื่อควรสอดคล้องกับ scope เช่นถ้าตั้งชื่อด้วยอักษรเดียว scope มันควรจะสั้น ไม่ใช่ประกาศ global ด้วยชื่อตัวเดียว อายเค้านะ

  11. ทำ Document เพื่อตอบคำถามว่ามีไว้ทำไม ไม่ใช่แค่ว่ามันทำอะไรได้

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

Top comments (0)