ไปที่เทคนิคการจัดการข้อผิดพลาด [ปิด]


108

ฉันเพิ่งเริ่มต้นกับ Go รหัสของฉันเริ่มมีสิ่งนี้มากมาย:

   if err != nil {
      //handle err
   }

หรือนี่

  if err := rows.Scan(&some_column); err != nil {
      //handle err
  }

มีสำนวน / กลยุทธ์ / แนวทางปฏิบัติที่ดีสำหรับการตรวจสอบและจัดการข้อผิดพลาดใน Go หรือไม่?

แก้ไขเพื่อชี้แจง: ฉันไม่ได้ท้องหรือแนะนำให้ทีม Go หาสิ่งที่ดีกว่า ฉันกำลังถามว่าฉันทำถูกต้องหรือฉันพลาดเทคนิคบางอย่างที่ชุมชนคิดขึ้นมา ขอบคุณทุกคน


4
ไม่มีไม่ได้จริงๆ นั่นเป็นหัวข้อที่มีการพูดคุยกันบ่อยครั้งและเป็นหัวข้อที่สมเหตุสมผล มีข้อเสนอวิวัฒนาการมากมายเช่นกัน คำตอบของทีมงานดูเหมือนว่าไม่น่าจะเป็นปัญหาในการเขียนโค้ดที่ดี
Denys Séguret

ที่เกี่ยวข้อง: stackoverflow.com/questions/15397419/…

โปรดทราบว่าคำถามที่เกี่ยวข้องนี้ไม่เหมือนกับคำถามนี้จริงๆ คำตอบเฉพาะเจาะจงเกินไป
Denys Séguret

นอกจากนี้ยังมีเหตุผลสำหรับความรำคาญนี้: มันทำให้การเขียนโปรแกรมอย่างรวดเร็วยากขึ้น แต่ก็ทำให้ยากต่อการสร้างจุดบกพร่องโดยเพียงแค่สร้างข้อผิดพลาดอีกครั้ง
Denys Séguret

คุณสามารถค้นหา Andrew Gerrand และ Brad Fitzpatrick เขียนจุดเริ่มต้นของไคลเอนต์ HTTP / 2 ใน Go ในรูปแบบที่คล้ายกันไม่มากก็น้อย youtube.com/watch?v=yG-UaBJXZ80
Supreet Sethi

คำตอบ:


61

รหัสของคุณเป็นสำนวนและในความคิดของฉันมันเป็นแนวทางปฏิบัติที่ดีที่สุด บางคนก็ไม่เห็นด้วยแน่นอน แต่ฉันจะยืนยันว่านี่คือรูปแบบที่เห็นทั่วทุกมุมห้องสมุดมาตรฐานในการ golang กล่าวอีกนัยหนึ่งผู้เขียน Go เขียนข้อผิดพลาดในการจัดการด้วยวิธีนี้


12
"ผู้เขียนไปเขียนการจัดการข้อผิดพลาดด้วยวิธีนี้" ฟังดูดีสำหรับฉัน.
gmoore

"บางคนไม่เห็นด้วยอย่างแน่นอน" : ฉันไม่แน่ใจว่าจะมีคนบอกว่านี่ไม่ใช่แนวทางปฏิบัติที่ดีที่สุดในปัจจุบัน บางคนถามน้ำตาลไวยากรณ์หรือการเปลี่ยนแปลงอื่น ๆ แต่วันนี้ฉันไม่คิดว่า coder ร้ายแรงใด ๆ จะตรวจสอบข้อผิดพลาดเป็นอย่างอื่น
Denys Séguret

@dystroy: โอเคบางคนบอกว่า " มัน sux " บางคนเรียกว่า"ข้อผิดพลาดได้รับการจัดการในค่าตอบแทนสไตล์ 70" และอื่น ๆ ;-)
zzzz

2
@jnml การจัดการข้อผิดพลาดด้วยวิธีนี้เป็นเรื่องของการออกแบบภาษาซึ่งเป็นหัวข้อที่มีการถกเถียงกันมาก โชคดีที่มีหลายสิบภาษาให้เลือก
fuz

