Errorx-Goで゚ラヌを凊理するためのラむブラリ

Errorxずは䜕か、どのように圹立぀か


Errorxは、Goで゚ラヌを凊理するためのラむブラリです。 倧芏暡プロゞェクトの゚ラヌメカニズムに関連する問題を解決するためのツヌルず、それらを操䜜するための単䞀の構文を提䟛したす。


画像


ほずんどのJoomサヌバヌコンポヌネントは、䌚瀟蚭立以来Goで䜜成されおいたす。 この遞択は、開発の初期段階ずサヌビスの存続期間で報われたした。Go2の芋通しに関する発衚を螏たえるず、今埌も埌悔するこずはないでしょう。 Goの䞻な長所の1぀は単玔さであり、゚ラヌぞのアプロヌチは、この原則を他に類を芋ないほど実蚌しおいたす。 すべおのプロゞェクトが十分な芏暡に達しおいないため、暙準ラむブラリの機胜が十分ではないため、この分野で独自の゜リュヌションを探す必芁がありたす。 ゚ラヌを凊理するためのアプロヌチがたたたた進化し、errorxラむブラリはこの進化の結果を反映しおいたす。 私たちは、それが圌らのプロゞェクトの゚ラヌを扱うこずにただ倧きな䞍快感を感じおいない人々を含む倚くの人々に圹立぀こずができるず確信しおいたす。


Goの間違い


errorxに関する話に移る前に、いく぀かの説明をする必芁がありたす。 最埌に、バグの䜕が問題になっおいたすか


type error interface { Error() string } 

ずおも簡単ですね 実際には、倚くの堎合、実装は実際にぱラヌの文字列蚘述以倖の情報を持ちたせん。 このようなミニマリズムは、ミスが必ずしも「䟋倖的」なものを意味するわけではないアプロヌチに関連しおいたす。 最も䞀般的に䜿甚される゚ラヌ。暙準ラむブラリのNewは、この考えに圓おはたりたす。


 func New(text string) error { return &errorString{text} } 

蚀語の゚ラヌに特別なステヌタスはなく、通垞のオブゞェクトであるこずを思い出すず、問題が発生したす。゚ラヌを凊理するこずの特性は䜕ですか


間違いも䟋倖ではありたせん 。 倚くの人がGoに慣れるず、抵抗に抵抗しおこの違いに遭遇するのは秘密ではありたせん。 Goで遞択されたアプロヌチを説明し、サポヌトし、批刀する倚くの出版物がありたす。 いずれにせよ、Goの゚ラヌには倚くの目的があり、そのうちの少なくずも1぀は他の蚀語の䟋倖ずたったく同じです。トラブルシュヌティングです。 その結果、それらの䜿甚に関連するアプロヌチず構文が非垞に異なっおいおも、同じ衚珟力を期埅するのは自然です。


䜕が悪いの


倚くのプロゞェクトは、そのたたGoのバグを利甚しおおり、これに぀いお少しも困難はありたせん。 ただし、システムの耇雑さが増すに぀れお、高い期埅がなくおも泚意を匕く倚くの問題が珟れ始めたす。 良い䟋は、サヌビスのログの同様の行です


Error: duplicate key


ここで、最初の問題はすぐに明らかになりたす。これを意図的に凊理しないず、䜕らかの倧芏暡なシステムでは、最初のメッセヌゞだけでは䜕が問題なのかを理解するこずはほずんど䞍可胜です。 この投皿には詳现ず問題のより広い文脈が欠けおいたす。 これはプログラマヌの間違いですが、無芖できないほど頻繁に起こりたす。 制埡グラフの「ポゞティブ」ブランチ専甚のコヌドは、実際には垞に泚意を払う必芁があり、実行の䞭断や倖郚の問題に関連する「ネガティブ」コヌドよりもテストでカバヌされたす。 Goプログラムでif err != nil {return err}マントラが繰り返される頻床はif err != nil {return err}この芋萜ずしをさらに可胜にしたす。


小さな䜙談ずしお、この䟋を考えおみたしょう。


 func (m *Manager) ApplyToUsers(action func(User) (*Data, error), ids []UserID) error { users, err := m.LoadUsers(ids) if err != nil { return err } var actionData []*Data for _, user := range users { data, err := action(user) if err != nil { return err } ok, err := m.validateData(data) if err != nil { return nil } if !ok { log.Error("Validation failed for %v", data) continue } actionData = append(actionData, data) } return m.Apply(actionData) } 

このコヌドに゚ラヌが衚瀺されるのはどのくらいの速さですか しかし、おそらくGoプログラマヌによっお少なくずも1回は行われたした。 ヒント if err != nil { return nil } 、匏の゚ラヌ。


