GOLANGエラー処理最適案
14987 ワード
原文:https://gocn.io/article/348
GOLANGのエラーは簡単です.errorインタフェースでgolang error handlingを参照してください.
実際にCがエラーコードを返すことに慣れていれば、整形errorを定義することもできます.
これは何も難しいことはないようですね.実際、これはerrorの基本ユニットにすぎません.実際の製品では、例えばプレーヤーがこの情報を印刷します.
はい、このメッセージしかありません.そして?その後はなく、復号に失敗しただけで、何の手がかりもなく、プレーヤーをデバッグしなければ何が起こったのか分からない.私たちの例を見ると、
これらの情報は十分ではありません.これはエラーライブラリが流行している理由です.このライブラリはerrorsで、Wrapメソッドを提供しています.
つまり、複数のerrorが追加されています.このライブラリを使用すると、上記の例は次のように書きます.
このライブラリは、各errorに追加のメッセージ
多層関数呼び出しでは、各層に独自の情報を追加することもできます.たとえば、次のようになります.
これにより,
例えば、AACのライブラリでは、ASCオブジェクトが使用されており、解析時にデータが正当か否かを判断する必要があり、以下のように実現される(code参照).
エラーが発生した最初の場所にスタックを追加し、外部に追加の必要な情報を追加することで、使用中にエラーが発生した後、問題がどこにあるかを知ることができ、インスタンスプログラムを書くことができます.
詳細スタックの印刷:
エラーメッセージは次のとおりです. は、
この情報がクライアントであれば、バックグラウンドに送信すると、問題点が見つかりやすく、簡単な
スタックを加えるとパフォーマンスが低下しますか?エラーが発生する確率はまだ小さく、性能に損失はほとんどありません.複雑なerrorオブジェクトを使用すると、ライブラリでloggerを使用しないで、アプリケーション層でloggerを使用してファイルまたはネットワークに印刷することができます.
他の言語、例えばマルチスレッドプログラムについても、intエラーコードを同様の方法で返すことができますが、コンテキスト情報をスレッドの情報に保存し、スレッドをクリーンアップする際にもこの情報をクリーンアップします.コンシステントについても同様であり、例えばSTのthreadは現在のIDを取得し、グローバル変数を利用して情報を保存することもできる.goroutineのようなコヒーレントIDが得られない場合は、
C++の例は、マクロ定義によって定義されます.
使用する場合、GOLANGと似ています.
単純なcodeよりもずっとよく、エラーが発生する確率も高くなく、詳細な情報を取得したほうがいいです.
また、loggerとerrorは2つの異なる概念であり、例えばlibraryでは、エラー時にerrorsで複雑なエラーを返し、豊富な情報を含むが、loggerは同様に非常に重要であり、例えば特定の情報に対してaccess logはクライアントのアクセス情報を見ることができ、プロトコルは一般的に重要なプロセスポイントにログを加え、現在の運行状況を説明する.json形式のログやメッセージと呼ばれるものもあり、これらのログをデータシステム処理に送信することができます.
loggerの場合、
これにより、テキスト・ログ、またはメッセージ・システムで、どのセッションを区別することができます.もちろんerrorにはcontextの情報も含まれていて、エラーのエラーやスタックだけでなく、前の重要なログも見ることができます.
GOLANGのエラーは簡単です.errorインタフェースでgolang error handlingを参照してください.
if f,err := os.Open("test.txt"); err != nil {
return err
}
実際にCがエラーコードを返すことに慣れていれば、整形errorを定義することもできます.
type errorCode int
func (v errorCode) Error() string {
return fmt.Sprintf("error code is %v", v)
}
const loadFailed errorCode = 100
func load(filename string) error {
if f,err := os.Open(filename); err != nil {
return loadFailed
}
defer f.Close()
content : = readFromFile(f);
if len(content) == 0 {
return loadFailed
}
return nil
}
これは何も難しいことはないようですね.実際、これはerrorの基本ユニットにすぎません.実際の製品では、例えばプレーヤーがこの情報を印刷します.
Player: Decode failed.
はい、このメッセージしかありません.そして?その後はなく、復号に失敗しただけで、何の手がかりもなく、プレーヤーをデバッグしなければ何が起こったのか分からない.私たちの例を見ると、
load
が失敗した場合も同様で、1つの情報しか印刷されません.error code is 100
これらの情報は十分ではありません.これはエラーライブラリが流行している理由です.このライブラリはerrorsで、Wrapメソッドを提供しています.
_, err := ioutil.ReadAll(r)
if err != nil {
return errors.Wrap(err, "read failed")
}
つまり、複数のerrorが追加されています.このライブラリを使用すると、上記の例は次のように書きます.
func load(filename string) error {
if f,err := os.Open(filename); err != nil {
return errors.Wrap(err, "open failed")
}
defer f.Close()
content : = readFromFile(f);
if len(content) == 0 {
return errors.New("content empty")
}
return nil
}
このライブラリは、各errorに追加のメッセージ
errors.WithMessage(err,msg)
を追加したり、スタック情報errors.WithStack(err)
を追加したり、両方にerros.Wrap
を追加したり、スタック情報付きエラーerrors.New
とerrors.Errorf
を作成したりすることができる.これにより,多層関数呼び出し時に,その時の状況を示すのに十分な情報が得られる.多層関数呼び出しでは、各層に独自の情報を追加することもできます.たとえば、次のようになります.
func initialize() error {
if err := load("sys.db"); err != nil {
return errors.WithMessage(err, "init failed")
}
if f,err := os.Open("sys.log"); err != nil {
return errors.Wrap(err, "open log failed")
}
return nil
}
init
関数では、load
を呼び出すとこのerrはWrap
に過ぎているので、自分の情報を加えるだけです(Wrap
を使うと重複するスタックになりますが、問題はありません).2番目のエラーはWrapでメッセージを追加します.印刷ログは次のとおりです.empty content
main.load
/Users/winlin/git/test/src/demo/test/main.go:160
main.initialize
/Users/winlin/git/test/src/demo/test/main.go:167
main.main
/Users/winlin/git/test/src/demo/test/main.go:179
runtime.main
/usr/local/Cellar/go/1.8.1/libexec/src/runtime/proc.go:185
runtime.goexit
/usr/local/Cellar/go/1.8.1/libexec/src/runtime/asm_amd64.s:2197
load sys.db failed
これにより,
sys.db
をロード中にエラーが発生し,エラー内容はempty content
であり,スタックも存在することが分かる.エラーが発生した場合、問題を解決しやすくなります.例えば、AACのライブラリでは、ASCオブジェクトが使用されており、解析時にデータが正当か否かを判断する必要があり、以下のように実現される(code参照).
func (v *adts) Decode(data []byte) (raw, left []byte, err error) {
p := data
if len(p) <= 7 {
return nil, nil, errors.Errorf("requires 7+ but only %v bytes", len(p))
}
// Decode the ADTS.
if err = v.asc.validate(); err != nil {
return nil, nil, errors.WithMessage(err, "adts decode")
}
return
}
func (v *AudioSpecificConfig) validate() (err error) {
if v.Channels < ChannelMono || v.Channels > Channel7_1 {
return errors.Errorf("invalid channels %#x", uint8(v.Channels))
}
return
}
エラーが発生した最初の場所にスタックを追加し、外部に追加の必要な情報を追加することで、使用中にエラーが発生した後、問題がどこにあるかを知ることができ、インスタンスプログラムを書くことができます.
func run() {
adts,_ := aac.NewADTS()
if _,_,err := adts.Decode(nil); err != nil {
fmt.Println(fmt.Sprintf("Decode failed, err is %+v", err))
}
}
func main() {
run()
}
詳細スタックの印刷:
Decode failed, err is invalid object 0x0
github.com/ossrs/go-oryx-lib/aac.(*AudioSpecificConfig).validate
/Users/winlin/go/src/github.com/ossrs/go-oryx-lib/aac/aac.go:462
github.com/ossrs/go-oryx-lib/aac.(*adts).Decode
/Users/winlin/go/src/github.com/ossrs/go-oryx-lib/aac/aac.go:439
main.run
/Users/winlin/git/test/src/test/main.go:13
main.main
/Users/winlin/git/test/src/test/main.go:19
runtime.main
/usr/local/Cellar/go/1.8.1/libexec/src/runtime/proc.go:185
runtime.goexit
/usr/local/Cellar/go/1.8.1/libexec/src/runtime/asm_amd64.s:2197
adts decode
エラーメッセージは次のとおりです.
adts decode
は、ADTSにより印刷される.invalid object 0x00
、ASCで印刷されます.main/run/aac.Decode/asc.Decode
を含む完全なスタックです.この情報がクライアントであれば、バックグラウンドに送信すると、問題点が見つかりやすく、簡単な
Decode failed
よりも役に立ちすぎて、本質的な違いがあります.サーバ側であれば、コンテキストの接続に関する情報を加えて、このエラーがどの接続によるものなのかを区別する必要があり、問題を見つけやすい.スタックを加えるとパフォーマンスが低下しますか?エラーが発生する確率はまだ小さく、性能に損失はほとんどありません.複雑なerrorオブジェクトを使用すると、ライブラリでloggerを使用しないで、アプリケーション層でloggerを使用してファイルまたはネットワークに印刷することができます.
他の言語、例えばマルチスレッドプログラムについても、intエラーコードを同様の方法で返すことができますが、コンテキスト情報をスレッドの情報に保存し、スレッドをクリーンアップする際にもこの情報をクリーンアップします.コンシステントについても同様であり、例えばSTのthreadは現在のIDを取得し、グローバル変数を利用して情報を保存することもできる.goroutineのようなコヒーレントIDが得られない場合は、
context.Context
を使用することができます.実際に最も簡単なのは、Context
が1.7以降に標準ライブラリに組み込まれるため、errorにコンテキストを追加することです.C++の例は、マクロ定義によって定義されます.
struct ComplexError {
int code;
ComplexError* wrapped;
string msg;
string func;
string file;
int line;
};
#define errors_new(code, fmt, ...) \
_errors_new(__FUNCTION__, __FILE__, __LINE__, code, fmt, ##__VA_ARGS__)
extern ComplexError* _errors_new(const char* func, const char* file, int line, int code, const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
char buffer[1024];
size_t size = vsnprintf(buffer, sizeof(buffer), fmt, ap);
va_end(ap);
ComplexError* err = new ComplexError();
err->code = code;
err->func = func;
err->file = file;
err->line = line;
err->msg.assign(buffer, size);
return err;
}
#define errors_wrap(err, fmt, ...) \
_errors_wrap(__FUNCTION__, __FILE__, __LINE__, err, fmt, ##__VA_ARGS__)
extern ComplexError* _errors_wrap(const char* func, const char* file, int line, ComplexError* v, const char* fmt, ...) {
ComplexError* wrapped = (ComplexError*)v;
va_list ap;
va_start(ap, fmt);
char buffer[1024];
size_t size = vsnprintf(buffer, sizeof(buffer), fmt, ap);
va_end(ap);
ComplexError* err = new ComplexError();
err->wrapped = wrapped;
err->code = wrapped->code;
err->func = func;
err->file = file;
err->line = line;
err->msg.assign(buffer, size);
return err;
}
使用する場合、GOLANGと似ています.
ComplexError* loads(string filename) {
if (filename.empty()) {
return errors_new(100, "invalid file");
}
return NULL;
}
ComplexError* initialize() {
string filename = "sys.db";
ComplexError* err = loads(filename);
if (err) {
return errors_wrap("load system from %s failed", filename.c_str());
}
return NULL;
}
int main(int argc, char** argv) {
ComplexError* err = initialize();
// Print err stack.
return err;
}
単純なcodeよりもずっとよく、エラーが発生する確率も高くなく、詳細な情報を取得したほうがいいです.
また、loggerとerrorは2つの異なる概念であり、例えばlibraryでは、エラー時にerrorsで複雑なエラーを返し、豊富な情報を含むが、loggerは同様に非常に重要であり、例えば特定の情報に対してaccess logはクライアントのアクセス情報を見ることができ、プロトコルは一般的に重要なプロセスポイントにログを加え、現在の運行状況を説明する.json形式のログやメッセージと呼ばれるものもあり、これらのログをデータシステム処理に送信することができます.
loggerの場合、
context.Context
をサポートすることは特に重要であり、実際にはcontext
はhttp requestの要求のようなセッションの処理プロセス、またはRTMPの接続の処理である.典型的なloggerの定義は次のとおりです.// C++ style
logger(int level, void* ctx, const char* fmt, ...)
// GOLANG style
logger(level:int, ctx:context.Context, format string, args ...interface{})
これにより、テキスト・ログ、またはメッセージ・システムで、どのセッションを区別することができます.もちろんerrorにはcontextの情報も含まれていて、エラーのエラーやスタックだけでなく、前の重要なログも見ることができます.