4
สิ่งที่ฆ่าฉันคือการใช้รูปแบบเดียวกันสำหรับการเรียกใช้ฟังก์ชันทุกครั้ง สิ่งนี้ทำให้โค้ดค่อนข้างมีเสียงดังในบางสถานที่และเป็นเพียงการกรีดร้องเพื่อหาน้ำตาลวากยสัมพันธ์เพื่อทำให้โค้ดง่ายขึ้นโดยไม่สูญเสียข้อมูลใด ๆ ซึ่งโดยพื้นฐานแล้วคำจำกัดความของความกระชับ (ซึ่งฉันเชื่อว่าเป็นคุณลักษณะที่ดีกว่าการใช้คำฟุ่มเฟือย แต่ก็เป็นเนื้อหาที่ถกเถียงกัน จุด). หลักการคือเสียง แต่ไวยากรณ์ทำให้ IMHO ต้องการมาก อย่างไรก็ตามการบ่นเป็นสิ่งต้องห้ามดังนั้นฉันจะดื่ม kool-aid ตอนนี้ ;-)
โทมัส

30

หกเดือนหลังจากคำถามนี้ถูกถามร็อบหอกเขียนโพสต์บล็อกชื่อข้อผิดพลาดที่มีค่า

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

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

...

ใช้ภาษาเพื่อลดความซับซ้อนในการจัดการข้อผิดพลาดของคุณ

แต่จำไว้ว่า: ไม่ว่าคุณจะทำอะไรให้ตรวจสอบข้อผิดพลาดของคุณเสมอ!

เป็นเรื่องที่น่าอ่าน


ขอบคุณ! จะตรวจสอบว่า
gmoore

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

@ Waterlink คำพูดของคุณไม่มีความหมาย ทุกสิ่งที่มีสถานะเกือบจะเป็นสิ่งที่น่ารังเกียจหากคุณเหล่มันเล็กน้อย ฉันคิดว่าการเปรียบเทียบกับen.wikipedia.org/wiki/Null_Object_patternนั้นมีประโยชน์มากกว่า
7610

@ user7610 ขอบคุณสำหรับคำติชม ฉันทำได้แค่เห็นด้วย
Waterlink

3
ไพค์: "แต่จำไว้ว่า: ไม่ว่าคุณจะทำอะไรให้ตรวจสอบข้อผิดพลาดของคุณเสมอ!" - นี่คือยุค 80 ข้อผิดพลาดสามารถเกิดขึ้นได้ทุกที่หยุดสร้างภาระให้กับโปรแกรมเมอร์และนำข้อยกเว้นมาใช้เพื่อประโยชน์ของ Pete
Slawomir

22

ฉันเห็นด้วยกับคำตอบของ jnml ว่าเป็นรหัสสำนวนและเพิ่มสิ่งต่อไปนี้:

ตัวอย่างแรกของคุณ:

if err != nil {
      //handle err
}

เป็นสำนวนมากกว่าเมื่อจัดการกับค่าส่งคืนมากกว่าหนึ่งค่า ตัวอย่างเช่น:

val, err := someFunc()
if err != nil {
      //handle err
}
//do stuff with val

ตัวอย่างที่สองของคุณคือชวเลขที่ดีเมื่อจัดการกับerrมูลค่าเท่านั้น สิ่งนี้จะใช้ในกรณีที่ฟังก์ชันส่งคืนค่า an errorหรือถ้าคุณจงใจละเว้นค่าที่ส่งคืนอื่นที่ไม่ใช่error. ตัวอย่างเช่นบางครั้งจะใช้กับฟังก์ชันReaderand Writerที่ส่งคืนintจำนวนไบต์ที่เขียน (ข้อมูลที่ไม่จำเป็นในบางครั้ง) และerror:

if _, err := f.Read(file); err != nil {
      //handle err
}
//do stuff with f

รูปแบบที่สองจะเรียกว่าใช้งบถ้าเริ่มต้น

สำหรับแนวทางปฏิบัติที่ดีที่สุดเท่าที่ฉันทราบ (ยกเว้นการใช้แพ็คเกจ"ข้อผิดพลาด"เพื่อสร้างข้อผิดพลาดใหม่เมื่อคุณต้องการ) คุณได้ครอบคลุมทุกสิ่งที่คุณจำเป็นต้องรู้เกี่ยวกับข้อผิดพลาดใน Go!

แก้ไข: หากคุณพบว่าคุณจริงๆไม่สามารถอยู่โดยไม่มีข้อยกเว้นคุณสามารถเลียนแบบพวกเขาด้วยdefer,panicrecoverและ


4

ฉันสร้างไลบรารีสำหรับการจัดการข้อผิดพลาดที่มีประสิทธิภาพและส่งผ่านคิวของฟังก์ชัน Go

คุณสามารถค้นหาได้ที่นี่: https://github.com/go-on/queue

มีรูปแบบวากยสัมพันธ์ที่กะทัดรัดและละเอียดอ่อน นี่คือตัวอย่างของไวยากรณ์สั้น ๆ :

import "github.com/go-on/queue/q"

