په Go کې د غلطیو اداره کول ساده او انعطاف منونکي دي - مګر هیڅ جوړښت نلري!
دا باید ساده وي، سمه ده؟ یوازې یوه error
بیرته راشئ ، د پیغام سره پوښل شوی ، او پرمخ لاړشئ. ښه، دا سادگي په چټکۍ سره په ګډوډۍ بدلیږي ځکه چې زموږ کوډبیس د ډیرو کڅوړو، ډیرو پراختیا کونکو، او نور "چټک اصلاحات" سره وده کوي چې د تل لپاره پاتې کیږي. د وخت په تیریدو سره، لاګونه د "دا کار کولو کې پاتې راتلل" او "غیر متوقع هغه" څخه ډک شوي، او هیڅوک نه پوهیږي چې ایا دا د کارونکي غلطی، د سرور غلطی، بګی کوډ، یا دا د ستورو غلطه بڼه ده!
غلطۍ د متناسب پیغامونو سره رامینځته کیږي. هر بسته د خپل سټایلونو سیټ لري، ثابت، یا د دودیز غلطی ډولونه. د خطا کوډونه په خپله خوښه اضافه شوي. کومه اسانه لار نشته چې ووایاست چې کومې خطاګانې له کوم فعالیت څخه بیرته راستنیدل کیدی شي پرته لدې چې د هغې پلي کولو کې کیندنې!
نو ، ما د نوي خطا چوکاټ رامینځته کولو ننګونه واخیسته. موږ پریکړه وکړه چې د نوم ځای کوډونو په کارولو سره د جوړښت شوي، مرکزي سیسټم سره لاړ شو ترڅو غلطۍ معنی لرونکي، د موندلو وړ، او - تر ټولو مهم - موږ ته د ذهن سکون راکړئ!
دا د هغه کیسه ده چې موږ څنګه د یوې ساده غلطۍ اداره کولو طریقې سره پیل وکړ، په بشپړه توګه خپه شو لکه څنګه چې ستونزې زیاتې شوې، او بالاخره زموږ د غلطۍ چوکاټ جوړ کړ. د ډیزاین پریکړې، دا څنګه پلي کیږي، درسونه زده کړل، او ولې یې د غلطیو اداره کولو لپاره زموږ چلند بدل کړ. زه امید لرم چې دا به ستاسو لپاره یو څه نظرونه هم راوړي!
Go د غلطیو اداره کولو لپاره مستقیمه لاره لري: غلطی یوازې ارزښتونه دي. یوه تېروتنه یوازې یو ارزښت دی چې د error
انٹرفیس د واحد میتود Error() string
سره پلي کوي. د دې پرځای چې استثنا وغورځوي او د اوسني اجرا کولو جریان ګډوډ کړي ، د Go افعال د نورو پایلو تر څنګ د error
ارزښت بیرته راولي. بیا زنګ وهونکی کولی شي پریکړه وکړي چې دا څنګه اداره کړي: د پریکړې کولو لپاره د هغې ارزښت چیک کړئ ، د نوي پیغامونو او شرایطو سره وتړئ ، یا په ساده ډول غلطي بیرته راوباسئ ، د والدینو زنګ وهونکو لپاره د مدیریت منطق پریږدي.
موږ کولی شو په دې کې د Error() string
میتود اضافه کولو سره هر ډول error
وکړو. دا انعطاف هر بسته ته اجازه ورکوي چې خپله د غلطۍ اداره کولو ستراتیژي تعریف کړي ، او هر هغه څه غوره کړي چې د دوی لپاره غوره کار کوي. دا د Go د کمپوزیت فلسفې سره هم ښه مدغم کوي ، د اړتیا په صورت کې د غلطیو لپاه کول ، غزول یا دودیز کول اسانه کوي.
عام عمل دا دی چې د غلطۍ ارزښت بیرته راوباسئ چې د error
انٹرفیس پلي کوي او زنګ وهونکي ته اجازه ورکوي پریکړه وکړي چې نور څه وکړي. دلته یو عادي مثال دی:
func loadCredentials() (Credentials, error) { data, err := os.ReadFile("cred.json") if errors.Is(err, os.ErrNotExist) { return nil, fmt.Errorf("file not found: %w", err) } if err != nil { return nil, fmt.Errorf("failed to read file: %w", err) } cred, err := verifyCredentials(cred); if err != nil { return nil, fmt.Errorf("invalid credentials: %w", err) } return cred, nil }
Go د غلطیو سره کار کولو لپاره یو څو اسانتیاوې چمتو کوي:
errors.New()
او fmt.Errorf()
د ساده تېروتنو د رامنځته کولو لپاره.fmt.Errorf()
او %w
فعل په کارولو سره د اضافي شرایطو سره تېروتنې وتړئ.errors.Join()
ډیری تېروتنې په یو واحد کې یوځای کوي.errors.Is()
د یو ځانګړي ارزښت سره د یوې تېروتنې سره سمون لري، errors.As()
د یو ځانګړي ډول سره د یوې تېروتنې سره سمون لري، او errors.Unwrap()
اصلي تېروتنه بیا ترلاسه کوي.
په عمل کې، موږ معمولا دا نمونې ګورو:
errors.New()
یا fmt.Errorf()
سره ساده غلطی بیرته راګرځول.په لومړیو ورځو کې، د ډیری Go پراختیا کونکو په څیر، موږ د Go عمومي کړنې تعقیب کړې او د تېروتنې اداره کول یې لږ تر لږه فعال ساتل. دا د څو کلونو لپاره کافي ښه کار وکړ.
د pkg/errors په کارولو سره د سټیکټریس شامل کړئ، په هغه وخت کې یو مشهور بسته.
د بسته بندي ځانګړي غلطیو لپاره ثابت یا تغیرات صادر کړئ.
د ځانګړو تېروتنو د کتلو لپاره errors.Is()
وکاروئ.
د نوي پیغامونو او شرایطو سره تېروتنې وتړئ.
د API غلطیو لپاره، موږ د پروټوبف اینوم سره د خطا ډولونه او کوډونه تعریفوو.
pkg/errors
سره د سټیکټریس په شمول
موږ pkg/errors ، په هغه وخت کې د تېروتنې د سمبالولو یوه مشهوره بسته وکاروه، ترڅو زموږ په تېروتنو کې سټریکټریس شامل کړو. دا په ځانګړي ډول د ډیبګ کولو لپاره ګټور و ، ځکه چې دا موږ ته اجازه راکوله چې د غوښتنلیک په بیلابیلو برخو کې د غلطیو اصلیت تعقیب کړو.
د Stacktrace سره د غلطیو رامینځته کولو، لپیٹ کولو او تبلیغ کولو لپاره، موږ Newf()
، NewValuef()
، او Wrapf()
په څیر افعال پلي کړل. دلته زموږ د لومړني پلي کولو یوه بیلګه ده:
type xError struct { msg message, stack: callers(), } func Newf(msg string, args ...any) error { return &xError{ msg: fmt.Sprintf(msg, args...), stack: callers(), // 👈 stacktrace } } func NewValuef(msg string, args ...any) error { return fmt.Errorf(msg, args...) // 👈 no stacktrace } func Wrapf(err error, msg string, args ...any) error { if err == nil { return nil } stack := getStack(err) if stack == nil { stack = callers() } return &xError{ msg: fmt.Sprintf(msg, args...), stack: stack, } }
د تېروتنې تغیرات صادرول
زموږ په کوډبیس کې هر کڅوړه خپل غلطی متغیرونه تعریف کړي، ډیری وختونه د متضاد سټایلونو سره.
package database var ErrNotFound = errors.NewValue("record not found") var ErrMultipleFound = errors.NewValue("multiple records found") var ErrTimeout = errors.NewValue("request timeout")
package profile var ErrUserNotFound = errors.NewValue("user not found") var ErrBusinessNotFound = errors.NewValue("business not found") var ErrContextCancel = errors.NewValue("context canceled")
errors.Is()
سره د تېروتنې چک کول او د اضافي شرایطو سره سمول
res, err := repo.QueryUser(ctx, req) switch { case err == nil: // continue case errors.Is(database.NotFound): return nil, errors.Wrapf(ErrUserNotFound, "user not found (id=%v)", req.UserID) default: return nil, errors.Wrapf(ctx, "failed to query user (id=%v)", req.UserID) }
دا د ډیرو توضیحاتو سره د غلطو تبلیغاتو سره مرسته وکړه مګر ډیری وختونه په لاګونو کې د فعلیت ، نقل کولو ، او لږ وضاحت په پایله کې:
internal server error: failed to query user: user not found (id=52a0a433-3922-48bd-a7ac-35dd8972dfe5): record not found: not found
د پروټوبف سره د بهرنۍ غلطیو تعریف کول
د بهرني مخ شوي APIs لپاره ، موږ د پروټوبف پراساس خطا ماډل غوره کړ چېد میټا ګراف API لخوا الهام شوی:
message Error { string message = 1; ErrorType type = 2; ErrorCode code = 3; string user_title = 4; string user_message = 5; string trace_id = 6; map<string, string> details = 7; } enum ErrorType { ERROR_TYPE_UNSPECIFIED = 1; ERROR_TYPE_AUTHENTICATION = 2; ERROR_TYPE_INVALID_REQUEST = 3; ERROR_TYPE_RATE_LIMIT = 4; ERROR_TYPE_BUSINESS_LIMIT = 5; ERROR_TYPE_WEBHOOK_DELIVERY = 6; } enum ErrorCode { ERROR_CODE_UNSPECIFIED = 1 [(error_type = UNSPECIFIED)]; ERROR_CODE_UNAUTHENTICATED = 2 [(error_type = AUTHENTICATION)]; ERROR_CODE_CAMPAIGN_NOT_FOUND = 3 [(error_type = NOT_FOUND)]; ERROR_CODE_META_CHOSE_NOT_TO_DELIVER = 4 /* ... */; ERROR_CODE_MESSAGE_WABA_TEMPLATE_CAN_ONLY_EDIT_ONCE_IN_24_HOURS = 5; }
دې تګلارې د جوړښت له تېروتنو سره مرسته وکړه، خو د وخت په تېرېدو سره، د تېروتنې ډولونه او کوډونه له واضح پلان پرته اضافه شول، چې د تضاد او نقل لامل کېږي.
تېروتنې هرچیرې اعلان شوې
gorm.ErrRecordNotFound
یا user.ErrNotFound
یا دواړه؟
د تصادفي تېروتنې ریپ کول د متضاد او خپلسري لاګونو لامل شوي
unexpected gorm error: failed to find business channel: error received when invoking API: unexpected: context canceled
هیڅ معیاري کول د غلطې غلطۍ اداره کولو لامل شوي
هیڅ کټګورۍ څارنه ناممکنه کړې
context.Canceled
لغوه شوې تېروتنه ممکن یو عادي چلند وي کله چې کاروونکي د براوزر ټب بند کړي، مګر دا مهمه ده که غوښتنه لغوه شي ځکه چې دا پوښتنه په تصادفي ډول ورو ده.د مخ پر ودې ننګونو د حل لپاره، موږ پریکړه وکړه چې د مرکزي او جوړښت شوي غلطی کوډونو اصلي مفکورې په شاوخوا کې د غلطې غوره ستراتیژي جوړه کړو.
Error
ډول چیک کول د هراړخیز مرستندویانو سره.ټول د خطا کوډونه په مرکزي ځای کې د نوم ځای جوړښت سره تعریف شوي.
د روښانه، معنی لرونکي، او د غزولو وړ غلطی کوډونو جوړولو لپاره د نوم ځایونه وکاروئ. بېلګه:
PRFL.USR.NOT_FOUND
د "کاروونکي نه موندل" لپاره.FLD.NOT_FOUND
لپاره "د فلو سند ونه موندل شو."DEPS.PG.NOT_FOUND
، پدې معنی چې "ریکارډ په PostgreSQL کې ندی موندل شوی."
د خدمت یا کتابتون هر پرت باید یوازې د خپل نوم ځای کوډونه بیرته راستانه کړي .
gorm.ErrRecordNotFound
ترلاسه کول، د "ډیټابیس" بسته باید دا د DEPS.PG.NOT_FOUND
په توګه وتړل شي. وروسته، د "پروفایل/کارن" خدمت باید دا بیا د PRFL.USR.NOT_FOUND
په توګه وپلټئ.
ټولې تېروتنې باید د Error
انٹرفیس پلي کړي.
error
) او زموږ د داخلي Error
تر مینځ روښانه سرحد رامینځته کوي.
یوه تېروتنه کولی شي یو یا څو تېروتنې وتړي. په ګډه، دوی یوه ونه جوړوي.
[FLD.INVALID_ARGUMENT] invalid argument → [TPL.INVALID_PARAMS] invalid input params 1. [TPL.PARAM.EMPTY] name can not be empty 2. [TPL.PARAM.MALFORM] invalid format for param[2]
تل context.Context
اړتیا ده . د خطا سره شرایط ضمیمه کیدی شي.
trace_id
، او نه پوهیږو چې دا له کوم ځای څخه راځي.
کله چې تېروتنې د خدماتو د حد په اوږدو کې لیږل کیږي، یوازې د لوړې کچې غلطی کوډ ښکاره کیږي.
د بهرنیو غلطیو لپاره، د اوسني پروټوبف ایرر کوډ او ایرر ټایپ کارولو ته دوام ورکړئ.
د پروټوبف کوډونو، HTTP حالت کوډونو، او ټګونو ته د نوم ځای غلطی کوډونه اتوماتیک کړئ.
ErrorCode
، ErrorType
، gRPC حالت، HTTP حالت، او د ننوتلو/میټریکونو لپاره ټاګونو ته نقشه کړي.دلته یو څو اصلي کڅوړې شتون لري چې زموږ د نوي غلطۍ اداره کولو چوکاټ بنسټ جوړوي.
connectly.ai/go/pkgs/
errors
: اصلي بسته چې د Error
ډول او کوډونه تعریفوي.errors/api
: د مخکني پای یا بهرني API ته د خطا لیږلو لپاره.errors/E
: مرستندویه بسته چې د ډاټ وارداتو سره کارول کیږي.testing
: د نوم ځای غلطیو سره کار کولو لپاره د اسانتیاو ازمول.
Error
او Code
د Error
انٹرفیس د معیاري error
انٹرفیس توسیع دی، د Code
بیرته راستنیدو لپاره اضافي میتودونو سره. یو Code
د uint16
په توګه پلي کیږي.
package errors // import "connectly.ai/go/pkgs/errors" type Error interface { error Code() Code } type Code struct { code uint16 } type CodeI interface { CodeDesc() CodeDesc } type GroupI interface { /* ... */ } type CodeDesc struct { /* ... */ }
د بسته errors/E
ټول غلطی کوډونه او عام ډولونه صادروي
package E // import "connectly.ai/go/pkgs/errors/E" import "connectly.ai/go/pkgs/errors" type Error = errors.Error var ( DEPS = errors.DEPS PRFL = errors.PRFL ) func MapError(ctx context.Context, err error) errors.Mapper { /* ... */ } func IsErrorCode(err error, codes ...errors.CodeI) { /* ... */ } func IsErrorGroup(err error, groups ...errors.GroupI) { /* ... */ }
د تېروتنې کوډونو بېلګه:
// dependencies → postgres DEPS.PG.NOT_FOUND DEPS.PG.UNEXPECTED // sdk → hash SDK.HASH.UNEXPECTED // profile → user PRFL.USR.NOT_FOUND PFRL.USR.UNKNOWN // profile → user → repository PRFL.USR.REPO.NOT_FOUND PRFL.USR.REPO.UNKNOWN // profile → auth PRFL.AUTH.UNAUTHENTICATED PRFL.AUTH.UNKNOWN PRFL.AUTH.UNEXPECTED
د بسته بندۍ database
:
package database // import "connectly.ai/go/pkgs/database" import "gorm.io/gorm" import . "connectly.ai/go/pkgs/errors/E" type DB struct { gorm: gorm.DB } func (d *DB) Exec(ctx context.Context, sql string, params ...any) *DB { tx := d.gorm.WithContext(ctx).Exec(sql, params...) return wrapTx(tx) } func (x *DB) Error(msgArgs ...any) Error { return wrapError(tx.Error()) // 👈 convert gorm error to 'Error' } func (x *DB) SingleRowError(msgArgs ...any) Error { if err := x.Error(); err != nil { return err } switch { case x.RowsAffected == 1: return nil case x.RowsAffected == 0: return DEPS.PG.NOT_FOUND.CallerSkip(1). New(x.Context(), formatMsgArgs(msgArgs)) default: return DEPS.PG.UNEXPECTED.CallerSkip(1). New(x.Context(), formatMsgArgs(msgArgs)) } }
بسته pb/services/profile
:
package profile // import "connectly.ai/pb/services/profile" // these types are generated from services/profile.proto type QueryUserRequest struct { BusinessId string UserId string } type LoginRequest struct { Username string Password string }
د بسته بندۍ service/profile
:
package profile import uuid "github.com/google/uuid" import . "connectly.ai/go/pkgs/errors/E" import l "connectly.ai/go/pkgs/logging/l" import profilepb "connectly.ai/pb/services/profile" // repository requests type QueryUserByUsernameRequest struct { Username string } // repository layer → query user func (r *UserRepository) QueryUserByUsernameAuth( ctx context.Context, req *QueryUserByUsernameRequest, ) (*User, Error) { if req.Username == "" { return PRFL.USR.REPO.INVALID_ARGUMENT.New(ctx, "empty request") } var user User sqlQuery := `SELECT * FROM "user" WHERE username = ? LIMIT 1` tx := r.db.Exec(ctx, sqlQuery, req.Username).Scan(&user) err := tx.SingleRowError() switch { case err == nil: return &user, nil case IsErrorCode(DEPS.PG.NOT_FOUND): return PRFL.USR.REPO.USER_NOT_FOUND. With(l.String("username", req.Username)) Wrap(ctx, "user not found") default: return PRFL.USR.REPO.UNKNOWN. Wrap(ctx, "failed to query user") } } // user service layer → query user func (u *UserService) QueryUser( ctx context.Context, req *profilepb.QueryUserRequest, ) (*profilepb.QueryUserResponse, Error) { // ... rr := QueryUserByUsernameRequest{ Username: req.Username } err := u.repo.QueryUserByUsername(ctx, rr) if err != nil { return nil, MapError(ctx, err). Map(PRFL.USR.REPO.NOT_FOUND, PRFL.USR.NOT_FOUND, "the user %q cannot be found", req.UserName, api.UserTitle("User Not Found"), api.UserMsg("The requested user id %q can not be found", req.UserId)). KeepGroup(PRFL.USR). Default(PRFL.USR.UNKNOWN, "failed to query user") } // ... return resp, nil } // auth service layer → login user func (a *AuthService) Login( ctx context.Context, req *profilepb.LoginRequest, ) (*profilepb.LoginResponse, *profilepb.LoginResponse, Error) { vl := PRFL.AUTH.INVALID_ARGUMENT.WithMsg("invalid request") vl.Vl(req.Username != "", "no username", api.Detail("username is required")) vl.Vl(req.Password != "", "no password", api.Detail("password is required")) if err := vl.ToError(ctx); err != nil { return err } hashpwd, err := hash.Hash(req.Password) if err != nil { return PRFL.AUTH.UNEXPECTED.Wrap(ctx, err, "failed to calc hash") } usrReq := profilepb.QueryUserByUsernameRequest{/*...*/} usrRes, err := a.userServiceClient.QueryUserByUsername(ctx, usrReq) if err != nil { return nil, MapError(ctx, err). Map(PRFL.USR.NOT_FOUND, PRFL.AUTH.UNAUTHENTICATED, "unauthenticated"). Default(PRFL.AUTH.UNKNOWN, "failed to query by username") } // ... }
ښه، په پورتني کوډ کې ډیری نوي افعال او مفکورې شتون لري. راځئ چې ګام په ګام دوی ته لاړ شو.
لومړی، د بسته بندۍ errors/E
د ډاټ واردولو په کارولو سره وارد کړئ
دا به تاسو ته اجازه درکړي چې په مستقیم ډول عام ډولونه وکاروئ لکه errors.Error
پرځای Error
. تېروتنه او کوډونو ته لاسرسی د PRFL.USR.NOT_FOUND
پرځای errors.PRFL.USR.NOT_FOUND
.
import . "connectly.ai/go/pkgs/errors/E"
CODE.New()
په کارولو سره نوې غلطۍ رامینځته کړئ
فرض کړئ چې تاسو یوه ناسمه غوښتنه ترلاسه کړئ، تاسو کولی شئ د دې له لارې نوې تېروتنه جوړه کړئ:
err := PRFL.USR.INVALID_ARGUMENT.New(ctx, "invalid request")
PRFL.USR.INVALID_ARGUMENT
یو Code
دی.Code
د نوي غلطۍ رامینځته کولو لپاره میتودونه لکه New()
یا Wrap()
افشا کوي.New()
فنکشن د لومړي دلیل په توګه context.Context
ترلاسه کوي ، وروسته پیغام او اختیاري دلیلونه.
دا fmt.Print(err)
سره چاپ کړئ:
[PRFL.USR.INVALID_ARGUMENT] invalid request
یا fmt.Printf("%+v")
سره د نورو جزیاتو لیدلو لپاره:
[PRFL.USR.INVALID_ARGUMENT] invalid request connectly.ai/go/services/profile.(*UserService).QueryUser /usr/i/src/go/services/profile/user.go:1234 connectly.ai/go/services/profile.(*UserRepository).QueryUser /usr/i/src/go/services/profile/repo/user.go:2341
CODE.Wrap()
په کارولو سره په یوه نوې تېروتنه کې یوه تېروتنه وتړئ
dbErr := DEPS.PG.NOT_FOUND.Wrap(ctx, gorm.ErrRecordNotFound, "not found") usrErr := PRFL.USR.NOT_FOUND.Wrap(ctx, dbErr, "user not found")
دا محصول به fmt.Print(usrErr)
سره تولید کړي:
[PRFL.USR.NOT_FOUND] user not found → [DEPS.PG.NOT_FOUND] not found → record not found
یا fmt.Printf("%+v", usrErr)
سره
[PRFL.USR.NOT_FOUND] user not found → [DEPS.PG.NOT_FOUND] not found → record not found connectly.ai/go/services/profile.(*UserService).QueryUser /usr/i/src/go/services/profile/user.go:1234
د سټیکټریس به د ترټولو داخلي Error
څخه راشي. که تاسو یو مرستندویه فنکشن لیکئ، تاسو کولی شئ د چوکاټونو پریښودو لپاره CallerSkip(skip)
وکاروئ:
func mapUserError(ctx context.Context, err error) Error { switch { case IsErrorCode(err, DEPS.PG.NOT_FOUND): return PRFL.USR.NOT_FOUND.CallerSkip(1).Wrap(ctx, err, "...") default: return PRFL.USR.UNKNOWN.CallerSkip(1).Wrap(ctx, err, "...") } }
With()
کارولو سره خطا ته شرایط اضافه کړئ
.With(l.String(...))
په واسطه غلطیو ته اضافي کلیدي/ ارزښت جوړه اضافه کړئ.logging/l
د ننوتلو لپاره د شوګر افعال صادرولو لپاره یو مرستندویه بسته ده.l.String("flag", flag)
یو Tag{String: flag}
او l.UUID("user_id, userID)
بیرته ستنیدنه Tag{Stringer: userID}
. import l "connectly.ai/go/pkgs/logging/l" usrErr := PRFL.USR.NOT_FOUND. With(l.UUID("user_id", req.UserID), l.String("flag", flag)). Wrap(ctx, dbErr, "user not found")
ټاګونه fmt.Printf("%+v", usrErr)
سره تولید کیدی شي :
[PRFL.USR.NOT_FOUND] user not found {"user_id": "81febc07-5c06-4e01-8f9d-995bdc2e0a9a", "flag": "ABRW"} → [DEPS.PG.NOT_FOUND] not found {"a number": 42} → record not found
په مستقیم ډول New()
, Wrap()
, or MapError()
دننه غلطیو ته شرایط اضافه کړئ :
l.String()
فنکشن او د هغې د کورنۍ د ګټې اخیستنې په واسطه، New()
او ورته افعال کولی شي په هوښیارۍ سره د فارمینګ دلیلونو په مینځ کې ټاګونه کشف کړي. اړتیا نشته چې مختلف دندې معرفي کړئ.
err := INF.HEALTH.NOT_READY.New(ctx, "service %q is not ready (retried %v times)", req.ServiceName, l.String("flag", flag) countRetries, l.Number("count", countRetries), )
تولید به وکړي:
[INF.HEALTH.NOT_READY] service "magic" is not ready (retried 2 times) {"flag": "ABRW", "count": 2}
Error0
، VlError
، ApiError
اوس مهال، 3 ډولونه شتون لري چې د Error
انٹرفیس پلي کوي. که اړتیا وي تاسو کولی شئ نور ډولونه اضافه کړئ. هر یو کولی شي مختلف جوړښت ولري، د ځانګړو اړتیاو لپاره د دودیز میتودونو سره.
Error
د Go معیاري error
انٹرفیس توسیع دی
type Error interface { error Code() Message() Fields() []tags.Field StackTrace() stacktrace.StackTrace _base() *base // a private method }
دا یو شخصي میتود لري ترڅو ډاډ ترلاسه کړي چې موږ په ناڅاپي ډول د errors
کڅوړې څخه بهر Error
نوي ډولونه پلي نه کوو. موږ ممکن (یا ممکن) په راتلونکي کې دا محدودیت لرې کړو کله چې موږ د کارونې ډیر نمونې تجربه کړو.
ولې موږ یوازې د معیاري error
انٹرفیس نه کاروو او د ډول ادعا وکاروو؟
ځکه چې موږ غواړو د دریمې ډلې تېروتنې او زموږ داخلي تېروتنې ترمنځ جلا کړو. زموږ په داخلي کوډونو کې ټولې پرتونه او کڅوړې باید تل Error
راستانه کړي. پدې توګه موږ کولی شو په خوندي ډول پوه شو کله چې موږ باید د دریمې ډلې غلطۍ بدل کړو ، او کله چې موږ یوازې زموږ د داخلي خطا کوډونو سره معامله کولو ته اړتیا لرو.
دا د مهاجرت شوي کڅوړو او نه تر اوسه مهاجر شوي کڅوړو ترمنځ سرحد هم رامینځته کوي. بیرته واقعیت ته ، موږ نشو کولی یوازې یو نوی ډول اعلان کړو ، د جادو څنډه وغځوو ، د سپیل پرامپټ غږ وکړو ، او بیا د کوډ ټولې ملیونونه لاینونه په جادویی ډول بدل شوي او پرته له کوم بګ پرته کار کوي! نه، دا راتلونکې لا دلته نه ده. دا ممکن یوه ورځ راشي ، مګر د اوس لپاره ، موږ لاهم باید خپل کڅوړې یو له بل سره مهاجر کړو.
Error0
د Error
اصلي ډول دی
ډیری غلطی کوډونه به د Error0
ارزښت تولید کړي. دا یو base
او اختیاري فرعي تېروتنه لري. تاسو کولی شئ د Error
انٹرفیس پرځای د کانکریټ *Error0
جوړښت بیرته راستنولو لپاره NewX()
وکاروئ ، مګر تاسو اړتیا لرئ محتاط اوسئ .
type Error0 struct { base err error } var errA: Error = DEPS.PG.NOT_FOUND.New (ctx, "not found") var errB: *Error0 = DEPS.PG.NOT_FOUND.NewX(ctx, "not found")
base
هغه ګډ جوړښت دی چې د ټولو Error
پلي کولو لخوا شریک شوی، د ګډ فعالیت چمتو کولو لپاره: Code()
, Message()
, StackTrace()
, Fields()
, او نور.
type base struct { code Code msg string kv []tags.Field stack stacktrace.StackTrace }
VlError
د تایید غلطیو لپاره دی
دا کولی شي ډیری فرعي غلطی ولري، او د اعتبار وړ مرسته کونکو سره کار کولو لپاره ښه میتودونه چمتو کړي.
type VlError struct { base errs []error }
تاسو کولی شئ د نورو Error
په څیر یو VlError
جوړ کړئ:
err := PRFL.USR.INVALID_ARGUMENT.New(ctx, "invalid request")
یا VlBuilder
جوړ کړئ، په هغې کې غلطۍ اضافه کړئ، بیا یې په VlError
کې بدل کړئ:
userID, err0 := parseUUID(req.UserId) err1 := validatePassword(req.Password) vl := PRFL.USR.INVALID_ARGUMENT.WithMsg("invalid request") vl.Add(err0, err1) vlErr := vl.ToError(ctx)
او د معمول په څیر کلیدي / ارزښت جوړه شامل کړئ:
vl := PRFL.USR.INVALID_ARGUMENT. With(l.Bool("testingenv", true)). WithMsg("invalid request") userID, err0 := parseUUID(req.UserId) err1 := validatePassword(req.Password) vl.Add(err0, err1) vlErr := vl.ToError(ctx, l.String("user_id", req.UserId))
fmt.Printf("%+v", vlErr)
په کارولو سره به تولید شي:
[PRFL.USR.INVALID_ARGUMENT] invalid request {"testingenv": true, "user_id": "A1234567890"}
ApiError
د API غلطیو مهاجرت لپاره اډاپټر دی
مخکې، موږ یو جلا api.Error
جوړښت کارولی ترڅو د API غلطیو بیرته راستنیدو لپاره مخکینۍ پای او بهرني مراجعینو ته. پدې کې ErrorType
ErrorCode
په توګه شامل دي لکه څنګه چې مخکې یادونه وشوه .
package api import errorpb "connectly.ai/pb/models/error" // Deprecated type Error struct { pbType errorpb.ErrorType pbCode errorpb.ErrorCode cause error msg string usrMsg string usrTitle string // ... }
دا ډول اوس رد شوی دی. پرځای یې، موږ به ټول نقشه ( ErrorType
، ErrorCode
، gRPC کوډ، HTTP کوډ) په مرکزي ځای کې اعلان کړو، او په اړونده حدودو کې یې بدل کړو. زه به په راتلونکې برخه کې د کوډ اعلان په اړه بحث وکړم.
د نوي نوم ځای خطا چوکاټ ته د مهاجرت کولو لپاره، موږ یو لنډمهاله نوم ځای ZZZ.API_TODO
اضافه کړ. هر ErrorCode
د ZZZ.API_TODO
کوډ کیږي.
ZZZ.API_TODO.UNEXPECTED ZZZ.API_TODO.INVALID_REQUEST ZZZ.API_TODO.USERNAME_ ZZZ.API_TODO.META_CHOSE_NOT_TO_DELIVER ZZZ.API_TODO.MESSAGE_WABA_TEMPLATE_CAN_ONLY_EDIT_ONCE_IN_24_HOURS
او ApiError
د اډاپټر په توګه رامینځته شوی. ټول هغه فنکشنونه چې مخکې *api.Error
بیرته راګرځي د بیرته راستنیدو په Error
بدل شوي (د *ApiError
لخوا پلي شوي) پرځای.
package api import . "connectly.ai/go/pkgs/errors/E" // previous func FailPreconditionf(err error, msg string, args ...any) *Error { return &Error{ pbType: ERROR_TYPE_FAILED_PRECONDITION, pbCode: ERROR_CODE_MESSAGE_WABA_TEMPLATE_CAN_ONLY_EDIT_ONCE_IN_24_HOURS, cause: err, msg: fmt.Sprintf(msg, args...) } } // current: this is deprecated, and serves and an adapter func FailPreconditionf(err error, msg string, args ...any) *Error { ctx := context.TODO() return ZZZ.API_TODO.MESSAGE_WABA_TEMPLATE_CAN_ONLY_EDIT_ONCE_IN_24_HOURS. CallerSkip(1). // correct the stacktrace by 1 frame Wrap(ctx, err, msg, args...) }
کله چې ټول مهاجرت ترسره شي، پخوانی کارول:
wabaErr := verifyWabaTemplateStatus(tpl) apiErr := api.FailPreconditionf(wabaErr, "template cannot be edited"). WithErrorCode(ERROR_CODE_MESSAGE_WABA_TEMPLATE_CAN_ONLY_EDIT_ONCE_IN_24_HOURS). WithUserMsg("According to WhatsApp, the message template can be only edited once in 24 hours. Consider creating a new message template instead."). ErrorOrNil()
باید شي:
CPG.TPL.EDIT_ONCE_IN_24_HOURS.Wrap( wabaErr, "template cannot be edited", api.UserMsg("According to WhatsApp, the message template can be only edited once in 24 hours. Consider creating a new message template instead."))
په یاد ولرئ چې د ErrorCode
په ښکاره ډول د داخلي نوم ځای کوډ څخه اخیستل شوی. اړتیا نشته چې هر ځل یې په واضح ډول وټاکئ. مګر څنګه د کوډونو ترمنځ اړیکه اعلان کړئ؟ دا به په راتلونکې برخه کې تشریح شي.
پدې مرحله کې ، تاسو دمخه پوهیږئ چې څنګه د موجوده کوډونو څخه نوې غلطۍ رامینځته کړئ. دا وخت دی چې د کوډونو په اړه تشریح کړئ او څنګه یو نوی اضافه کړئ.
یو Code
د uint16
ارزښت په توګه پلي کیږي ، کوم چې د ورته تار پریزنټشن لري.
type Code struct { code: uint16 } fmt.Printf("%q", DEPS.PG.NOT_FOUND) // "DEPS.PG.NOT_FOUND"
د دې تارونو ذخیره کولو لپاره ، دلته د ټولو موجود CodeDesc
لړۍ شتون لري:
const MaxCode = 321 // 👈 this value is generated var allCodes [MaxCode]CodeDesc type CodeDesc { c int // 42 code string // DEPS.PG.NOT_FOUND api APICodeDesc } type APICodeDesc { ErrorType errorpb.ErrorType ErrorCode errorpb.ErrorCode HttpCode int DefMessage string UserMessage string UserTitle string }
دلته د کوډونو اعلان کولو څرنګوالی دی:
var DEPS deps // dependencies var PRFL prfl // profile var FLD fld // flow document type deps struct { PG pg // postgres RD rd // redis } // tag:postgres type pg struct { NOT_FOUND Code0 // record not found CONFLICT Code0 // record already exist MALFORM_SQL Code0 } // tag:profile type PRFL struct { REPO prfl_repo USR usr AUTH auth } // tag:profile type prfl_repo struct { NOT_FOUND Code0 // internal error code INVALID_ARGUMENT VlCode // internal error code } // tag:usr type usr struct { NOT_FOUND Code0 `api-code:"USER_NOT_FOUND"` INVALID_ARGUMENT VlCode `api-code:"INVALID_ARGUMENT"` DISABlED_ACCOUNT Code0 `api-code:"DISABLED_ACCOUNT"` } // tag:auth type auth struct { UNAUTHENTICATED Code0 `api-code:"UNAUTHENTICATED"` PERMISSION_DENIED Code0 `api-code:"PERMISSION_DENIED"` }
د نوي کوډونو اعلانولو وروسته، تاسو اړتیا لرئ چې د نسل سکریپټ چل کړئ :
run gen-errors
تولید شوی کوډ به داسې ښکاري:
// Code generated by error-codes. DO NOT EDIT. func init() { // ... PRFL.AUTH.UNAUTHENTICATED = Code0{Code{code: 143}} PRFL.AUTH.PERMISSION_DENIED = Code0{Code{code: 144}} // ... allCodes[143] = CodeDesc{ c: 143, code: "PRFL.AUTH.UNAUTHENTICATED", tags: []string{"auth", "profile"}, api: APICodeDesc{ ErrorType: ERROR_TYPE_UNAUTHENTICATED, ErrorCode: ERROR_CODE_UNAUTHENTICATED, HTTPCode: 401, DefMessage: "Unauthenticated error", UserMessage: "You are not authenticated.", UserTitle: "Unauthenticated error", })) }
د هرې Error
ډول د ورته Code
ډول لري
کله مو فکر کړی چې څنګه PRFL.USR.NOT_FOUND.New()
یو *Error0
جوړوي او PRFL.USR.INVALID_ARGUMENTS.New()
یو *VlError
جوړوي؟ دا ځکه چې دوی مختلف کوډ ډولونه کاروي.
او د هر Code
ډول مختلف Error
ډول بیرته راګرځوي، هر یو کولی شي خپل اضافي میتودونه ولري:
type Code0 struct { Code } type VlCode struct { Code } func (c Code0) New(/*...*/) Error { return &Error0{/*...*/} } func (c VlCode) New(/*...*/) Error { return &VlError{/*...*/} } // extra methods on VlCode to create VlBuilder func (c VlCode) WithMsg(msg string, args ...any) *VlBuilder {/*...*/} type VlBuilder struct { code VlCode msg string args []any } func (b *VlBuilder) ToError(/*...*/) Error { return &VlError{Code: code, /*...*/ } }
د بهرني API لپاره موجود کوډونو په نښه کولو لپاره api-code
وکاروئ
د نوم ځای خطا کوډ باید په داخلي توګه وکارول شي.
په بهرني HTTP API کې د بیرته راستنیدو لپاره کوډ چمتو کولو لپاره ، تاسو اړتیا لرئ دا api-code
سره په نښه کړئ. ارزښت د اړونده errorpb.ErrorCode
دی.
که د تېروتنې کوډ api-code
سره په نښه شوی نه وي، دا داخلي کوډ دی او د عمومي Internal Server Error
په توګه به ښودل شي.
په یاد ولرئ چې PRFL.USR.NOT_FOUND
بهرنی کوډ دی، پداسې حال کې چې PRFL.USR.REPO.NOT_FOUND
داخلي کوډ دی.
د اینوم اختیار په کارولو سره په پروټوبف کې ErrorCode
، ErrorType
، او gRPC/HTTP کوډونو ترمنځ نقشه اعلان کړئ :
// error/type.proto ERROR_TYPE_PERMISSION_DENIED = 707 [(error_type_detail_option) = { type: "PermissionDeniedError", grpc_code: PERMISSION_DENIED, http_code: 403, // Forbidden message: "permission denied", user_title: "Permission denied", user_message: "The caller does not have permission to execute the specified operation.", }]; // error/code.proto ERROR_CODE_DISABlED_ACCOUNT = 70020 [(error_code_detail_option) = { error_type: ERROR_TYPE_DISABlED_ACCOUNT, grpc_code: PERMISSION_DENIED, http_code: 403, // Forbidden message: "account is disabled", user_title: "Account is disabled", user_message: "Your account is disabled. Please contact support for more information.", }];
UNEXPECTED
او UNKNOWN
کوډونه
هره طبقه معمولا دوه عمومي کوډونه لري UNEXPECTED
او UNKNOWN
. دوی یو څه مختلف اهدافو ته خدمت کوي:
UNEXPECTED
کوډ د غلطیو لپاره کارول کیږي چې هیڅکله باید پیښ نشي.UNKNOWN
کوډ د هغو غلطیو لپاره کارول کیږي چې په ښکاره توګه نه اداره کیږي.کله چې د فنکشن څخه بیرته راستون شوې تېروتنه ترلاسه کول، تاسو اړتیا لرئ چې دا اداره کړئ: د دریمې ډلې تېروتنې د داخلي نوم ځای غلطیو ته بدل کړئ او د نقشې غلطی کوډونه د داخلي پرتونو څخه بهر پرتونو ته.
د دریمې ډلې غلطۍ د داخلي نوم ځای غلطیو ته واړوئ
تاسو څنګه خطاګانې اداره کوئ پدې پورې اړه لري: د دریمې ډلې کڅوړه څه بیرته راګرځي او ستاسو غوښتنلیک څه ته اړتیا لري. د مثال په توګه، کله چې د ډیټابیس یا بهرني API غلطیو اداره کول:
switch { case errors.Is(err, sql.ErrNoRows): // map a database "no rows" error to an internal "not found" error return nil, PRFL.USR.NOT_FOUND.Wrap(ctx, err, "user not found") case errors.Is(err, context.DeadlineExceeded): // map a context deadline exceeded error to a timeout error return nil, PRFL.USR.TIMEOUT.Wrap(ctx, err, "query timeout") default: // wrap any other error as unknown return nil, PRFL.USR.UNKNOWN.Wrap(ctx, err, "unexpected error") }
د داخلي نوم ځای غلطیو لپاره د مرستندویانو کارول
IsErrorCode(err, CODES...)
: چک کوي چې آیا تېروتنه کوم ټاکل شوي کوډونه لري.IsErrorGroup(err, GROUP)
: ریښتیا راشئ که چیرې خطا د ان پټ ګروپ پورې اړه ولري.
د کارونې عادي بڼه:
user, err := queryUser(ctx, userReq) switch { case err == nil: // continue case IsErrorCode(PRL.USR.REPO.NOT_FOUND): // check for specific error code and convert to external code // and return as HTTP 400 Not Found return nil, PRFL.USR.NOT_FOUND.Wrap(ctx, err, "user not found") case IsGroup(PRL.USR): // errors belong to the PRFL.USR group are returned as is return nil, err default: return nil, PRL.USR.UNKNOWN.Wrap(ctx, err, "failed to query user") }
MapError()
د نقشه کولو کوډ لیکلو لپاره آسانه:
څنګه چې د نقشه کولو خطا کوډونه یو عام نمونه ده ، نو د کوډ لیکلو ګړندي کولو لپاره د MapError()
مرسته کونکی شتون لري. پورتني کوډ په لاندې ډول لیکل کیدی شي:
user, err := queryUser(ctx, userReq) if err != nil { return nil, MapError(ctx, err). Map(PRL.USR.REPO.NOT_FOUND, PRFL.USR.NOT_FOUND, "user not found"). KeepGroup(PRF.USR). Default(PRL.USR.UNKNOWN, "failed to query user") }
تاسو کولی شئ دلیلونه فارمیټ کړئ او د معمول په توګه کلیدي / ارزښت جوړه اضافه کړئ:
return nil, MapError(ctx, err). Map(PRL.USR.REPO.NOT_FOUND, PRFL.USR.NOT_FOUND, "user %v not found", username, l.String("flag", flag)). KeepGroup(PRF.USR). Default(PRL.USR.UNKNOWN, "failed to query user", l.Any("retries", retryCount))
Error
s ازموینه د هر جدي کوډ اساس لپاره خورا مهمه ده. چوکاټ ځانګړي مرسته کونکي چمتو کوي لکه ΩxError()
ترڅو په ازموینو کې د خطا شرایطو لیکل او تاکید اسانه او ډیر څرګند کړي.
// 👉 return true if the error contains the message ΩxError(err).Contains("not found") // 👉 return true if the error does not contain the message ΩxError(err).NOT().Contains("not found")
ډیری نور میتودونه شتون لري، او تاسو کولی شئ دوی هم زنځیر کړئ:
ΩxError(err). MatchCode(DEPS.PG.NOT_FOUND). // match any code in top or wrapped errors TopErrorMatchCode(PRFL.TPL.NOT_FOUND) // only match code from the top error MatchAPICode(API_CODE.WABA_TEMPLATE_NOTE_FOUND). // match errorpb.ErrorCode MatchExact("exact message to match")
ولې د Ω(err).To(testing.MatchCode())
پر ځای میتودونه وکاروئ ؟
ځکه چې میتودونه د موندلو وړ دي. کله چې تاسو د لسګونو دندو لکه testing.MatchValues()
سره مخ شئ، دا ستونزمنه ده چې پوه شئ چې کوم به Error
s سره کار وکړي او کوم به نه. د میتودونو سره، تاسو کولی شئ په ساده ډول یو ټکی ټایپ کړئ .
، او ستاسو IDE به ټول موجود میتودونه لیست کړي چې په ځانګړي ډول Error
ادعا کولو لپاره ډیزاین شوي.
چوکاټ یوازې د کیسې نیمایي دی. کوډ لیکل؟ دا اسانه برخه ده. اصلي ننګونه هغه وخت پیل کیږي کله چې تاسو باید دا په پراخه ، ژوندي کوډبیس کې راوړئ چیرې چې لسګونه انجینران هره ورځ بدلونونه هڅوي ، پیرودونکي تمه لري چې هرڅه سم کار وکړي ، او سیسټم نشي کولی د چلولو مخه ونیسي.
مهاجرت د مسؤلیت سره راځي. دا په احتیاط سره د ویښتو کوچني کوډونو ویشلو ، په یو وخت کې کوچني بدلونونه رامینځته کول ، په پروسه کې د ټن ازموینې ماتولو په اړه دي. بیا په لاسي ډول معاینه کول او یو یو یې تنظیم کول ، په اصلي څانګه کې ضمیمه کول ، تولید ته ځای په ځای کول ، د لاګونو او خبرتیاو لیدل. په وار وار یې تکرارول...
دلته د مهاجرت لپاره ځینې لارښوونې دي چې موږ د لارې په اوږدو کې زده کړل:
د لټون او بدلولو سره پیل کړئ: د نوي چوکاټ سره د زړو نمونو بدلولو سره پیل کړئ. د تالیف کومې ستونزې حل کړئ چې د دې پروسې څخه راپورته کیږي.
د مثال په توګه، په دې بسته کې ټولې error
Error
سره بدل کړئ.
type ProfileController interface { LoginUser(req *LoginRequest) (*LoginResponse, error) QueryUser(req *QueryUserRequest) (*QueryUserResponse, error) }
نوی کوډ به داسې ښکاري:
import . "connectly.ai/go/pkgs/errors" type ProfileController interface { LoginUser(req *LoginRequest) (*LoginResponse, Error) QueryUser(req *QueryUserRequest) (*QueryUserResponse, Error) }
په یو وخت کې یوه کڅوړه مهاجرت کړئ: د ټیټې کچې کڅوړو سره پیل وکړئ او خپل کار پورته کړئ. په دې توګه، تاسو کولی شئ ډاډ ترلاسه کړئ چې د ټیټې کچې کڅوړې په بشپړه توګه لیږدول شوي مخکې له دې چې لوړې کچې ته لاړ شي.
د ورک شوي واحد ازموینې اضافه کړئ: که چیرې د کوډبیس برخې ازموینې نلري ، نو اضافه کړئ. که تاسو په خپلو بدلونونو ډاډه نه یاست، نور ازموینې اضافه کړئ. دوی ګټور دي ترڅو ډاډ ترلاسه کړي چې ستاسو بدلونونه موجوده فعالیت نه ماتوي.
که ستاسو بسته د لوړې کچې کڅوړو په زنګ وهلو پورې اړه ولري: اړونده افعال په پام کې ونیسئ په DEPRECATED کې بدل کړئ بیا د نوي Error
ډول سره نوي افعال اضافه کړئ.
فرض کړئ چې تاسو د ډیټابیس کڅوړه مهاجرت کوئ، کوم چې د Transaction()
میتود لري:
package database func (db *DB) Transaction(ctx context.Context, fn func(tx *gorm.DB) error) error { return db.gorm.Transaction(func(tx *gorm.DB) error { return fn(tx) }) }
او دا د کارونکي خدماتو بسته کې کارول کیږي:
err = s.DB(ctx).Transaction(func(tx *database.DB) error { user, usrErr := s.repo.CreateUser(ctx, tx, user) if usrErr != nil { return usrErr } }
له هغه ځایه چې تاسو لومړی د database
کڅوړه مهاجرت کوئ ، نو user
او لسګونه نور کڅوړې ورته پریږدئ. د s.repo.CreateUser()
زنګ لاهم د پخوانۍ error
ډول بیرته راګرځوي پداسې حال کې چې د Transaction()
میتود اړتیا لري چې د نوي Error
ډول بیرته راستانه کړي. تاسو کولی شئ د Transaction()
میتود په DEPRECATED
بدل کړئ او یو نوی TransactionV2()
میتود اضافه کړئ:
package database // DEPRECATED: use TransactionV2 instead func (db *DB) Transaction_DEPRECATED(ctx context.Context, fn func(tx *gorm.DB) error) error { return db.gorm.Transaction(func(tx *gorm.DB) error { return fn(tx) }) } func (db *DB) TransactionV2(ctx context.Context, fn func(tx *gorm.DB) error) Error { err := db.gorm.Transaction(func(tx *gorm.DB) error { return fn(tx) }) return adaptToErrorV2(err) }
د تېروتنې نوي کوډونه اضافه کړئ لکه څنګه چې تاسو لاړ شئ : کله چې تاسو د یوې تېروتنې سره مخ شئ چې په موجوده کې مناسب نه وي، نو نوی کوډ اضافه کړئ. دا به تاسو سره د وخت په تیریدو سره د خطا کوډونو جامع سیټ رامینځته کولو کې مرسته وکړي. د نورو کڅوړو کوډونه تل د حوالې په توګه شتون لري.
په Go کې د خطا اداره کول په لومړي سر کې ساده احساس کولی شي — یوازې یوه error
بیرته راګرځئ او پرمخ لاړشئ. مګر لکه څنګه چې زموږ کوډبیس وده وکړه ، دا سادگي د مبهم لاګونو ، متناسب اداره کولو ، او نه ختمیدونکي ډیبګ سیشنونو په پیچلي ګډوډي بدله شوه.
په شا تګ او بیا فکر کولو سره چې موږ څنګه تېروتنې اداره کوو، موږ یو سیسټم جوړ کړی چې زموږ لپاره کار کوي، نه زموږ په وړاندې. مرکزي او جوړښت شوي نوم ځای کوډونه موږ ته روښانه کوي، پداسې حال کې چې د نقشه کولو، ریپ کولو، او ازموینې تېروتنې وسایل زموږ ژوند اسانه کوي. د دې پر ځای چې د لوګو د بحر له لارې لامبو وهو، موږ اوس معنی لرونکي، د موندلو وړ تېروتنې لرو چې موږ ته ووایي چې څه غلط دي او چیرته یې وګورو.
دا چوکاټ یوازې زموږ د کوډ پاکولو په اړه ندی؛ دا د وخت خوندي کولو، د مایوسۍ کمولو، او د نامعلومو لپاره چمتو کولو کې مرسته کوي. دا یوازې د سفر پیل دی - موږ لاهم نور نمونې کشف کوو - مګر پایله یې یو سیسټم دی چې کولی شي په یو ډول د غلطۍ اداره کولو لپاره د ذهن سوله راولي. په امید سره، دا کولی شي ستاسو د پروژو لپاره ځینې نظرونه هم روښانه کړي! 😊
زه Oliver Nguyen یم یو سافټویر جوړونکی چې ډیری یې په Go او JavaScript کې کار کوي. زه هره ورځ د خپل ځان غوره نسخه زده کولو او لیدو څخه خوند اخلم. ځینې وختونه د خلاصې سرچینې نوې پروژې بند کړئ. زما د سفر په جریان کې پوهه او فکرونه شریک کړئ.
پوسټ په blog.connectly.ai او olivernguyen.io کې هم خپور شوی 👋