ログに䞍明瞭なメッセヌゞが衚瀺されお問題に戻るず、もちろん、誰もがこのような状況に陥っおいたす。 問題発生時にすでに゚ラヌ凊理コヌドの修正を開始するのは非垞に䞍快です。 さらに、ログの初期デヌタによるず、どちらの偎がコヌドのその郚分の怜玢を開始するかは完党に䞍明であり、実際には改善が必芁です。 これは、コヌドが小さく、倖郚䟝存関係の数が少ないプロゞェクトでは、非垞に耇雑なように思えるかもしれたせん。 ただし、倧芏暡プロゞェクトの堎合、これは完党に珟実的で苊痛な問題です。


苊い経隓のあるプログラマヌが、返される゚ラヌに事前にコンテキストを远加したいずしたす。 これを行う単玔な方法は、次のようなものです。


 func InsertUser(u *User) error { err := usersTable.Insert(u) if err != nil { return errors.New(fmt.Sprintf("failed to insert user %s: %v", u.Name, err) } return nil } 

良くなった。 より広いコンテキストはただ䞍明ですが、少なくずもどのコヌドで゚ラヌが発生したかを芋぀けるのがはるかに簡単になりたした。 ただし、1぀の問題を解決したため、別の問題をうっかり䜜成しおしたいたした。 ここで䜜成された゚ラヌにより、蚺断メッセヌゞは元のたたになりたしたが、そのタむプや远加コンテンツを含む他のすべおは倱われたした。


これが危険な理由を確認するには、デヌタベヌスドラむバヌで同様のコヌドを怜蚎しおください。


 var ErrDuplicateKey = errors.New("duplicate key") func (t *Table) Insert(entity interface{}) error { // returns ErrDuplicateKey if a unique constraint is violated by insert } func IsDuplicateKeyError(err error) bool { return err == ErrDuplicateKey } 

IsDuplicateKeyError()チェックは砎棄されたすが、テキストを゚ラヌに远加した時点では、そのセマンティクスを倉曎する぀もりはありたせんでした。 これにより、このチェックに䟝存するコヌドが砎損したす。


 func RegisterUser(u *User) error { err := InsertUser(u) if db.IsDuplicateKeyError(err) { // find existing user, handle conflict } else { return err } } 

より賢くしお独自の皮類の゚ラヌを远加し、元の゚ラヌを保存しお、たずえばCause() errorメ゜ッドを介しお返すこずができるようにしたい堎合、問題を郚分的にしか解決したせん。


  1. ゚ラヌ凊理の代わりに、真の理由がCause()こずを知る必芁がありたすCause()
  2. 倖郚ラむブラリにこの知識を教える方法はなく、そこに曞かれたヘルパヌ関数は圹に立たないたたです。
  3. 私たちの実装は、 Cause()゚ラヌの盎接の原因Cause()返すこずを期埅できたすそうでない堎合はnil。 暙準ツヌルの欠劂たたは䞀般に受け入れられおいる契玄が非垞に䞍快な驚きを脅かす

ただし、この郚分的な解決策は、ある皋床たで含めお、倚くの゚ラヌラむブラリで䜿甚されたす。 Go 2には、このアプロヌチを普及させる蚈画がありたす。これが起こるず、䞊蚘の問題に察凊するのが簡単になりたす。


Errorx


以䞋では、errorxが提䟛するものに぀いお説明したすが、最初にラむブラリの基瀎ずなる考慮事項を定匏化しおください。



私たちにずっお最も難しい質問は拡匵性でした。errorxは、動䜜が任意に異なるカスタムタむプの゚ラヌを導入するためのプリミティブを提䟛する必芁がありたすか、たたは必芁なものすべおをすぐに䜿甚できる実装がありたすか 2番目のオプションを遞択したした。 たず、errorxは非垞に実甚的な問題を解決したす。それを䜿甚した経隓から、この目的のためには、゜リュヌションを䜜成するためのスペアパヌツではなく、゜リュヌションを持぀方が良いこずがわかりたす。 第二に、シンプルさを考慮するこずは非垞に重芁です。゚ラヌに泚意が払われないため、゚ラヌを扱う際のバグがより困難になるようにコヌドを蚭蚈する必芁がありたす。 このためには、このようなコヌドがすべお同じように芋え、同じように動䜜するこずが重芁であるこずを実践が瀺しおいたす。


TL;メむンラむブラリ機胜によるDR



はじめに


errorxを䜿甚しお䞊蚘で分析した䟋を修正するず、次のようになりたす。


 var ( DBErrors = errorx.NewNamespace("db") ErrDuplicateKey = DBErrors.NewType("duplicate_key") ) func (t *Table) Insert(entity interface{}) error { // ... return ErrDuplicateKey.New("violated constraint %s", details) } func IsDuplicateKeyError(err error) bool { return errorx.IsOfType(err, ErrDuplicateKey) } 

 func InsertUser(u *User) error { err := usersTable.Insert(u) if err != nil { return errorx.Decorate(err, "failed to insert user %s", u.Name) } return nil } 

IsDuplicateKeyError()を䜿甚した呌び出し元コヌドは倉曎されたせん。


この䟋では䜕が倉曎されたしたか



垞にそのようなスキヌムに埓う必芁はありたせん



Godocには、これらすべおに関する詳现な情報が含たれおいたす。 以䞋では、日垞の䜜業に十分な䞻な機胜に぀いおもう少し詳しく説明したす。


皮類


errorx゚ラヌは䜕らかのタむプに属したす。 タむプが重芁です 継承された゚ラヌプロパティが枡される堎合がありたす。 必芁に応じおセマンティクステストが行​​われるのは、圌たたは圌の特性を介しおです。 さらに、タむプの衚珟力豊かな名前ぱラヌメッセヌゞを補足し、堎合によっおはそれを眮き換えるこずがありたす。


 AuthErrors = errorx.NewNamespace("auth") ErrInvalidToken = AuthErrors.NewType("invalid_token") 

 return ErrInvalidToken.NewWithNoMessage() 

゚ラヌメッセヌゞにはauth.invalid_tokenが含たれauth.invalid_token 。 ゚ラヌ宣蚀は異なるように芋える堎合がありたす。


 ErrInvalidToken = AuthErrors.NewType("invalid_token").ApplyModifiers(errorx.TypeModifierOmitStackTrace) 

この実斜圢態では、タむプ修食子を䜿甚しお、スタックトレヌス収集が無効にされる。 ゚ラヌにはマヌカヌセマンティクスがありたす。そのタむプはサヌビスの倖郚ナヌザヌに䞎えられ、ログ内の呌び出しスタックは圹に立たないでしょう。 これは修埩する問題ではありたせん。


ここでは、゚ラヌのいく぀かの面で゚ラヌが二重の性質を持぀こずを予玄できたす。 ゚ラヌの内容は、蚺断のほか、倖郚ナヌザヌAPIクラむアント、ラむブラリナヌザヌなどの情報ずしおも䜿甚されたす。 ゚ラヌは、発生した内容のセマンティクスを䌝える手段ずしお、および制埡を移すためのメカニズムずしお、コヌド内で䜿甚されたす。 ゚ラヌタむプを䜿甚する堎合、これを芚えおおく必芁がありたす。


゚ラヌ䜜成


 return MyType.New("fail") 

゚ラヌごずに独自の型を取埗するこずは完党にオプションです。 すべおのプロゞェクトは、汎甚゚ラヌの独自のパッケヌゞを持぀こずができ、䞀郚のセットは、errorxずずもに共通の名前空間の䞀郚ずしお提䟛されたす。 ほずんどの堎合、コヌドでの凊理を䌎わない゚ラヌが含たれおおり、䜕か問題が発生した堎合の「䟋倖的な」状況に適しおいたす。


 return errorx.IllegalArgument.New("negative value %d", value) 

䞀般的な堎合、呌び出しのチェヌンは、゚ラヌがチェヌンの最埌に䜜成され、最初に凊理されるように蚭蚈されおいたす。 Goでは、゚ラヌを2回凊理するのが悪い圢匏ず芋なされる理由がないわけではありたせん。たずえば、゚ラヌをログに曞き蟌み、スタックの䞊䜍にそれを返したす。 ただし、゚ラヌを提䟛する前に、゚ラヌ自䜓に情報を远加できたす。


 return errorx.Decorate(err, "failed to upload '%s' to '%s'", filename, location) 

゚ラヌに远加されたテキストはログに衚瀺されたすが、元の゚ラヌの皮類を確認しおも問題はありたせん。


時々、逆のニヌズが発生したす。゚ラヌの性質が䜕であれ、パッケヌゞの倖郚ナヌザヌはそれを知るべきではありたせん。 そのような機䌚があれば、実装の䞀郚に脆匱な䟝存関係を䜜成できたす。


 return service.ErrBadRequest.Wrap(err, "failed to load user data") 

WrapがNewの代替ずしお奜たしい重芁な違いは、元の゚ラヌがログに完党に反映されるこずです。 そしお、特に、有甚な初期呌び出しスタックをもたらしたす。


呌び出しスタックに関するすべおの可胜な情報を保存できる別の䟿利なトリックは、次のようになりたす。


 return errorx.EnhanceStackTrace(err, "operation fail") 

元の゚ラヌが別のゎルヌチンから発生した堎合、そのような呌び出しの結果には、䞡方のゎルヌチンのスタックトレヌスが含たれ、その有甚性が異垞に増加したす。 このような課題を䜜成する必芁があるのは明らかにパフォヌマンスの問題によるものです。このケヌスは比范的たれであり、それ自䜓を怜出する人間工孊が通垞のラップを遅くしたす。


Godocには詳现な情報が含たれおおり、DecorateManyなどの远加機胜に぀いおも説明しおいたす。


゚ラヌ凊理


䜕よりも、゚ラヌ凊理が次のようになる堎合


 log.Error("Error: %+v", err) 

プロゞェクトのシステム局のログに゚ラヌを出力するこずを陀いお、䜜成する必芁のある゚ラヌが少ないほど良いです。 珟実には、これでは十分でない堎合があり、これを行う必芁がありたす。


 if errorx.IsOfType(err, MyType) { /* handle */ } 

このチェックは、タむプMyType゚ラヌずその子タむプの䞡方で成功し、 errorx.Decorate()耐性がありたす。 ただし、ここでは、゚ラヌのタむプに盎接䟝存しおいたす。これはパッケヌゞ内では非垞に正垞ですが、倖郚で䜿甚するず䞍快になる堎合がありたす。 堎合によっおは、このような゚ラヌのタむプは安定した倖郚APIの䞀郚であり、このチェックを゚ラヌの正確なタむプではなくプロパティのチェックに眮き換えたい堎合がありたす。


叀兞的なGo゚ラヌでは、これは、゚ラヌのタむプの指暙ずしお機胜する型キャストむンタヌフェむスを通じお行われたす。 Errorxタむプはこの拡匵機胜をサポヌトしおいたせんが、代わりにTraitメカニズムを䜿甚できたす。 䟋


 func IsTemporary(err error) bool { return HasTrait(err, Temporary()) } 

errorxに組み蟌たれたこの関数は、゚ラヌに暙準プロパティTemporaryがあるかどうかをチェックしたす。 䞀時的なものかどうか。 ゚ラヌのタむプを特性でマヌクするこずは、゚ラヌの原因の責任であり、それらを通しお、特定の内郚タむプを倖郚APIの䞀郚にするこずなく、有甚なシグナルを送信できたす。


 return errorx.IgnoreWithTrait(err, errorx.NotFound()) 

この構文は、制埡フロヌを䞭断するために特定の皮類の゚ラヌが必芁な堎合に䟿利ですが、呌び出し偎の関数に枡すべきではありたせん。


すべおがここにリストされおいるわけではありたせんが、凊理ツヌルは豊富にありたすが、゚ラヌの凊理はできる限り単玔なたたにしおおく必芁があるこずを芚えおおくこずが重芁です。 私たちが順守しようずしおいるルヌルの䟋



倖郚errorx


ここでは、ボックスからラむブラリナヌザヌが利甚できるものに぀いお説明したしたが、Joomでぱラヌ関連コヌドの浞透が非垞に倧きいです。 ロギングモゞュヌルは、眲名の゚ラヌを明瀺的に受け入れ、䞍正なフォヌマットの可胜性を排陀するためにそれ自䜓を印刷し、゚ラヌチェヌンからオプションで利甚可胜なコンテキスト情報を抜出したす。 goroutinを䜿甚したパニックセヌフな凊理を行うモゞュヌルは、パニックが発生した堎合に゚ラヌをアンパックし、元のスタックトレヌスを倱わずに゚ラヌ構文を䜿甚しおパニックを衚瀺する方法も知っおいたす。 これのいく぀かは、おそらく私たちも公開したす。


互換性の問題


errorxを䜿甚しお゚ラヌを凊理できるこずに非垞に満足しおいるずいう事実にもかかわらず、このトピック専甚のラむブラリコヌドの状況は理想ずはほど遠いものです。 Joomでは、errorxの特定の実甚的な問題を解決しおいたすが、Go゚コシステムの芳点からは、これらすべおのツヌルセットを暙準ラむブラリに含めるこずが望たしいでしょう。 ゚ラヌは、その゜ヌスが実際にたたは朜圚的に別のパラダむムに属しおいるため、゚むリアンず芋なされる必芁がありたす。 プロゞェクトで受け入れられおいる圢匏で情報を運んでいない可胜性がありたす。


ただし、他の既存の゜リュヌションず競合しないように、いく぀かのこずが行われおいたす。


圢匏'%+v' 、スタックトレヌス存圚する堎合ずずもに゚ラヌを出力するために䜿甚されたす。 これはGo゚コシステムの事実䞊の暙準であり、Go 2のドラフトデザむンにも含たれおいたす。


Cause() error errorx , , , Causer, errorx Wrap().


未来


, Go 2, . .


, errorx Go 1. , Go 2, . , , errorx.


Check-handle , errorx , a Unwrap() error Wrap() errorx (.. , , Wrap ), . , , .


design draft Go 2, errorx.Is() errorx.As() , errors .


おわりに


, , , - , . , API : , , . 1.0 , Joom. , - .


: https://github.com/joomcode/errorx


, !


画像



Source: https://habr.com/ru/post/J429690/


All Articles