func SaveUser(w http.ResponseWriter, rq *http.Request) {
    u := &User{}
    err := q.Q(                      
        ioutil.ReadAll, rq.Body,  // read json (returns json and error)
    )(
        // q.V pipes the json from the previous function call
        json.Unmarshal, q.V, u,   // unmarshal json from above  (returns error)
    )(
        u.Validate,               // validate the user (returns error)
    )(
        u.Save,                   // save the user (returns error)
    )(
        ok, w,                    // send the "ok" message (returns no error)
    ).Run()

    if err != nil {
       switch err {
         case *json.SyntaxError:
           ...
       }
    }
}

โปรดทราบว่ามีค่าใช้จ่ายด้านประสิทธิภาพเล็กน้อยเนื่องจากใช้การสะท้อนแสง

นอกจากนี้นี่ไม่ใช่รหัสไปที่เป็นสำนวนดังนั้นคุณจะต้องใช้ในโครงการของคุณเองหรือถ้าทีมของคุณตกลงที่จะใช้มัน


3
เพียงเพราะคุณสามารถทำได้ไม่ได้หมายความว่าเป็นความคิดที่ดี สิ่งนี้ดูเหมือนรูปแบบChain of Responsibilityยกเว้นบางทีอ่านยากกว่า (ความเห็น) ขอแนะนำว่าไม่ใช่ "สำนวนโก๊ะ" น่าสนใจแม้ว่า
Steven Soroka

2

"กลยุทธ์" ในการจัดการข้อผิดพลาดใน golang และในภาษาอื่น ๆ คือการนำเสนอข้อผิดพลาดอย่างต่อเนื่องใน call stack จนกว่าคุณจะมีระดับสูงพอใน call stack ที่จะจัดการข้อผิดพลาดนั้น หากคุณพยายามจัดการข้อผิดพลาดนั้นเร็วเกินไปคุณอาจจะต้องใช้รหัสซ้ำ หากคุณจัดการช้าเกินไปคุณจะทำลายบางอย่างในรหัสของคุณ Golang ทำให้กระบวนการนี้ง่ายมากเนื่องจากทำให้ชัดเจนมากว่าคุณกำลังจัดการข้อผิดพลาดในตำแหน่งที่กำหนดหรือเผยแพร่ข้อมูล

หากคุณกำลังจะเพิกเฉยต่อข้อผิดพลาด _ แบบธรรมดาจะเปิดเผยข้อเท็จจริงนี้อย่างชัดเจน หากคุณกำลังจัดการข้อผิดพลาดที่คุณจัดการอยู่นั้นชัดเจนในกรณีที่คุณจะตรวจสอบในคำสั่ง if

เช่นเดียวกับที่คนกล่าวไว้ข้างต้นข้อผิดพลาดเป็นเพียงค่าปกติ สิ่งนี้ถือว่าเป็นเช่นนั้น


2

Go Gods ได้เผยแพร่ "แบบร่าง" สำหรับการจัดการข้อผิดพลาดใน Go 2 โดยมีจุดมุ่งหมายเพื่อเปลี่ยนสำนวนข้อผิดพลาด:

ภาพรวมและการออกแบบ

พวกเขาต้องการความคิดเห็นจากผู้ใช้!

วิกิความคิดเห็น

สั้น ๆ ดูเหมือนว่า:

func f() error {
   handle err { fmt.Println(err); return err }
   check mayFail()
   check canFail()
}

อัปเดต: การออกแบบร่างได้รับคำวิจารณ์มากมายดังนั้นฉันจึงร่างข้อกำหนดที่ต้องพิจารณาสำหรับการจัดการข้อผิดพลาด Go 2พร้อมเมนูความเป็นไปได้สำหรับวิธีแก้ปัญหาในที่สุด


1

มากที่สุดในอุตสาหกรรมที่ติดตามกฎระเบียบมาตรฐานที่กล่าวถึงในเอกสาร golang จัดการข้อผิดพลาดและไป และยังช่วยสร้างเอกสารสำหรับโครงการ


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

ขอบคุณสำหรับความคิดเห็นที่มีค่า
pschilakanti

0

ด้านล่างนี้คือการลดการจัดการข้อผิดพลาดสำหรับ Go ตัวอย่างสำหรับเมื่อรับพารามิเตอร์ HTTP URL:

(รูปแบบการออกแบบมาจากhttps://blog.golang.org/errors-are-values )

type HTTPAdapter struct {
    Error *common.AppError
}

func (adapter *HTTPAdapter) ReadUUID(r *http.Request, param string, possibleError int) uuid.UUID {
    requestUUID := uuid.Parse(mux.Vars(r)[param])
    if requestUUID == nil { 
        adapter.Error = common.NewAppError(fmt.Errorf("parameter %v is not valid", param),
            possibleError, http.StatusBadRequest)
    }
    return requestUUID
}

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

    adapter := &httphelper.HTTPAdapter{}
    viewingID := adapter.ReadUUID(r, "viewingID", common.ErrorWhenReadingViewingID)
    messageID := adapter.ReadUUID(r, "messageID", common.ErrorWhenReadingMessadeID)
    if adapter.Error != nil {
        return nil, adapter.Error
    }

นี่ไม่ใช่กระสุนเงินข้อเสียคือหากคุณมีข้อผิดพลาดหลายครั้งคุณจะได้รับข้อผิดพลาดสุดท้ายเท่านั้น

แต่ในกรณีนี้มันค่อนข้างซ้ำซากและมีความเสี่ยงต่ำดังนั้นฉันจึงสามารถรับข้อผิดพลาดสุดท้ายที่เป็นไปได้


-1

คุณสามารถล้างรหัสการจัดการข้อผิดพลาดสำหรับข้อผิดพลาดที่คล้ายกันได้ (เนื่องจากข้อผิดพลาดเป็นค่าที่คุณต้องระวังที่นี่) และเขียนฟังก์ชันที่คุณเรียกโดยมีข้อผิดพลาดที่ส่งผ่านเพื่อจัดการข้อผิดพลาด คุณจะไม่ต้องเขียน "if err! = nil {}" ทุกครั้ง อีกครั้งสิ่งนี้จะส่งผลให้เกิดการล้างโค้ดเท่านั้น แต่ฉันไม่คิดว่ามันเป็นวิธีการทำสิ่งต่างๆ

อีกครั้งเพียงเพราะคุณสามารถไม่ได้หมายความว่าคุณควร


-1

goerrอนุญาตให้จัดการข้อผิดพลาดด้วยฟังก์ชัน

package main

import "github.com/goerr/goerr"
import "fmt"

func ok(err error) {
    if err != nil {
        goerr.Return(err)
        // returns the error from do_somethingN() to main()
        // sequence() is terminated
    }
}

func sequence() error {
    ok(do_something1())
    ok(do_something2())
    ok(do_something3())

    return nil /// 1,2,3 succeeded
}
func do_something1() error { return nil }
func do_something2() error { return fmt.Errorf("2") }
func do_something3() error {
    fmt.Println("DOING 3")
    return nil
}

func main() {
    err_do_something := goerr.OR1(sequence)

    // handle errors

    fmt.Println(err_do_something)
}

อิก. การจัดการข้อผิดพลาดที่ซับซ้อน / ซ่อนตรรกะเช่นนี้ไม่ใช่ความคิดที่ดี IMO รหัสผลลัพธ์ (ซึ่งต้องการการประมวลผลล่วงหน้าของแหล่งที่มาโดย goerr) นั้นอ่าน / เหตุผลได้ยากกว่ารหัส Go แบบสำนวน
Dave C

-4

หากคุณต้องการควบคุมข้อผิดพลาดอย่างแม่นยำนี่อาจไม่ใช่วิธีแก้ปัญหา แต่สำหรับฉันโดยส่วนใหญ่แล้วข้อผิดพลาดใด ๆ ก็คือตัวขัดขวางการแสดง

ดังนั้นฉันจึงใช้ฟังก์ชันแทน

func Err(err error) {
    if err!=nil {
        fmt.Println("Oops", err)
        os.Exit(1)
    }
}

fi, err := os.Open("mmm.txt")
Err(err)

ข้อความดังกล่าวควรไปstderrมากกว่าstdoutดังนั้นเพียงใช้หรือlog.Fatal(err) log.Fatalln("some message:", err)เนื่องจากแทบจะไม่มีอะไรอื่นนอกจากmainควรตัดสินใจยุติโปรแกรมทั้งหมด (เช่นส่งคืนข้อผิดพลาดจากฟังก์ชัน / วิธีการอย่ายกเลิก) ในกรณีที่หายากนี่คือสิ่งที่คุณต้องการทำมันสะอาดกว่าและดีกว่าที่จะทำอย่างชัดเจน (เช่นif err := someFunc(); err != nil { log.Fatal(err) }) แทนที่จะใช้ฟังก์ชัน "ตัวช่วย" ที่ไม่ชัดเจนเกี่ยวกับสิ่งที่กำลังทำอยู่ (ชื่อ "Err" ไม่ดี แต่ก็ไม่มีข้อบ่งชี้ว่าอาจยุติโปรแกรมได้)
Dave C

เรียนรู้สิ่งใหม่! ขอบคุณ @DaveC
Gon
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.