Go: الاقتراح: وظيفة مضمنة للتحقق من الخطأ Go ، "حاول"

تم إنشاؤها على ٥ يونيو ٢٠١٩  ·  808تعليقات  ·  مصدر: golang/go

الاقتراح: وظيفة مدمجة للتحقق من الخطأ Go ، try

تم إغلاق هذا الاقتراح.

قبل التعليق ، يرجى قراءة مستند التصميم التفصيلي والاطلاع على ملخص المناقشة اعتبارًا من 6 يونيو ، والملخص اعتبارًا من 10 يونيو ، والأهم من ذلك النصيحة بشأن استمرار التركيز _. ربما تم بالفعل الرد على سؤالك أو اقتراحك أو تقديمه. شكرا.

نقترح وظيفة مضمنة جديدة تسمى try ، مصممة خصيصًا لإزالة العبارات المعيارية if المرتبطة عادةً بمعالجة الأخطاء في Go. لم يتم اقتراح أي تغييرات لغة أخرى. ندعو إلى استخدام عبارة defer الحالية ووظائف المكتبة القياسية للمساعدة في زيادة الأخطاء أو التفافها. يعالج هذا الأسلوب البسيط السيناريوهات الأكثر شيوعًا مع إضافة القليل جدًا من التعقيد إلى اللغة. من السهل شرح العنصر المضمن try ، وهو سهل التنفيذ ، ومتعامد مع تركيبات اللغة الأخرى ، ومتوافق تمامًا مع الإصدارات السابقة. كما أنه يترك السبيل مفتوحا لتوسيع الآلية ، إذا أردنا أن نفعل ذلك في المستقبل.

[تم تحرير النص أدناه ليعكس مستند التصميم بشكل أكثر دقة.]

تأخذ الدالة المضمنة try تعبيرًا واحدًا كوسيطة. يجب أن يتم تقييم التعبير إلى قيم n + 1 (حيث قد تكون n صفراً) حيث يجب أن تكون القيمة الأخيرة من النوع error . تقوم بإرجاع قيم n الأولى (إن وجدت) إذا كانت وسيطة الخطأ (النهائية) صفرية ، وإلا فإنها ترجع من دالة التضمين بهذا الخطأ. على سبيل المثال ، رمز مثل

f, err := os.Open(filename)
if err != nil {
    return …, err  // zero values for other results, if any
}

يمكن تبسيطها إلى

f := try(os.Open(filename))

لا يمكن استخدام try إلا في دالة تقوم بدورها بإرجاع نتيجة error ، ويجب أن تكون هذه النتيجة آخر معلمة نتيجة لوظيفة التضمين.

يقلل هذا الاقتراح من تصميم المسودة الأصلي المقدم في GopherCon العام الماضي إلى جوهره. إذا كانت زيادة الخطأ أو التفافه مرغوبة ، فهناك طريقتان: التمسك بعبارة if المجربة والصحيحة ، أو بدلاً من ذلك ، "أعلن" معالج خطأ باستخدام عبارة defer :

defer func() {
    if err != nil { // no error may have occurred - check for it
        err = … // wrap/augment error
    }
}()

هنا ، err هو اسم نتيجة الخطأ لوظيفة التضمين. في الممارسة العملية ، ستعمل الوظائف المساعدة المناسبة على تقليل إعلان معالج الخطأ إلى سطر واحد. على سبيل المثال

defer fmt.HandleErrorf(&err, "copy %s %s", src, dst)

(حيث يزين $ fmt.HandleErrorf *err ) يقرأ جيدًا ويمكن تنفيذه دون الحاجة إلى ميزات لغة جديدة.

العيب الرئيسي لهذا الأسلوب هو أن معلمة نتيجة الخطأ تحتاج إلى تسمية ، مما قد يؤدي إلى واجهات برمجة تطبيقات أقل جمالًا. في النهاية ، هذه مسألة أسلوب ، ونعتقد أننا سوف نتكيف مع توقع النمط الجديد ، مثلما تكيفنا مع عدم وجود فواصل منقوطة.

باختصار ، قد يبدو try غير معتاد في البداية ، لكنه ببساطة سكر نحوي مصمم خصيصًا لمهمة واحدة محددة ، ومعالجة الأخطاء مع عدد أقل من المتغيرات ، والتعامل مع هذه المهمة بشكل جيد بما فيه الكفاية. على هذا النحو فإنه يتناسب بشكل جيد مع فلسفة Go. لم يتم تصميم try لمعالجة _جميع_ حالات معالجة الأخطاء ؛ إنه مصمم للتعامل مع الحالة _ الأكثر شيوعًا_ بشكل جيد ، للحفاظ على التصميم بسيطًا وواضحًا.

الاعتمادات

يتأثر هذا الاقتراح بشدة بالتعليقات التي تلقيناها حتى الآن. على وجه التحديد ، يستعير الأفكار من:

وثيقة التصميم التفصيلي

https://github.com/golang/proposal/blob/master/design/32437-try-builtin.md

أداة tryhard لاستكشاف تأثير try

https://github.com/griesemer/tryhard

Go2 LanguageChange Proposal error-handling

التعليق الأكثر فائدة

مرحبا جميعا،

هدفنا بمقترحات مثل هذا هو إجراء مناقشة على مستوى المجتمع حول الآثار والمقايضات وكيفية المضي قدمًا ، ثم استخدام هذه المناقشة للمساعدة في تحديد المسار إلى الأمام.

بناءً على استجابة المجتمع الساحقة والمناقشة المكثفة هنا ، فإننا نصنف هذا الاقتراح بالرفض قبل الموعد المحدد .

فيما يتعلق بالتعليقات الفنية ، فقد حددت هذه المناقشة بشكل مفيد بعض الاعتبارات المهمة التي فاتناها ، وأبرزها الآثار المترتبة على إضافة مطبوعات تصحيح الأخطاء وتحليل تغطية الكود.

والأهم من ذلك ، لقد سمعنا بوضوح العديد من الأشخاص الذين جادلوا بأن هذا الاقتراح لا يستهدف مشكلة جديرة بالاهتمام. ما زلنا نعتقد أن معالجة الأخطاء في Go ليست مثالية ويمكن تحسينها بشكل هادف ، ولكن من الواضح أننا كمجتمع نحتاج إلى التحدث أكثر عن الجوانب المحددة لمعالجة الأخطاء التي يجب أن نعالجها.

بقدر ما نناقش المشكلة التي يتعين حلها ، حاولنا وضع رؤيتنا للمشكلة في أغسطس الماضي في " نظرة عامة على مشكلة معالجة الخطأ في Go 2 " ، ولكن في وقت لاحق لم نلفت الانتباه الكافي إلى هذا الجزء ولم نشجع بما فيه الكفاية مناقشة حول ما إذا كانت المشكلة المحددة هي المشكلة الصحيحة. قد يكون عرض try حلاً جيدًا للمشكلة الموضحة هناك ، ولكن بالنسبة للكثيرين منكم ، لا يمثل حل المشكلة ببساطة. نحتاج في المستقبل إلى القيام بعمل أفضل للفت الانتباه إلى بيانات المشكلة المبكرة هذه والتأكد من وجود اتفاق واسع النطاق حول المشكلة التي تحتاج إلى حل.

(من الممكن أيضًا أن يكون بيان مشكلة معالجة الخطأ قد تم تغييره بالكامل من خلال نشر مسودة تصميم الأدوية الجنيسة في نفس اليوم.)

فيما يتعلق بالموضوع الأوسع حول ما يجب تحسينه بشأن معالجة أخطاء Go ، سنكون سعداء جدًا لرؤية تقارير الخبرة حول جوانب معالجة الأخطاء في Go التي تمثل أكثر إشكالية بالنسبة لك في قواعد التعليمات البرمجية وبيئات العمل الخاصة بك ومدى تأثير الحل الجيد لديك في التنمية الخاصة بك. إذا كتبت مثل هذا التقرير ، فالرجاء نشر ارتباط على صفحة Go2ErrorHandlingFeedback .

شكراً لكل من شارك في هذه المناقشة ، هنا وفي أي مكان آخر. كما أشار روس كوكس من قبل ، فإن المناقشات على مستوى المجتمع مثل هذه المناقشات مفتوحة المصدر في أفضل حالاتها . نحن نقدر حقًا مساعدة الجميع في دراسة هذا الاقتراح المحدد وبشكل عام في مناقشة أفضل الطرق لتحسين حالة معالجة الأخطاء في Go.

روبرت جريسيمر ، عن لجنة مراجعة العروض.

ال 808 كومينتر

أوافق على أن هذه هي أفضل طريقة للمضي قدمًا: إصلاح المشكلة الأكثر شيوعًا بتصميم بسيط.

لا أرغب في ركوب الدراجة (لا تتردد في تأجيل هذه المحادثة) ، لكن راست ذهب إلى هناك واستقر في النهاية مع عامل postfix ? بدلاً من وظيفة مضمنة ، لزيادة سهولة القراءة.

يستشهد اقتراح gophercon ? في الأفكار المدروسة ويعطي ثلاثة أسباب لتجاهلها: الأول ("عمليات نقل التدفق كقاعدة عامة مصحوبة بالكلمات الرئيسية") والثالث ("يتم تعريف المعالجات بشكل طبيعي أكثر بكلمة رئيسية ، لذا يجب أن تكون عمليات التحقق أيضًا ") لا تنطبق بعد الآن. الثاني هو الأسلوب: يقول أنه حتى لو كان عامل التشغيل postfix يعمل بشكل أفضل للتسلسل ، فإنه لا يزال بإمكانه قراءة الأسوأ في بعض الحالات مثل:

check io.Copy(w, check newReader(foo))

بدلا من:

io.Copy(w, newReader(foo)?)?

لكن الآن سيكون لدينا:

try(io.Copy(w, try(newReader(foo))))

الذي أعتقد أنه من الواضح أنه الأسوأ من الثلاثة ، لأنه لم يعد واضحًا بعد الآن ما هي الوظيفة الرئيسية التي يتم استدعاؤها.

لذا فإن جوهر تعليقي هو أن الأسباب الثلاثة المذكورة في اقتراح gophercon لعدم استخدام ? لا تنطبق على عرض try هذا ؛ ? موجز ، سهل القراءة ، لا يحجب بنية البيان (مع تسلسل استدعاءات وظيفته الداخلية) ، وهو قابل للتسلسل. إنه يزيل المزيد من الفوضى من العرض ، بينما لا يحجب تدفق التحكم أكثر مما يفعله بالفعل try() المقترح.

للتوضيح:

هل

func f() (n int, err error) {
  n = 7
  try(errors.New("x"))
  // ...
}

إرجاع (0، "x") أو (7، "x")؟ كنت أفترض الأخير.

هل يجب تسمية الخطأ الذي تم إرجاعه في حالة عدم وجود زخرفة أو معالجة (كما هو الحال في وظيفة المساعد الداخلي)؟ لا أفترض.

المثال الخاص بك يعيد 7, errors.New("x") . يجب أن يكون هذا واضحًا في المستند الكامل الذي سيتم إرساله قريبًا (https://golang.org/cl/180557).

لا يلزم تسمية معلمة نتيجة الخطأ لاستخدام try . تحتاج فقط إلى تسميتها إذا كانت الوظيفة تحتاج إلى الرجوع إليها في دالة مؤجلة أو في مكان آخر.

أنا غير سعيد حقًا بوظيفة مدمجة تؤثر على تدفق التحكم في المتصل. أقدر استحالة إضافة كلمات رئيسية جديدة في Go 1 ، ولكن حل هذه المشكلة باستخدام الوظائف المدمجة السحرية يبدو خطأً بالنسبة لي. لا يؤدي تظليل العناصر المضمنة الأخرى إلى نتائج غير متوقعة مثل تغيير تدفق التحكم.

لا أحب الطريقة التي تبدو بها postfix ? ، لكنني أعتقد أنها لا تزال تتفوق try() .

تحرير: حسنًا ، لقد نسيت تمامًا أن الذعر موجود وليس كلمة رئيسية.

الاقتراح المفصل هنا (تحسينات التنسيق المعلقة ، ستأتي قريبًا) ونأمل أن يجيب على الكثير من الأسئلة.

dominikh يناقش الاقتراح التفصيلي هذا الأمر بالتفصيل ، ولكن يرجى ملاحظة أن panic و recover هما عنصران مدمجان يؤثران على تدفق التحكم أيضًا.

توضيح / اقتراح واحد للتحسين:

if the last argument supplied to try, of type error, is not nil, the enclosing function’s error result variable (...) is set to that non-nil error value before the enclosing function returns

هل يمكن أن يقول هذا بدلاً من ذلك is set to that non-nil error value and the enclosing function returns ؟ (ق / قبل / و)

في القراءة الأولى ، بدا أن before the enclosing function returns سيحدد قيمة الخطأ _ في بعض الأحيان_ في وقت ما في المستقبل قبل إرجاع الدالة مباشرةً - ربما في سطر لاحق. التفسير الصحيح هو أن المحاولة قد تؤدي إلى عودة الوظيفة الحالية. يعد هذا سلوكًا مفاجئًا للغة الحالية ، لذا سيكون النص الأكثر وضوحًا موضع ترحيب.

أعتقد أن هذا مجرد سكر ، وقام عدد قليل من المعارضين الصاخبين بمضايقة جولانغ بشأن الاستخدام المتكرر لكتابة if err != nil ... وأخذها شخص ما على محمل الجد. لا أعتقد أنها مشكلة. الأشياء الوحيدة المفقودة هي هاتان المادتان المضمنتان:

https://github.com/purpleidea/mgmt/blob/a235b760dc3047a0d66bb0b9d63c25bc746ed274/util/errwrap/errwrap.go#L26

لست متأكدًا من سبب كتابة أي شخص لوظيفة مثل هذه ولكن ما هو الناتج المتصور

try(foobar())

إذا أرجع $ foobar (error, error)

لقد تراجعت عن مخاوفي السابقة بشأن تدفق التحكم ولم أعد أقترح استخدام ? . أعتذر عن الرد السريع (على الرغم من أنني أود أن أشير إلى أن هذا لم يكن ليحدث لو تم تقديم المشكلة بعد _ كان الاقتراح الكامل متاحًا).

لا أوافق على ضرورة المعالجة المبسطة للخطأ ، لكنني متأكد من أنها معركة خاسرة. يبدو أن try كما هو موضح في الاقتراح هو أقل الطرق سوءًا للقيام بذلك.

webermaster تعتبر النتيجة error الأخيرة فقط خاصة للتعبير الذي تم تمريره إلى try ، كما هو موضح في مستند الاقتراح.

مثل dominikh ، أنا أيضًا لا أتفق مع ضرورة معالجة الأخطاء بشكل مبسط.

إنه ينقل التعقيد الرأسي إلى التعقيد الأفقي الذي نادرًا ما يكون فكرة جيدة.

إذا كان لا بد لي من الاختيار بين تبسيط مقترحات معالجة الأخطاء ، فسيكون هذا هو اقتراحي المفضل.

سيكون من المفيد أن يكون هذا مصحوبًا (في مرحلة ما من القبول) بأداة لتحويل كود Go لاستخدام try في بعض المجموعات الفرعية من وظائف إرجاع الخطأ حيث يمكن إجراء مثل هذا التحويل بسهولة بدون تغيير الدلالات. ثلاث فوائد تخطر ببالي:

  • عند تقييم هذا الاقتراح ، سيسمح للأشخاص بالتعرف بسرعة على كيفية استخدام try في قاعدة الكود الخاصة بهم.
  • إذا try في إصدار مستقبلي من Go ، فمن المحتمل أن يرغب الناس في تغيير الكود الخاص بهم للاستفادة منه. إن امتلاك أداة لأتمتة الحالات السهلة سيساعد كثيرًا.
  • إن وجود طريقة لتحويل قاعدة بيانات كبيرة بسرعة لاستخدام try سيجعل من السهل فحص تأثيرات التنفيذ على نطاق واسع. (على سبيل المثال ، الدقة والأداء وحجم الكود.) قد يكون التنفيذ بسيطًا بما يكفي لجعل هذا اعتبارًا لا يُذكر.

أود فقط أن أعبر عن أنني أعتقد أن الإنقاذ الفعلي لوظيفة الاستدعاء من try(foo()) يزيل منا الإشارة المرئية التي قد يتغير تدفق الوظيفة اعتمادًا على النتيجة.

أشعر أنه يمكنني العمل مع try نظرًا لما يكفي من التعود ، لكنني أشعر أيضًا أننا سنحتاج إلى دعم IDE إضافي (أو بعضه) لتسليط الضوء على try للتعرف بكفاءة على التدفق الضمني في مراجعات الكود / جلسات التصحيح

أكثر ما يقلقني هو الحاجة إلى تسمية قيم الإرجاع فقط حتى يكون بيان التأجيل سعيدًا.

أعتقد أن مشكلة معالجة الخطأ الشاملة التي يشكو المجتمع منها هي مزيج من النموذج المعياري if err != nil وإضافة سياق إلى الأخطاء. تنص الأسئلة الشائعة بوضوح على أن المشكلة الأخيرة تم استبعادها عن قصد كمشكلة منفصلة ، لكنني أشعر أن هذا سيصبح حلاً غير مكتمل ، لكنني سأكون على استعداد لمنحه فرصة بعد التفكير في هذين الأمرين:

  1. قم بتعريف err في بداية الوظيفة.
    هل هذا فعال؟ أذكر المشكلات المتعلقة بالنتائج المؤجلة وغير المسماة. إذا لم يكن الأمر كذلك ، فإن الاقتراح يحتاج إلى النظر في ذلك.
func sample() (string, error) {
  var err error
  defer fmt.HandleErrorf(&err, "whatever")
  s := try(f())
  return s, nil
}
  1. قم بتعيين القيم كما فعلنا في الماضي ، ولكن استخدم الدالة المساعدة wrapf التي تحتوي على if err != nil boilerplate.
func sample() (string, error) {
  s, err := f()
  try(wrapf(err, "whatever"))
  return s, nil
}
func wrapf(err error, format string, ...v interface{}) error {
  if err != nil {
    // err = wrapped error
  }
  return err
}

إذا نجح أي منهما ، يمكنني التعامل معه.

func sample() (string, error) {
  var err error
  defer fmt.HandleErrorf(&err, "whatever")
  s := try(f())
  return s, nil
}

هذا لن يعمل. سيقوم المؤجل بتحديث المتغير المحلي err ، والذي لا علاقة له بقيمة الإرجاع.

func sample() (string, error) {
  s, err := f()
  try(wrapf(err, "whatever"))
  return s, nil
}
func wrapf(err error, format string, ...v interface{}) error {
  if err != nil {
    // err = wrapped error
  }
  return err
}

التي يجب أن تعمل. ومع ذلك ، فإنه سوف يستدعي wrapf حتى عند وجود خطأ لا شيء.
سيعمل هذا أيضًا (يستمر في) العمل ، وستكون IMO أكثر وضوحًا:

func sample() (string, error) {
  s, err := f()
  if err != nil {
      return "", wrap(err)
  }
  return s, nil
}

لن يجعلك أحد تستخدم try .

لست متأكدًا من سبب كتابة أي شخص لوظيفة مثل هذه ولكن ما هو الناتج المتصور

try(foobar())

إذا أرجع $ foobar (error, error)

لماذا تقوم بإرجاع أكثر من خطأ من دالة؟ إذا كنت تقوم بإرجاع أكثر من خطأ واحد من الوظيفة ، فربما يجب تقسيم الوظيفة إلى جزأين منفصلين في المقام الأول ، كل منهما يعرض خطأ واحدًا فقط.

هل يمكنك التفصيل بمثال؟

cespare : يجب أن يكون بإمكان أي شخص كتابة go fix يعيد كتابة الكود الحالي المناسب لـ try بحيث يستخدم try . قد يكون من المفيد التعرف على كيفية تبسيط الكود الموجود. لا نتوقع أي تغييرات كبيرة في حجم الشفرة أو أدائها ، نظرًا لأن try هو مجرد سكر نحوي ، يستبدل النمط الشائع بقطعة أقصر من شفرة المصدر تنتج بشكل أساسي نفس كود الإخراج. لاحظ أيضًا أن الكود الذي يستخدم try سيكون ملزمًا باستخدام إصدار Go وهو على الأقل الإصدار الذي تم تقديم try فيه.

lestrrat : وافق على أنه يجب على المرء أن يتعلم أن try يمكن أن يغير تدفق التحكم. نشك في أن IDE يمكن أن يسلط الضوء على ذلك بسهولة كافية.

@ Goodwine : كما أشار @ randall77 بالفعل ، فإن اقتراحك الأول لن ينجح. أحد الخيارات التي فكرنا فيها (ولكن لم تتم مناقشتها في المستند) هو إمكانية وجود متغير محدد مسبقًا يشير إلى النتيجة error (إذا كان أحدهم موجودًا في المقام الأول). سيؤدي ذلك إلى التخلص من الحاجة إلى تسمية تلك النتيجة بحيث يمكن استخدامها في defer . لكن هذا سيكون سحرًا أكثر ؛ لا يبدو له ما يبرره. مشكلة تسمية النتيجة المرتجعة هي مشكلة تجميلية بشكل أساسي ، وأهم ما يكون في واجهات برمجة التطبيقات التي يتم إنشاؤها تلقائيًا والتي يقدمها go doc والأصدقاء. سيكون من السهل معالجة هذا الأمر في تلك الأدوات (انظر أيضًا الأسئلة الشائعة الخاصة بمستند التصميم التفصيلي حول هذا الموضوع).

nictuku : فيما يتعلق باقتراحك للتوضيح (ق / قبل / و /): أعتقد أن الكود مباشرة قبل الفقرة التي تشير إليها يوضح ما يحدث بالضبط ، لكني أرى وجهة نظرك ، قبل / و / قد اجعل النثر أكثر وضوحا. سأقوم بالتغيير.

انظر CL 180637 .

أنا حقا أحب هذا الاقتراح. ومع ذلك ، لدي نقد واحد. تم دائمًا تمييز نقطة الخروج من الوظائف في Go بـ return . تعتبر حالات الذعر أيضًا نقاط خروج ، ولكنها أخطاء فادحة لا يُقصد عادةً مواجهتها أبدًا.

قد يؤدي إنشاء نقطة خروج لدالة ليست return ، ويُقصد بها أن تكون شائعة ، إلى رمز أقل قابلية للقراءة. لقد سمعت عن هذا في حديث ومن الصعب أن أفهم جمال كيفية بناء هذا الكود:

func CopyFile(src, dst string) error {
    r, err := os.Open(src)
    if err != nil {
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }
    defer r.Close()

    w, err := os.Create(dst)
    if err != nil {
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }

    if _, err := io.Copy(w, r); err != nil {
        w.Close()
        os.Remove(dst)
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }

    if err := w.Close(); err != nil {
        os.Remove(dst)
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }
}

قد يبدو هذا الرمز وكأنه فوضى كبيرة ، وكان _ يعني _ من خلال مسودة معالجة الخطأ ، لكن دعنا نقارنه بالشيء نفسه بـ try .

func CopyFile(src, dst string) error {
    defer func() {
        err = fmt.Errorf("copy %s %s: %v", src, dst, err)
    }()
    r, err := try(os.Open(src))
    defer r.Close()

    w, err := try(os.Create(dst))

    defer w.Close()
    defer os.Remove(dst)
    try(io.Copy(w, r))
    try(w.Close())

    return nil
}

قد تنظر إلى هذا للوهلة الأولى وتعتقد أنه يبدو أفضل ، لأنه يوجد عدد أقل بكثير من التعليمات البرمجية المتكررة. ومع ذلك ، كان من السهل جدًا تحديد جميع النقاط التي أعادتها الوظيفة في المثال الأول. تم وضع مسافة بادئة لها جميعًا وبدأت بـ return ، متبوعة بمسافة. هذا بسبب حقيقة أن جميع المرتجعات الشرطية _يجب_ أن تكون داخل الكتل الشرطية ، وبالتالي يتم وضع مسافة بادئة لها بواسطة معايير gofmt . return هو أيضًا ، كما ذكر سابقًا ، الطريقة الوحيدة لترك دالة دون القول بحدوث خطأ فادح. في المثال الثاني ، لا يوجد سوى return واحد ، لذلك يبدو أن الشيء الوحيد الذي يجب أن تقوم الوظيفة _ever_ بإرجاعه هو nil . من السهل رؤية مكالمتين الأخيرتين try ، لكن الأولين يكونان أصعب قليلاً ، وسيكونان أكثر صعوبة إذا تم تداخلهما في مكان ما ، أي شيء مثل proc := try(os.FindProcess(try(strconv.Atoi(os.Args[1])))) .

يبدو أن العودة من الوظيفة كان أمرًا "مقدسًا" ، ولهذا السبب أعتقد شخصيًا أن جميع نقاط الخروج للوظيفة يجب تمييزها بـ return .

شخص ما نفذ هذا بالفعل منذ 5 سنوات. إذا كنت مهتمًا ، يمكنك ذلك
جرب هذه الميزة

https://news.ycombinator.com/item؟id=20101417

لقد نفذت try () في Go منذ خمس سنوات باستخدام معالج أولي AST واستخدمته في مشاريع حقيقية ، لقد كان رائعًا: https://github.com/lunixbochs/og

فيما يلي بعض الأمثلة على استخدامه في وظائف التحقق من الأخطاء الثقيلة: https://github.com/lunixbochs/poxd/blob/master/tls.go#L13

أنا أقدر الجهد المبذول في هذا. أعتقد أنه أكثر الحلول التي رأيتها حتى الآن. لكنني أعتقد أنه يقدم مجموعة من الأعمال عند تصحيح الأخطاء. محاولة فك التفاف وإضافة كتلة if في كل مرة أقوم فيها بتصحيحها وإعادة تغليفها عند الانتهاء هو أمر ممل. ولدي أيضًا بعض الانزعاج بشأن متغير الخطأ السحري الذي أحتاج إلى مراعاته. لم يزعجني التحقق الصريح من الخطأ مطلقًا ، لذا ربما أكون الشخص الخطأ الذي أسأله. لقد أذهلني دائمًا أنه "جاهز للتصحيح".

تضمين التغريدة
مشكلتي في استخدامك المقترح لـ "تأجيل" كطريقة للتعامل مع التفاف الخطأ هو أن السلوك من المقتطف الذي عرضته (المكرر أدناه) ليس شائعًا جدًا في AFAICT ، ولأنه نادر جدًا ، يمكنني تخيل الناس يكتبون هذا التفكير أنه يعمل عندما لا يحدث ذلك.

مثل .. لن يعرف المبتدئ هذا ، إذا كان لديهم خطأ بسبب هذا فلن يذهبوا "بالطبع ، أنا بحاجة إلى عودة محددة" ، فسيصابون بالتوتر لأنه يجب أن يعمل وهو لا.

var err error
defer fmt.HandleErrorf(err);

try هو بالفعل ساحر للغاية ، لذا يمكنك أيضًا المضي قدمًا وإضافة قيمة الخطأ الضمني هذه. فكر في المبتدئين ، وليس أولئك الذين يعرفون كل الفروق الدقيقة في Go. إذا لم يكن الأمر واضحًا بما فيه الكفاية ، فلا أعتقد أنه الحل الصحيح.

أو ... لا تقترح استخدام تأجيل مثل هذا ، جرب طريقة أخرى أكثر أمانًا ولكن لا تزال قابلة للقراءة.

deanveloper صحيح أن هذا الاقتراح (وفي هذا الصدد ، أي اقتراح يحاول محاولة نفس الشيء) سيزيل عبارات return المرئية بوضوح من الكود المصدري - وهذا هو بيت القصيد من الاقتراح بعد كل شيء ، أليس كذلك؟ لإزالة الصيغة المعيارية لكشوفات if و returns فكلها متطابقة. إذا كنت تريد الاحتفاظ بـ return ، فلا تستخدم try .

لقد اعتدنا على التعرف فورًا على كشوفات الحساب returnpanic ) لأن هذه هي الطريقة التي يتم بها التعبير عن هذا النوع من تدفق التحكم في Go (والعديد من اللغات الأخرى). يبدو أنه ليس بعيد المنال أننا سنتعرف أيضًا على try كتغيير تدفق التحكم بعد أن يعتاد البعض عليه ، تمامًا كما نفعل مقابل return . ليس لدي شك في أن دعم IDE الجيد سيساعد في هذا أيضًا.

لدي شاغلان:

  • لقد كانت عمليات الإرجاع المسماة مربكة للغاية ، وهذا يشجعهم على استخدام حالة استخدام جديدة ومهمة
  • سيؤدي هذا إلى تثبيط إضافة سياق إلى الأخطاء

من واقع خبرتي ، تعد إضافة السياق إلى الأخطاء فورًا بعد كل موقع اتصال أمرًا بالغ الأهمية للحصول على رمز يمكن تصحيحه بسهولة. وقد تسببت عمليات العودة المسماة في حدوث ارتباك لكل مطور Go أعرفه تقريبًا في مرحلة ما.

مصدر القلق الأسلوبي البسيط هو أنه من المؤسف عدد سطور التعليمات البرمجية التي سيتم تغليفها الآن بـ try(actualThing()) . يمكنني تخيل رؤية معظم السطور في قاعدة بيانات ملفوفة بـ try() . هذا شعور مؤسف.

أعتقد أنه سيتم التعامل مع هذه المخاوف من خلال تعديل:

a, b, err := myFunc()
check(err, "calling myFunc on %v and %v", a, b)

سيتصرف check() مثل try() ، لكنه سيتخلى عن سلوك تمرير قيم إرجاع الدالة بشكل عام ، وبدلاً من ذلك سيوفر القدرة على إضافة سياق. لا يزال من شأنه أن يؤدي إلى العودة.

سيحتفظ هذا بالعديد من مزايا try() :

  • إنه مدمج
  • إنه يتبع WRT لتدفق التحكم الحالي للتأجيل
  • يتماشى مع الممارسة الحالية لإضافة سياق إلى الأخطاء بشكل جيد
  • يتماشى مع العروض والمكتبات الحالية لتغليف الأخطاء ، مثل errors.Wrap(err, "context message")
  • ينتج عنه موقع اتصال نظيف: لا توجد لغة معيارية في السطر a, b, err := myFunc()
  • لا يزال وصف الأخطاء باستخدام defer fmt.HandleError(&err, "msg") ممكنًا ، ولكن لا داعي للتشجيع.
  • التوقيع check أبسط قليلاً ، لأنه لا يحتاج إلى إرجاع عدد عشوائي من الوسيطات من الوظيفة التي يتم تغليفها.

@ s4n-gt شكرا لهذا الارتباط. لم أكن على علم به.

Goodwine نقطة مأخوذة. تمت مناقشة سبب عدم تقديم المزيد من الدعم المباشر لمعالجة الأخطاء في مستند التصميم بالتفصيل. ومن الحقائق أيضًا أنه على مدار عام أو نحو ذلك (منذ أن نُشرت مسودة التصميمات في Gophercon العام الماضي) لم يتم التوصل إلى حل مُرضٍ لمعالجة الأخطاء الصريحة. وهذا هو سبب ترك هذا الاقتراح لهذا الأمر عن قصد (وبدلاً من ذلك يقترح استخدام defer ). ولا يزال هذا الاقتراح يترك الباب مفتوحًا للتحسينات المستقبلية في هذا الصدد.

يذكر الاقتراح تغيير اختبار الحزمة للسماح للاختبارات والمعايير بإرجاع خطأ. على الرغم من أنه لن يكون "تغييرًا متواضعًا في المكتبة" ، إلا أنه يمكننا التفكير في قبول func main() error أيضًا. سيجعل كتابة نصوص صغيرة أجمل بكثير. الدلالات ستكون معادلة لـ:

func main() {
  if err := newmain(); err != nil {
    println(err.Error())
    os.Exit(1)
  }
}

نقد أخير. ليس في الحقيقة انتقادًا للاقتراح نفسه ، ولكن بدلاً من ذلك نقدًا لاستجابة مشتركة للحجة المضادة "وظيفة التحكم في التدفق".

الرد على "لا أحب أن تقوم الوظيفة بالتحكم في التدفق" هو ​​أن " panic يتحكم أيضًا في تدفق البرنامج!". ومع ذلك ، هناك بعض الأسباب التي تجعل من المقبول أن يقوم panic بالقيام بذلك والتي لا تنطبق على try .

  1. panic ودود للمبرمجين المبتدئين لأن ما يفعله بديهي ، ويستمر في فك المكدس. لا يجب على المرء حتى البحث عن كيفية عمل panic لفهم ما يفعله. لا يحتاج المبرمجون المبتدئون إلى القلق بشأن recover ، لأن المبتدئين لا يبنون عادة آليات التعافي من الذعر ، خاصة وأنهم دائمًا ما يكونون أقل تفضيلًا من مجرد تجنب الذعر في المقام الأول.

  2. panic اسم يسهل رؤيته. إنه يجلب القلق ، ويحتاج إلى ذلك. إذا رأى أحدهم panic في قاعدة بيانات ، يجب أن يفكر على الفور في كيفية تجنب الذعر ، حتى لو كان تافهًا.

  3. بعيدًا عن النقطة الأخيرة ، لا يمكن تضمين panic في مكالمة ، مما يجعل رؤيتها أسهل.

لا بأس في أن يتحكم الذعر في تدفق البرنامج لأنه من السهل للغاية اكتشافه ، كما أنه أمر بديهي فيما يفعله.

لا تحقق الوظيفة try أيًا من هذه النقاط.

  1. لا يمكن لأحد أن يخمن ما يفعله try دون البحث عن الوثائق الخاصة به. تستخدم العديد من اللغات الكلمة الأساسية بطرق مختلفة ، مما يجعل من الصعب فهم ما تعنيه في Go.

  2. try لا يلفت انتباهي ، خاصة عندما تكون وظيفة. _ على وجه الخصوص_ عندما يؤدي إبراز بناء الجملة إلى إبرازها كوظيفة. _ESPECIALLY_ بعد التطوير بلغة مثل Java ، حيث يُنظر إلى try على أنه نموذج معياري غير ضروري (بسبب الاستثناءات المحددة).

  3. يمكن استخدام try في وسيطة لاستدعاء دالة ، وفقًا لمثالي في تعليقي السابق proc := try(os.FindProcess(try(strconv.Atoi(os.Args[1])))) . هذا يجعل من الصعب اكتشافها.

تتجاهل عيني وظائف try ، حتى عندما أبحث عنها على وجه التحديد. ستراهم عيناي ، لكني انتقل فورًا إلى المكالمات os.FindProcess أو strconv.Atoi . try هو إرجاع مشروط. يتم تعليق كل من تدفق التحكم والعودة على قواعد في Go. يتم وضع مسافة بادئة لكل تدفق التحكم داخل دالة ، وتبدأ جميع المرتجعات بـ return . إن المزج بين هذين المفهومين معًا في استدعاء دالة سهل الفقدان يبدو قليلاً.


هذا التعليق والأخير هو انتقاداتي الحقيقية الوحيدة للفكرة. أعتقد أنني قد أعتبر أنني لا أحب هذا الاقتراح ، لكنني ما زلت أعتقد أنه فوز شامل لـ Go. لا يزال هذا الحل يشبه Go أكثر من الحلول الأخرى. إذا تمت إضافة هذا ، فسأكون سعيدًا ، ومع ذلك أعتقد أنه لا يزال من الممكن تحسينه ، لست متأكدًا من كيفية القيام بذلك.

تضمين التغريدة كما هو مكتوب ، فإنه ينقل تنسيق fmt من حزمة إلى اللغة نفسها ، مما يفتح علبة من الديدان.

كما هو مكتوب ، فإنه ينقل تنسيق fmt من حزمة إلى اللغة نفسها ، مما يفتح علبة من الديدان.

نقطة جيدة. مثال أبسط:

a, b, err := myFunc()
check(err, "calling myFunc")

buchanae لقد فكرنا في جعل معالجة الخطأ الصريح أكثر ارتباطًا بشكل مباشر بـ try - يرجى الاطلاع على مستند التصميم التفصيلي ، وتحديدًا القسم الخاص بتكرارات التصميم. اقتراحك المحدد لـ check سيسمح فقط بزيادة الأخطاء من خلال شيء مثل fmt.Errorf مثل API (كجزء من check ) ، إذا فهمت بشكل صحيح. بشكل عام ، قد يرغب الأشخاص في القيام بكل أنواع الأشياء التي تحتوي على أخطاء ، وليس فقط إنشاء واحدة جديدة تشير إلى الشيء الأصلي عبر سلسلة الخطأ الخاصة به.

مرة أخرى ، لا يحاول هذا الاقتراح حل جميع حالات معالجة الأخطاء. أظن أنه في معظم الحالات يكون try منطقيًا للشفرة التي تبدو الآن بشكل أساسي كما يلي:

a, b, c, ... err := try(someFunctionCall())
if err != nil {
   return ..., err
}

هناك عدد هائل من التعليمات البرمجية التي تبدو مثل هذا. وليس كل جزء من الكود يبدو بهذا الشكل يحتاج إلى مزيد من معالجة الأخطاء. وحيث يكون defer غير صحيح ، لا يزال بإمكان المرء استخدام كشف حساب if .

أنا لا أتبع هذا الخط:

defer fmt.HandleErrorf(&err, “foobar”)

يسقط الخطأ الداخلي على الأرض ، وهو أمر غير معتاد. هل من المفترض أن يتم استخدام شيء مثل هذا؟

defer fmt.HandleErrorf(&err, “foobar: %v”, err)

الازدواجية في الخطأ هي تلعثم بعض الشيء. هذا لا ينطبق بشكل مباشر على الاقتراح ، مجرد تعليق جانبي حول المستند.

أشارك القلقين اللذين أثارهماbuchanae ، re: الإرجاع المسماة والأخطاء السياقية.

أجد أن الاسم يعود مزعجًا بعض الشيء كما هو ؛ أعتقد أنها مفيدة حقًا فقط كتوثيق. الاعتماد عليهم أكثر هو مصدر قلق. آسف لكوني غامضة جدا ، رغم ذلك. سأفكر في هذا أكثر وأقدم بعض الأفكار الملموسة.

أعتقد أن هناك قلقًا حقيقيًا من أن الناس سوف يسعون جاهدين لبناء الكود الخاص بهم بحيث يمكن استخدام try ، وبالتالي تجنب إضافة سياق إلى الأخطاء. هذا وقت غريب بشكل خاص لتقديم هذا ، نظرًا لأننا نقدم الآن طرقًا أفضل لإضافة سياق إلى الأخطاء من خلال ميزات التفاف الأخطاء الرسمية.

أعتقد أن try كما هو مقترح يجعل بعض الكود أجمل بشكل ملحوظ. هذه وظيفة اخترتها بشكل عشوائي من قاعدة رمز مشروعي الحالي ، مع تغيير بعض الأسماء. إنني معجب بشكل خاص بكيفية عمل try عند التعيين لحقول الهيكل. (هذا على افتراض أن قراءتي للاقتراح صحيحة ، وأن هذا يعمل؟)

الكود الحالي:

func NewThing(thingy *foo.Thingy, db *sql.DB, client pb.Client) (*Thing, error) {
        err := dbfile.RunMigrations(db, dbMigrations)
        if err != nil {
                return nil, err
        }
        t := &Thing{
                thingy: thingy,
        }
        t.scanner, err = newScanner(thingy, db, client)
        if err != nil {
                return nil, err
        }
        t.initOtherThing()
        return t, nil
}

بـ try :

func NewThing(thingy *foo.Thingy, db *sql.DB, client pb.Client) (*Thing, error) {
        try(dbfile.RunMigrations(db, dbMigrations))
        t := &Thing{
                thingy:  thingy,
                scanner: try(newScanner(thingy, db, client)),
        }
        t.initOtherThing()
        return t, nil
}

لا يوجد فقدان في قابلية القراءة ، باستثناء أنه من غير الواضح أن newScanner قد يفشل. ولكن في عالم به try Go ، سيكون المبرمجون أكثر حساسية لوجودها.

josharian بخصوص main إرجاع error : يبدو لي أن وظيفتك المساعدة الصغيرة هي كل ما تحتاجه للحصول على نفس التأثير. لست متأكدًا من أن تغيير توقيع main له ما يبرره.

فيما يتعلق بمثال "foobar": إنه مجرد مثال سيء. ربما يجب أن أغيرها. شكرا لإحضاره.

defer fmt.HandleErrorf(&err, “foobar: %v”, err)

في الواقع ، هذا لا يمكن أن يكون صحيحًا ، لأنه سيتم تقييم err مبكرًا جدًا. هناك طريقتان للتغلب على هذا ، لكن أيا منها ليس نظيفًا مثل المقود الأصلي (أعتقد أنه معيب). أعتقد أنه سيكون من الجيد أن يكون لديك مثال عملي أكثر واقعية أو مثالين للدالة المساعدة.

تحرير: خطأ التقييم المبكر هذا موجود في أحد الأمثلة
بالقرب من نهاية المستند:

defer fmt.HandleErrorf(&err, "copy %s %s: %v", src, dst, err)

adg نعم ، يمكن استخدام try أثناء استخدامه في المثال الخاص بك. اسمحوا لي أن تعليقاتكم إعادة: المسماة العودة تقف كما هي.

قد يرغب الأشخاص في القيام بكل أنواع الأشياء التي تحتوي على أخطاء ، وليس فقط إنشاء واحدة جديدة تشير إلى الشيء الأصلي عبر سلسلة الخطأ الخاصة به.

try لا يحاول التعامل مع كل أنواع الأشياء التي يريد الناس القيام بها مع الأخطاء ، فقط تلك الأشياء التي يمكننا إيجاد طريقة عملية لجعلها أبسط بشكل ملحوظ. أعتقد أن مثال check يسير على نفس الخط.

في تجربتي ، فإن الشكل الأكثر شيوعًا لرمز معالجة الأخطاء هو الكود الذي يضيف بشكل أساسي تتبع المكدس ، أحيانًا مع سياق إضافي. لقد وجدت أن تتبع المكدس هذا مهم جدًا لتصحيح الأخطاء ، حيث أتبع رسالة خطأ من خلال الكود.

ولكن ، ربما ستضيف المقترحات الأخرى تتبعات مكدس لجميع الأخطاء؟ لقد فقدت المسار.

في المثال الذي قدمتهadg ، هناك نوعان من الإخفاقات المحتملة ولكن لا يوجد سياق. إذا لم يقدم newScanner و RunMigrations هم أنفسهم الرسائل التي تدل على الخطأ الذي حدث ، فأنت تترك التخمين.

في المثال الذي قدمتهadg ، هناك نوعان من الإخفاقات المحتملة ولكن لا يوجد سياق. إذا لم يقدم كل من newScanner و RunMigrations الرسائل التي تدل على الخطأ الذي حدث فيه أحد ، فأنت تترك التخمين.

هذا صحيح ، وهذا هو اختيار التصميم الذي قمنا به في هذا الجزء المحدد من التعليمات البرمجية. نحن نلف الأخطاء كثيرًا في أجزاء أخرى من الكود.

أشاركك القلق كـ deanveloper وآخرين من أنه قد يجعل تصحيح الأخطاء أكثر صعوبة. صحيح أنه يمكننا اختيار عدم استخدامه ، لكن أنماط تبعيات الطرف الثالث ليست تحت سيطرتنا.
إذا كانت النقطة الأساسية أقل تكرارًا هي if err := ... { return err } ، أتساءل عما إذا كانت "العودة المشروطة" كافية ، مثل https://github.com/golang/go/issues/27794 المقترحة.

        return nil, err if f, err := os.Open(...)
        return nil, err if _, err := os.Write(...)

أعتقد أن ? سيكون أفضل من try ، كما أن الاضطرار دائمًا إلى مطاردة defer للخطأ سيكون أمرًا صعبًا أيضًا.

يؤدي هذا أيضًا إلى إغلاق البوابات لوجود استثناءات باستخدام try/catch إلى الأبد.

يؤدي هذا أيضًا إلى إغلاق البوابات لوجود استثناءات باستخدام try / catch إلى الأبد.

أنا بخير مع هذا.

أتفق مع بعض المخاوف التي أثيرت أعلاه فيما يتعلق بإضافة سياق إلى الخطأ. أحاول ببطء التحول من مجرد إرجاع خطأ إلى تزيينه دائمًا بسياق ثم إعادته. مع هذا الاقتراح ، سأضطر إلى تغيير وظيفتي تمامًا لاستخدام معلمات الإرجاع المسماة (والتي أشعر أنها غريبة لأنني بالكاد استخدم عوائد عارية).

كما يقول griesemer :

مرة أخرى ، لا يحاول هذا الاقتراح حل جميع حالات معالجة الأخطاء. أظن أن المحاولة في معظم الحالات منطقية بالنسبة إلى الكود الذي يبدو الآن بشكل أساسي كالتالي:
أ ، ب ، ج ، ... يخطئ: = حاول (someFunctionCall ())
إذا أخطأت! = لا شيء {
العودة ... ، يخطئ
}
هناك عدد هائل من التعليمات البرمجية التي تبدو مثل هذا. وليس كل جزء من الكود يبدو بهذا الشكل يحتاج إلى مزيد من معالجة الأخطاء. وحيثما يكون التأجيل غير صحيح ، لا يزال بإمكان المرء استخدام عبارة if.

نعم ، ولكن ليس جيدًا ، فالشفرة الاصطلاحية تغلف / تزين أخطائها دائمًا؟ أعتقد أن هذا هو السبب في أننا نقدم آليات معالجة الأخطاء المحسّنة لإضافة أخطاء السياق / الالتفاف في stdlib. كما أرى ، يبدو أن هذا الاقتراح ينظر فقط في حالة الاستخدام الأساسية.

علاوة على ذلك ، لا يعالج هذا الاقتراح سوى حالة التفاف / تزيين عدة مواقع إرجاع خطأ محتملة في مكان واحد ، باستخدام معلمات مسماة مع استدعاء مؤجل.

لكنها لا تفعل أي شيء للحالة عندما يحتاج المرء إلى إضافة سياقات مختلفة لأخطاء مختلفة في وظيفة واحدة. على سبيل المثال ، من الضروري جدًا تزيين أخطاء قاعدة البيانات للحصول على مزيد من المعلومات حول المكان الذي أتوا منه (بافتراض عدم وجود آثار مكدس)

هذا مثال على رمز حقيقي لدي -

func (p *pgStore) DoWork() error {
    tx, err := p.handle.Begin()
    if err != nil {
        return err
    }
    var res int64
    err = tx.QueryRow(`INSERT INTO table (...) RETURNING c1`, ...).Scan(&res)
    if err != nil {
        tx.Rollback()
        return fmt.Errorf("insert table: %w", err)
    }

    _, err = tx.Exec(`INSERT INTO table2 (...) VALUES ($1)`, res)
    if err != nil {
        tx.Rollback()
        return fmt.Errorf("insert table2: %w", err)
    }
    return tx.Commit()
}

حسب العرض:

إذا كان هناك رغبة في زيادة أو التفاف الخطأ ، فهناك طريقتان: التمسك بعبارة if try-and-true ، أو ، بدلاً من ذلك ، "أعلن" معالج خطأ ببيان تأجيل:

أعتقد أن هذا سوف يندرج ضمن فئة "التمسك بعبارة if المجربة والصحيحة". آمل أن يتم تحسين الاقتراح لمعالجة هذا أيضًا.

أقترح بشدة أن يعطي فريق Go الأولوية للأدوية ، حيث يسمع Go معظم الانتقادات ، وينتظر معالجة الأخطاء. تقنية اليوم ليست مؤلمة (يجب أن go fmt على سطر واحد).

يحتوي مفهوم try() على جميع مشكلات check من الشيك / المقبض:

  1. لا يقرأ مثل Go. يريد الناس بناء جملة الواجب ، بدون اختبار الصفر اللاحق ، حيث يبدو ذلك مثل Go. اقترحت ثلاثة عشر إجابة منفصلة للتحقق / التعامل هذا ؛ انظر _السمات المتكررة_ هنا:
    https://github.com/golang/go/wiki/Go2ErrorHandlingFeedback#recurring -المواضيع

    f, #      := os.Open(...) // return on error
    f, #panic := os.Open(...) // panic on error
    f, #hname := os.Open(...) // invoke named handler on error
    // # is any available symbol or unambiguous pair
    
  2. يؤدي تداخل استدعاءات الوظائف التي ترجع أخطاء إلى حجب ترتيب العمليات ، ويعيق تصحيح الأخطاء. يجب أن تكون الحالة عند حدوث خطأ ، وبالتالي تسلسل الاتصال ، واضحين ، لكن الأمر ليس كذلك:
    try(step4(try(step1()), try(step3(try(step2())))))
    تذكر الآن أن اللغة تمنع:
    f(t ? a : b) و f(a++)

  3. سيكون من التافه إرجاع أخطاء بدون سياق. كان الأساس المنطقي الرئيسي للفحص / التعامل هو تشجيع التهيئة للسياق.

  4. إنه مرتبط بكتابة error وقيمة الإرجاع الأخيرة. إذا احتجنا إلى فحص قيم / أنواع الإرجاع الأخرى للحالة الاستثنائية ، فنعود إلى: if errno := f(); errno != 0 { ... }

  5. لا تقدم مسارات متعددة. الكود الذي يستدعي التخزين أو واجهات برمجة التطبيقات للشبكات يعالج مثل هذه الأخطاء بشكل مختلف عن تلك الناتجة عن الإدخال غير الصحيح أو الحالة الداخلية غير المتوقعة. يقوم الكود الخاص بي بواحد من هذه الأشياء في كثير من الأحيان أكثر من return err :

    • سجل. فادح ()
    • panic () للأخطاء التي لا يجب أن تظهر أبدًا
    • سجل رسالة وأعد المحاولة

gopherbot إضافة Go2، LanguageChange

ماذا عن استخدام ? فقط لإلغاء النتيجة مثل rust

قد يكون سبب تشككنا في استدعاء try () عبارة عن رابطين ضمنيين. لا يمكننا رؤية الارتباط لخطأ القيمة المرجعة والوسيطات الخاصة بـ try (). بالنسبة إلى try () ، يمكننا وضع قاعدة مفادها أننا يجب أن نستخدم try () مع وظيفة الوسيطة التي تحتوي على خطأ في قيم الإرجاع. لكن القيم الملزمة للعودة ليست كذلك. لذلك أعتقد أن المزيد من التعبير مطلوب للمستخدمين لفهم ما تفعله هذه الشفرة.

func doSomething() (int, %error) {
  f := try(foo())
  ...
}
  • لا يمكننا استخدام try () إذا لم يكن لدى doSomething %error في قيم الإرجاع.
  • لا يمكننا استخدام try () إذا لم يكن لدى foo () خطأ في آخر قيم الإرجاع.

من الصعب إضافة متطلبات / ميزة جديدة إلى البنية الحالية.

لأكون صادقًا ، أعتقد أن foo () يجب أن يحتوي أيضًا على خطأ٪.

أضف قاعدة واحدة أخرى

  • يمكن أن يكون الخطأ٪ واحدًا فقط في قائمة القيمة المرجعة للدالة.

في مستند التصميم التفصيلي ، لاحظت أنه في تكرار سابق ، تم اقتراح تمرير معالج الأخطاء إلى وظيفة المحاولة المضمنة. مثله:

handler := func(err error) error {
        return fmt.Errorf("foo failed: %v", err)  // wrap error
}

f := try(os.Open(filename), handler)  

أو أفضل ، مثل هذا:

f := try(os.Open(filename), func(err error) error {
        return fmt.Errorf("foo failed: %v", err)  // wrap error
})  

على الرغم من أن هذا ، كما تنص الوثيقة ، يثير العديد من الأسئلة ، إلا أنني أعتقد أن هذا الاقتراح سيكون مرغوبًا فيه ومفيدًا أكثر إذا احتفظ بهذه الإمكانية لتحديد وظيفة معالج الأخطاء أو الإغلاق بشكل اختياري.

ثانيًا ، لا مانع من أن عنصرًا مضمّنًا يمكن أن يتسبب في عودة الوظيفة ، ولكن ، لركوب الدراجة قليلاً ، فإن الاسم "try" قصير جدًا بحيث لا يشير إلى أنه يمكن أن يتسبب في إرجاع. لذا فإن الاسم الأطول ، مثل attempt يبدو أفضل بالنسبة لي.

تحرير: ثالثًا ، من الناحية المثالية ، يجب أن تكتسب لغة go الأدوية الجنسية أولاً ، حيث ستكون حالة الاستخدام المهمة هي القدرة على تنفيذ وظيفة المحاولة هذه باعتبارها عامة ، بحيث يمكن أن ينتهي التعرق بالدراجات ، ويمكن للجميع الحصول على معالجة الخطأ التي يفضلونها.

تحتوي أخبار القرصنة على بعض النقاط: try لا تتصرف مثل وظيفة عادية (يمكن أن تعود) لذلك ليس من الجيد إعطائها بناء جملة يشبه الوظيفة. سيكون بناء الجملة A return أو defer أكثر ملاءمة:

func CopyFile(src, dst string) (err error) {
        r := try os.Open(src)
        defer r.Close()

        w := try os.Create(dst)
        defer func() {
                w.Close()
                if err != nil {
                        os.Remove(dst) // only if a “try” fails
                }
        }()

        try io.Copy(w, r)
        try w.Close()
        return nil
}

sheerun الحجة المضادة الشائعة لهذا هو أن panic هو أيضًا وظيفة مضمنة لتغيير التحكم في التدفق. أنا شخصياً لا أتفق معها ، لكنها صحيحة.

  1. صدى deanveloper أعلاه ، بالإضافة إلى التعليقات المشابهة للآخرين ، أخشى جدًا أننا نقلل من تكاليف إضافة كلمة رئيسية جديدة ، دقيقة إلى حد ما ، وخاصة عندما تكون مضمنة في استدعاءات وظيفية أخرى - يتم التغاضي عنها بسهولة والتي تدير التحكم في مكدس المكالمات تدفق. panic(...) هو استثناء واضح نسبيًا (التورية ليست مقصودة) لقاعدة أن return هو السبيل الوحيد للخروج من الوظيفة. لا أعتقد أننا يجب أن نستخدم وجودها كمبرر لإضافة ثلث.
  2. من شأن هذا الاقتراح أن يجعل إرجاع الخطأ غير المغلف هو السلوك الافتراضي ، ويحيل أخطاء التغليف كشيء يجب عليك الاشتراك فيه ، مع احتفال إضافي. ولكن ، من واقع خبرتي ، فإن ذلك يعود بالضبط إلى الممارسات الجيدة. آمل أن يسهّل الاقتراح في هذا الفضاء ، أو على الأقل ليس أكثر صعوبة ، إضافة معلومات سياقية إلى الأخطاء في موقع الخطأ.

ربما يمكننا إضافة متغير مع وظيفة التعزيز الاختيارية شيء مثل tryf بهذه الدلالات:

func tryf(t1 T1, t1 T2, … tn Tn, te error, fn func(error) error) (T1, T2, … Tn)

يترجم هذا

x1, x2, … xn = tryf(f(), func(err error) { return fmt.Errorf("foobar: %q", err) })

في هذا

t1, … tn, te := f()
if te != nil {
    if fn != nil {
        te = fn(te)
    }
    err = te
    return
}

نظرًا لأن هذا اختيار صريح (بدلاً من استخدام try ) ، يمكننا العثور على إجابات معقولة للأسئلة في الإصدار السابق من هذا التصميم. على سبيل المثال ، إذا كانت وظيفة الزيادة لا شيء ، فلا تفعل أي شيء وقم بإرجاع الخطأ الأصلي فقط.

أشعر بالقلق من أن try سيحل محل معالجة الأخطاء التقليدية ، وأن ذلك سيجعل كتابة مسارات الخطأ أكثر صعوبة نتيجة لذلك.

سيتم اعتبار الكود الذي يعالج الأخطاء عن طريق تسجيل الرسائل وتحديث عدادات القياس عن بُعد معيبًا أو غير لائق من قِبل كل من linters والمطورين الذين يتوقعون try كل شيء.

a, b, err := doWork()
if err != nil {
  updateCounters()
  writeLogs()
  return err
}

Go هي لغة اجتماعية للغاية لها مصطلحات شائعة يتم فرضها بواسطة الأدوات (fmt ، lint ، إلخ). يرجى مراعاة التداعيات الاجتماعية لهذه الفكرة - سيكون هناك ميل لاستخدامها في كل مكان.

politician ، آسف ، لكن الكلمة التي تبحث عنها ليست _ اجتماعية_ بل _مفتوحة_. Go هي لغة برمجة لها رأي. بالنسبة للباقي أتفق في الغالب مع ما تحصل عليه.

تُظهر أدوات مجتمع beoran مثل Godep و linters المختلفة أن Go لها رأي واجتماعي على حد سواء ، والعديد من الأعمال الدرامية مع اللغة تنبع من هذا المزيج. نأمل أن نتفق على أن try لا ينبغي أن يكون الدراما التالية.

olitician شكرًا للتوضيح ، لم أفهم الأمر بهذه الطريقة. يمكنني بالتأكيد أن أتفق على أنه يجب علينا محاولة تجنب الدراما.

أنا في حيرة من أمري حيال ذلك.

من المدونة: الأخطاء هي قيم ، من وجهة نظري ، فهي مصممة بحيث لا يتم تجاهلها.

وأنا أصدق ما قاله روب بايك ، "يمكن برمجة القيم ، وبما أن الأخطاء هي قيم ، يمكن برمجة الأخطاء.".

يجب ألا نعتبر error على أنه exception ، إنه مثل استيراد التعقيد ليس فقط للتفكير ولكن أيضًا للترميز إذا قمنا بذلك.

"استخدم اللغة لتبسيط معالجة الأخطاء." - روب بايك

وأكثر من ذلك ، يمكننا مراجعة هذه الشريحة

image

من المواقف التي أجد فيها التحقق من الأخطاء عبر if أمرًا محرجًا بشكل خاص عند إغلاق الملفات (على سبيل المثال على NFS). أعتقد ، نحن حاليًا نهدف إلى كتابة ما يلي ، إذا كان الخطأ يعود من .Close() ممكن؟

r, err := os.Open(src)
if err != nil {
    return err
}
defer func() {
    // maybe check whether a previous error occured?
    return r.Close()
}()

هل يمكن أن يكون defer try(r.Close()) طريقة جيدة للحصول على صيغة يمكن إدارتها لطريقة ما للتعامل مع مثل هذه الأخطاء؟ على الأقل ، سيكون من المنطقي تعديل مثال CopyFile() في الاقتراح بطريقة ما ، لعدم تجاهل الأخطاء من r.Close() و w.Close() .

seehuhn لن يتم ترجمة المثال الخاص بك لأن الدالة المؤجلة لا تحتوي على نوع إرجاع.

func doWork() (err error) {
  r, err := os.Open(src)
  if err != nil {
    return err
  }
  defer func() {
    err = r.Close()  // overwrite the return value
  }()
}

سوف تعمل كما تتوقع. المفتاح هو قيمة الإرجاع المسماة.

يعجبني الاقتراح لكني أعتقد أنه يجب إضافة عنوانseehuhn أيضًا:

defer try(w.Close())

سيعيد الخطأ من Close () فقط إذا لم يكن الخطأ قد تم تعيينه بالفعل.
يستخدم هذا النمط في كثير من الأحيان ...

أنا أتفق مع المخاوف المتعلقة بإضافة سياق إلى الأخطاء. أرى أنه أحد أفضل الممارسات التي تجعل رسائل الخطأ ودودة (وواضحة) وتجعل عملية تصحيح الأخطاء أسهل.

أول شيء فكرت فيه هو استبدال fmt.HandleErrorf # $ بوظيفة tryf ، والتي تسبق الخطأ بسياق إضافي.

func tryf(t1 T1, t1 T2, … tn Tn, te error, ts string) (T1, T2, … Tn)

على سبيل المثال (من رمز حقيقي لدي):

func (c *Config) Build() error {
    pkgPath, err := c.load()
    if err != nil {
        return nil, errors.WithMessage(err, "load config dir")
    }
    b := bytes.NewBuffer(nil)
    if err = templates.ExecuteTemplate(b, "main", c); err != nil {
        return nil, errors.WithMessage(err, "execute main template")
    }
    buf, err := format.Source(b.Bytes())
    if err != nil {
        return nil, errors.WithMessage(err, "format main template")
    }
    target := fmt.Sprintf("%s.go", filename(pkgPath))
    if err := ioutil.WriteFile(target, buf, 0644); err != nil {
        return nil, errors.WithMessagef(err, "write file %s", target)
    }
    // ...
}

يمكن تغييرها إلى شيء مثل:

func (c *Config) Build() error {
    pkgPath := tryf(c.load(), "load config dir")
    b := bytes.NewBuffer(nil)
    tryf(emplates.ExecuteTemplate(b, "main", c), "execute main template")
    buf := tryf(format.Source(b.Bytes()), "format main template")
    target := fmt.Sprintf("%s.go", filename(pkgPath))
    tryf(ioutil.WriteFile(target, buf, 0644), fmt.Sprintf("write file %s", target))
    // ...
}

أو ، إذا أخذت مثالagnivade :

func (p *pgStore) DoWork() (err error) {
    tx := tryf(p.handle.Begin(), "begin transaction")
        defer func() {
        if err != nil {
            tx.Rollback()
        }
    }()
    var res int64
    tryf(tx.QueryRow(`INSERT INTO table (...) RETURNING c1`, ...).Scan(&res), "insert table")
    _, = tryf(tx.Exec(`INSERT INTO table2 (...) VALUES ($1)`, res), "insert table2")
    return tryf(tx.Commit(), "commit transaction")
}

ومع ذلك ، أثار josharian نقطة جيدة تجعلني أتردد في هذا الحل:

كما هو مكتوب ، فإنه ينقل تنسيق fmt من حزمة إلى اللغة نفسها ، مما يفتح علبة من الديدان.

أنا موافق تمامًا على هذا الاقتراح ويمكنني رؤية فوائده عبر عدد من الأمثلة.

اهتمامي الوحيد بالاقتراح هو تسمية try ، أشعر أن دلالاته مع اللغات الأخرى ، قد تحرف تصورات deveopers لما هو الغرض منه عندما يأتي من لغات أخرى. تأتي جافا لتجد هنا.

بالنسبة لي ، أفضل أن يُطلق على المضمن اسم pass . أشعر أن هذا يعطي تمثيلًا أفضل لما يحدث. بعد كل شيء ، أنت لا تتعامل مع الخطأ - بدلاً من إعادته ليتم التعامل معه من قبل المتصل. يعطي try الانطباع بأنه تم معالجة الخطأ.

إنها إهانة مني ، ويرجع ذلك أساسًا إلى أن المشكلة التي تهدف إلى معالجتها ("النموذج المعياري إذا كانت العبارات المرتبطة عادةً بمعالجة الأخطاء") ليست مشكلة بالنسبة لي. إذا كانت جميع عمليات التحقق من الأخطاء ببساطة if err != nil { return err } ، فيمكنني أن أرى بعض القيمة في إضافة السكر النحوي لذلك (على الرغم من أن Go هي لغة خالية من السكر نسبيًا من خلال الميل).

في الواقع ، ما أريد القيام به في حالة حدوث خطأ غير معدوم يختلف اختلافًا كبيرًا من حالة إلى أخرى. ربما أريد أن t.Fatal(err) . ربما أريد إضافة رسالة تزيين return fmt.Sprintf("oh no: %v", err) . ربما قمت فقط بتسجيل الخطأ والمتابعة. ربما قمت بتعيين علامة خطأ على كائن SafeWriter الخاص بي والمتابعة ، والتحقق من العلامة في نهاية بعض تسلسل العمليات. ربما أحتاج إلى اتخاذ بعض الإجراءات الأخرى. لا يمكن أتمتة أي من هذه باستخدام try . لذا ، إذا كانت حجة try هي أنها ستزيل كل الكتل if err != nil ، فإن هذه الوسيطة لن تصمد.

هل ستقضي على بعض منها؟ بالتأكيد. هل هذا عرض جذاب بالنسبة لي؟ مه. أنا حقا لست قلقا. بالنسبة لي ، if err != nil هو مجرد جزء من Go ، مثل الأقواس المتعرجة ، أو defer . أتفهم أن الأمر يبدو مطولًا ومتكررًا للأشخاص الجدد على Go ، لكن الأشخاص الجدد في Go ليسوا في أفضل وضع لإجراء تغييرات جذرية على اللغة ، لمجموعة كاملة من الأسباب.

كان شريط التغييرات المهمة على Go تقليديًا هو أن التغيير المقترح يجب أن يحل مشكلة (أ) مهمة ، (ب) تؤثر على الكثير من الناس ، و (ج) تم حلها جيدًا من خلال الاقتراح. لست مقتنعًا بأي من هذه المعايير الثلاثة. أنا سعيد جدًا بمعالجة أخطاء Go كما هي.

لتكرار صدى peterbourgon و deanveloper ، فإن أحد الأشياء المفضلة لدي في Go هو أن تدفق الشفرة واضح ولا يتم التعامل مع الذعر () كآلية قياسية للتحكم في التدفق كما هي في Python.

فيما يتعلق بالنقاش حول الذعر ، فإن الذعر () دائمًا ما يظهر بمفرده على الخط لأنه لا قيمة له. لا يمكنك fmt.Println(panic("oops")) . هذا يزيد من ظهورها بشكل كبير ويجعلها أقل قابلية للمقارنة بـ try() مما يفعله الناس.

إذا كان هناك بنية أخرى للتحكم في التدفق للوظائف ، فسأفضل _far_ أن تكون عبارة مضمونة لتكون العنصر الموجود في أقصى اليسار في السطر.

أحد الأمثلة في الاقتراح يبرز المشكلة بالنسبة لي:

func printSum(a, b string) error {
        fmt.Println(
                "result:",
                try(strconv.Atoi(a)) + try(strconv.Atoi(b)),
        )
        return nil
}

يصبح تدفق التحكم أقل وضوحًا وغموضًا جدًا.

هذا أيضًا ضد النية الأولية لروب بايك بأن جميع الأخطاء تحتاج إلى معالجة صريحة.

في حين أن رد الفعل على هذا يمكن أن يكون "ثم لا تستخدمه" ، فإن المشكلة هي - المكتبات الأخرى سوف تستخدمه ، وتصحيح الأخطاء وقراءتها واستخدامها ، يصبح أكثر إشكالية. سيحفز هذا شركتي على عدم تبني go 2 مطلقًا ، والبدء في استخدام المكتبات فقط التي لا تستخدم try . إذا لم أكن وحدي مع هذا ، فقد يؤدي ذلك إلى تقسيم a-la python 2/3.

أيضًا ، تسمية try ستشير تلقائيًا إلى أن catch سيظهر في النهاية في بناء الجملة ، وسنعود إلى Java.

لذا ، وبسبب كل هذا ، أنا أعارض بشدة هذا الاقتراح.

لا يعجبني اسم try . إنه يعني محاولة _ في القيام بشيء ينطوي على مخاطر عالية للفشل (قد يكون لدي تحيز ثقافي ضد _try_ لأنني لست متحدثًا باللغة الإنجليزية) ، بينما سيتم استخدام try بدلاً من ذلك في حالة توقعنا لإخفاقات نادرة (الدافع وراء الرغبة في تقليل الإسهاب في معالجة الأخطاء) وهم متفائلون. بالإضافة إلى أن try في هذا الاقتراح يؤدي في الواقع _catches_ إلى حدوث خطأ لإعادته مبكرًا. يعجبني اقتراح pass الخاص بـHiImJC.

إلى جانب الاسم ، أجد أنه من المحرج أن يكون عبارة return -like مخفية الآن في منتصف التعبيرات. هذا يكسر نمط التدفق. سيجعل مراجعات الكود أكثر صعوبة.

بشكل عام ، أجد أن هذا الاقتراح لن يفيد إلا المبرمج الكسول الذي لديه الآن سلاح لرمز أقصر وسبب أقل لبذل جهد التفاف الأخطاء. نظرًا لأنه سيجعل المراجعات أكثر صعوبة (العودة في منتصف التعبير) ، أعتقد أن هذا الاقتراح يتعارض مع هدف Go "البرمجة على نطاق واسع".

من الأشياء المفضلة لدي في Go والتي أقولها عمومًا عند وصف اللغة هو أن هناك طريقة واحدة فقط للقيام بالأشياء ، لمعظم الأشياء. يتعارض هذا الاقتراح مع هذا المبدأ قليلاً من خلال تقديم طرق متعددة لفعل الشيء نفسه. أنا شخصياً أعتقد أن هذا ليس ضروريًا وأنه سيأخذ ، بدلاً من أن يضيف إلى بساطة اللغة وقابليتها للقراءة.

أنا أحب هذا الاقتراح بشكل عام. يبدو أن التفاعل مع defer كافٍ لتوفير طريقة مريحة لإرجاع خطأ أثناء إضافة سياق إضافي أيضًا. على الرغم من أنه سيكون من الجيد معالجة المشكلة التي أشار إليها josharian حول كيفية تضمين الخطأ الأصلي في رسالة الخطأ المغلفة.

ما ينقص هو طريقة مريحة للتفاعل مع اقتراح (مقترحات) فحص الأخطاء على الطاولة. أعتقد أن واجهات برمجة التطبيقات (API) يجب أن تكون متعمدة للغاية فيما يتعلق بأنواع الأخطاء التي تعرضها ، ومن المحتمل أن يكون الخطأ الافتراضي هو "الأخطاء المرتجعة غير القابلة للفحص بأي شكل من الأشكال". يجب أن يكون من السهل بعد ذلك الانتقال إلى حالة يمكن فيها فحص الأخطاء بطريقة دقيقة ، كما هو موثق في توقيع الوظيفة ("يُبلغ عن خطأ من النوع X في الظرف A وخطأ من النوع Y في الظرف B").

لسوء الحظ ، اعتبارًا من الآن ، يجعل هذا الاقتراح الخيار الأكثر راحة هو أكثر الخيارات غير المرغوب فيها (بالنسبة لي) ؛ تمر بشكل أعمى من خلال أنواع الخطأ التعسفي. أعتقد أن هذا غير مرغوب فيه لأنه يشجع على عدم التفكير في أنواع الأخطاء التي ترجعها وكيف سيستهلكها مستخدمو واجهة برمجة التطبيقات الخاصة بك. من المؤكد أن الراحة الإضافية لهذا الاقتراح لطيفة ، لكنني أخشى أنها ستشجع السلوك السيئ لأن الراحة المتصورة ستفوق القيمة المتصورة للتفكير بعناية في معلومات الخطأ التي تقدمها (أو التسريب).

قد يكون الشريط الجانبي إذا تم تحويل الأخطاء التي تم إرجاعها بواسطة try إلى أخطاء ليست "غير قابلة للإصلاح". لسوء الحظ ، فإن هذا له جوانب سلبية شديدة أيضًا ، لأنه يجعل الأمر كذلك بحيث لا يتمكن أي defer من فحص الأخطاء نفسها. بالإضافة إلى ذلك ، يمنع الاستخدام حيث يعرض try خطأ من النوع المرغوب فيه (أي حالات الاستخدام حيث يتم استخدام try بعناية بدلاً من الإهمال).

قد يكون الحل الآخر هو إعادة توظيف الفكرة (المهملة) الخاصة بالحصول على وسيطة ثانية اختيارية إلى try لتعريف / إدراج نوع (أنواع) الخطأ الذي يمكن إرجاعه من هذا الموقع في القائمة البيضاء. هذا مزعج بعض الشيء لأن لدينا طريقتين مختلفتين لتعريف "نوع الخطأ" ، إما بالقيمة ( io.EOF إلخ) أو حسب النوع ( *os.PathError ، *exec.ExitError ). من السهل تحديد أنواع الأخطاء التي تكون قيمًا كوسيطات لدالة ، ولكن يصعب تحديد الأنواع. لست متأكدًا من كيفية التعامل مع ذلك ، ولكن طرح الفكرة هناك.

يمكن تجنب المشكلة التي أشار إليها josharian عن طريق تأخير تقييم الخطأ:

defer func() { fmt.HandleErrorf(&err, "oops: %v", err) }()

لا يبدو رائعًا ، لكن يجب أن يعمل. لكنني أفضل ما إذا كان من الممكن معالجة ذلك عن طريق إضافة فعل / علامة تنسيق جديدة لمؤشرات الخطأ ، أو ربما للمؤشرات بشكل عام ، والتي تطبع القيمة غير المرجعية كما هو الحال مع %v . لغرض المثال ، دعنا نسميه %*v :

defer fmt.HandleErrorf(&err, "oops: %*v", &err)

بغض النظر عن العقبة ، أعتقد أن هذا الاقتراح يبدو واعدًا ، لكن يبدو أنه من المهم الحفاظ على بيئة العمل لإضافة سياق إلى الأخطاء تحت المراقبة.

يحرر:

هناك طريقة أخرى تتمثل في التفاف مؤشر الخطأ في بنية تنفذ Stringer :

type wraperr struct{ err *error }
func (w wraperr) String() string { return (*w.err).Error() }

...

defer handleErrorf(&err, "oops: %v", wraperr{&err})

زوجان من الأشياء من وجهة نظري. لماذا نحن مهتمون جدًا بحفظ بضعة أسطر من التعليمات البرمجية؟ أنا أعتبر هذا على غرار نفس الدوال الصغيرة التي تعتبر ضارة .

بالإضافة إلى ذلك ، أجد أن مثل هذا الاقتراح من شأنه أن يزيل مسؤولية التعامل مع الخطأ بشكل صحيح إلى بعض "السحر" الذي أخشى أنه سيتم إساءة استخدامه ويشجع الكسل مما يؤدي إلى سوء جودة التعليمات البرمجية والأخطاء.

يحتوي الاقتراح كما هو مذكور أيضًا على عدد من السلوكيات غير الواضحة ، لذا فإن هذا يمثل مشكلة بالفعل من 3 سطور إضافية أكثر وضوحًا.

نستخدم حاليًا نمط التأجيل باعتدال في المنزل. هناك مقال هنا كان له استقبال مختلط بالمثل عندما كتبناه - https://bet365techblog.com/better-error-handling-in-go

ومع ذلك ، كان استخدامنا له توقعًا لتقدم عرض check / handle .

كان الفحص / المقبض نهجًا أكثر شمولاً لجعل معالجة الأخطاء أكثر إيجازًا. احتفظت كتلتها handle بنفس نطاق الوظيفة الذي تم تحديده فيه ، في حين أن أي عبارات defer هي سياقات جديدة مع مبلغ ، مهما كان ، من النفقات العامة. يبدو أن هذا يتماشى أكثر مع مصطلحات go's ، من حيث أنك إذا أردت سلوك "أرجع الخطأ عند حدوثه فقط" يمكنك أن تعلن ذلك صراحةً على أنه handle { return err } .

من الواضح أن التأجيل يعتمد على مرجع الخطأ الذي يتم الاحتفاظ به أيضًا ، لكننا رأينا مشكلات تنشأ من حجب مرجع الخطأ باستخدام متغيرات النطاق. لذلك فهو ليس دليلًا خادعًا بما يكفي لاعتباره الطريقة القياسية للتعامل مع الأخطاء أثناء التنقل.

لا يبدو أن try ، في هذه الحالة ، يحل الكثير ، وأنا أشارك الآخرين في الخوف من أنه سيؤدي ببساطة إلى عمليات تنفيذ بطيئة ، أو تلك التي تفرط في استخدام نمط التأجيل.

إذا كانت معالجة الأخطاء القائمة على الإرجاء ستكون شيئًا ، فمن المحتمل أن تتم إضافة شيء كهذا إلى حزمة الأخطاء:

        f := try(os.Create(filename))
        defer errors.Deferred(&err, f.Close)

يعد تجاهل أخطاء بيانات الإغلاق المؤجلة مشكلة شائعة جدًا. يجب أن تكون هناك أداة قياسية للمساعدة في ذلك.

الوظيفة المضمنة التي يتم إرجاعها هي بيع أصعب من الكلمة الرئيسية التي تفعل الشيء نفسه.
أرغب أكثر إذا كانت كلمة رئيسية مثل Zig [1].

  1. https://ziglang.org/documentation/master/#try

الوظائف المدمجة ، التي لا يمكن التعبير عن توقيعها النوعي باستخدام نظام نوع اللغة ، والتي يربك سلوكها ماهية الوظيفة بشكل طبيعي ، تبدو وكأنها فتحة هروب يمكن استخدامها بشكل متكرر لتجنب تطور اللغة الفعلي.

لقد اعتدنا على التعرف على عبارات الإرجاع (والذعر) على الفور لأن هذه هي الطريقة التي يتم بها التعبير عن هذا النوع من تدفق التحكم في Go (والعديد من اللغات الأخرى). يبدو أنه ليس بعيد المنال أننا سوف ندرك أيضًا محاولة تغيير تدفق التحكم بعد أن يعتاد البعض عليها ، تمامًا كما نفعل للعودة. ليس لدي شك في أن دعم IDE الجيد سيساعد في هذا أيضًا.

أعتقد أنه بعيد المنال إلى حد ما. في التعليمات البرمجية gofmt'ed ، يتطابق العائد دائمًا مع /^\t*return / - إنه نمط تافه للغاية يمكن ملاحظته بالعين ، دون أي مساعدة. من ناحية أخرى ، يمكن أن يحدث try في أي مكان في الكود ، متداخلاً بشكل تعسفي في عمق استدعاءات الوظائف. لن يجعلنا أي قدر من التدريب قادرين على تحديد كل تدفق التحكم على الفور في وظيفة بدون مساعدة الأداة.

علاوة على ذلك ، فإن الميزة التي تعتمد على "دعم IDE الجيد" ستكون في وضع غير مؤات في جميع البيئات التي لا يوجد فيها دعم جيد لـ IDE. تتبادر إلى الذهن أدوات مراجعة الكود على الفور - هل سيبرز Gerrit كل المحاولات بالنسبة لي؟ ماذا عن الأشخاص الذين يختارون عدم استخدام IDEs ، أو إبراز رمز خيالي ، لأسباب مختلفة؟ هل سيبدأ Acme في تمييز try ؟

يجب أن تكون ميزة اللغة سهلة الفهم من تلقاء نفسها ، لا تعتمد على دعم المحرر.

kungfusheep أحب هذا المقال. يؤدي الاهتمام بالالتفاف في المؤجل وحده بالفعل إلى زيادة إمكانية القراءة قليلاً بدون try .

أنا في المعسكر ولا أشعر أن الأخطاء في Go تمثل مشكلة حقًا. ومع ذلك ، يمكن أن يكون if err != nil { return err } هو التلعثم تمامًا في بعض الوظائف. لقد كتبت وظائف احتاجت إلى التحقق من الأخطاء بعد كل عبارة تقريبًا ولم يكن أي منها بحاجة إلى أي معالجة خاصة بخلاف الالتفاف والإرجاع. في بعض الأحيان ، لا يوجد أي هيكل عازل ذكي سيجعل الأشياء أجمل. في بعض الأحيان تكون مجرد خطوة حاسمة مختلفة تلو الأخرى وتحتاج ببساطة إلى قصر الدائرة إذا حدث خطأ ما.

على الرغم من أن try سيجعل بالتأكيد هذا الرمز أسهل كثيرًا في القراءة مع كونه متوافقًا تمامًا مع الإصدارات السابقة ، إلا أنني أوافق على أن try ليست ميزة ضرورية ، لذلك إذا كان الناس خائفين جدًا من ربما يكون من الأفضل عدم الحصول عليها.

ومع ذلك ، فإن الدلالات واضحة تمامًا. في أي وقت ترى try إما أنه يتبع المسار السعيد أو يعود. أنا حقا لا أستطيع أن أبسط من ذلك.

هذا يشبه ماكرو بغلاف خاص.

dominikh try يتطابق دائمًا مع /try\(/ لذلك لا أعرف ما هي وجهة نظرك حقًا. إنه قابل للبحث بنفس القدر وكل محرر سمعت عنه لديه ميزة بحث.

qrpnxz أعتقد أن النقطة التي كان يحاول إيضاحها ليست أنه لا يمكنك البحث عنها برمجيًا ، ولكن يصعب البحث عنها بأعينك. كان التعبير العادي مجرد تشبيه ، مع التركيز على /^\t* ، مما يدل على أن جميع المرتجعات تبرز بوضوح من خلال كونها في بداية السطر (تجاهل المسافة البيضاء البادئة).

بالتفكير في الأمر أكثر ، يجب أن يكون هناك وظيفتان مساعدتان مشتركتان. ربما ينبغي أن يكونوا في حزمة تسمى "مؤجلة".

معالجة اقتراح check بالتنسيق لتجنب تسمية المرتجعات ، يمكنك فقط القيام بذلك من خلال وظيفة تتحقق من عدم وجود شيء ، مثل ذلك

func Format(err error, message string, args ...interface{}) error {
    if err == nil {
        return nil
    }
    return fmt.Errorf(...)
}

يمكن استخدام هذا بدون إرجاع مسمى مثل:

func foo(s string) (int, error) {
    n, err := strconv.Atoi(s)
    try(deferred.Format(err, "bad string %q", s))
    return n, nil
}

يمكن وضع fmt.HandleError المقترح في الحزمة المؤجلة بدلاً من ذلك وأخطائي. يمكن تسمية الوظيفة المساعدة المؤجلة deferred.Exec ويمكن أن يكون هناك تنفيذ مشروط للإجراءات ليتم تنفيذها فقط إذا كان الخطأ غير معدوم.

بتجميعها معًا ، تحصل على شيء مثل

func CopyFile(src, dst string) (err error) {
    defer deferred.Annotate(&err, "copy %s %s", src, dst)

    r := try(os.Open(src))
    defer deferred.Exec(&err, r.Close)

    w := try(os.Create(dst))
    defer deferred.Exec(&err, r.Close)

    defer deferred.Cond(&err, func(){ os.Remove(dst) })
    try(io.Copy(w, r))

    return nil
}

مثال آخر:

func (p *pgStore) DoWork() (err error) {
    tx := try(p.handle.Begin())

    defer deferred.Cond(&err, func(){ tx.Rollback() })

    var res int64 
    err = tx.QueryRow(`INSERT INTO table (...) RETURNING c1`, ...).Scan(&res)
    try(deferred.Format(err, "insert table")

    _, err = tx.Exec(`INSERT INTO table2 (...) VALUES ($1)`, res)
    try(deferred.Format(err, "insert table2"))

    return tx.Commit()
}

يأخذنا هذا الاقتراح من وجود if err != nil في كل مكان ، إلى وجود try في كل مكان. إنه يغير المشكلة المقترحة ولا يحلها.

على الرغم من أنني أزعم أن آلية معالجة الأخطاء الحالية ليست مشكلة في البداية. نحتاج فقط إلى تحسين الأدوات والتدقيق حولها.

علاوة على ذلك ، أود أن أزعم أن if err != nil هو في الواقع أكثر قابلية للقراءة من try لأنه لا يفسد سطر لغة منطق الأعمال ، بل يقع أسفلها مباشرة:

file := try(os.OpenFile("thing")) // less readable than, 

file, err := os.OpenFile("thing")
if err != nil {

}

وإذا كان Go سيصبح أكثر سحراً في التعامل مع الأخطاء ، فلماذا لا يمتلكه بالكامل. على سبيل المثال ، يمكن لـ Go استدعاء ضمنيًا try إذا لم يقم المستخدم بتعيين خطأ. علي سبيل المثال:

func getString() (string, error) { ... }

func caller() {
  defer func() {
    if err != nil { ... } // whether `err` must be defined or not is not shown in this example. 
  }

  // would call try internally, because a user is not 
  // assigning an error value. Also, it can add a compile error
  // for "defined and not used err value" if the user does not 
  // handle the error. 
  str := getString()
}

بالنسبة لي ، سيؤدي ذلك في الواقع إلى مشكلة التكرار على حساب السحر وإمكانية القراءة.

لذلك ، أقترح إما أن نحل `` المشكلة '' حقًا كما في المثال أعلاه أو نحافظ على معالجة الخطأ الحالية ولكن بدلاً من تغيير اللغة لحل التكرار والالتفاف ، لا نغير اللغة ولكننا نحسن الأدوات والتدقيق من التعليمات البرمجية لجعل التجربة أفضل.

على سبيل المثال ، في VSCode هناك مقتطف يسمى iferr إذا قمت بكتابته وضغطت على إدخال ، فإنه يتوسع إلى بيان معالجة الأخطاء بالكامل ... لذلك ، كتابته لا أشعر بالملل بالنسبة لي ، والقراءة لاحقًا أفضل .

تضمين التغريدة

على الرغم من أنه لن يكون "تغييرًا متواضعًا في المكتبة" ، إلا أنه يمكننا التفكير في قبول خطأ func main () أيضًا.

المشكلة في ذلك هي أنه ليست كل المنصات لديها دلالات واضحة حول ما يعنيه ذلك. تعمل إعادة الكتابة بشكل جيد في برامج Go "التقليدية" التي تعمل على نظام تشغيل كامل - ولكن بمجرد كتابة برنامج ثابت للمتحكم الدقيق أو حتى WebAssembly ، ليس من الواضح تمامًا ما يعنيه os.Exit(1) . حاليًا ، os.Exit عبارة عن مكالمة مكتبة ، لذا فإن تطبيقات Go مجانية فقط لا توفرها. شكل main هو مصدر قلق لغوي بالرغم من ذلك.


سؤال حول العرض ربما يكون أفضل إجابة له هو "لا": كيف يتفاعل try مع المتغيرات؟ إنها الحالة الأولى لدالة متغيرة (ish) لا تحتوي على متغيراتها في الوسيطة الأخيرة. هل هذا مسموح:

var e []error
try(e...)

ترك جانبا لماذا تفعل ذلك من أي وقت مضى. أظن أن الإجابة هي "لا" (وإلا فإن المتابعة هي "ماذا لو كان طول الشريحة الموسعة صفرًا). فقط قم بإحضار ذلك حتى يمكن وضعه في الاعتبار عند صياغة المواصفات في النهاية.

  • العديد من أعظم الميزات قيد التنفيذ هي أن العناصر المضمنة الحالية تضمن تدفقًا واضحًا للتحكم ، وأن معالجة الأخطاء واضحة ومُشجعة ، ويتم ثني المطورين بشدة عن كتابة التعليمات البرمجية "السحرية". لا يتوافق عرض try مع هذه المبادئ الأساسية ، حيث سيعزز الاختزال على حساب إمكانية قراءة تدفق التحكم.
  • إذا تم اعتماد هذا الاقتراح ، فربما يجب التفكير في إنشاء عبارة مضمنة في try بدلاً من دالة . ثم يكون أكثر اتساقًا مع عبارات التحكم في التدفق الأخرى مثل if . بالإضافة إلى ذلك ، تؤدي إزالة الأقواس المتداخلة إلى تحسين إمكانية القراءة بشكل هامشي.
  • مرة أخرى ، إذا تم اعتماد الاقتراح ، فربما يتم تنفيذه دون استخدام defer أو ما شابه. لا يمكن تنفيذه بالفعل في Pure go (كما أشار آخرون) لذلك قد يستخدم أيضًا تنفيذًا أكثر كفاءة تحت الغطاء.

أرى مشكلتين في هذا:

  1. يضع الكثير من التعليمات البرمجية المتداخلة داخل الوظائف. هذا يضيف الكثير من العبء المعرفي الإضافي ، في محاولة لتحليل الكود في رأسك.
  1. يعطينا الأماكن التي يمكن أن تخرج فيها الشفرة من منتصف العبارة.

رقم 2 أعتقد أنه أسوأ بكثير. جميع الأمثلة هنا عبارة عن مكالمات بسيطة تُرجع خطأً ، ولكن الأهم من ذلك هو هذا:

func doit(abc string) error {
    a := fmt.Sprintf("value of something: %s\n", try(getValue(abc)))
    log.Println(a)
    return nil
}

يمكن الخروج من هذا الرمز في منتصف هذا السباق ، وسيكون من السهل جدًا تفويت هذه الحقيقة.

تصويتي لا. هذا لن يجعل كود go أفضل. لن تجعلها أسهل للقراءة. لن تجعله أكثر قوة.

لقد قلتها من قبل ، وهذا الاقتراح يمثلها - أشعر أن 90٪ من الشكاوى حول Go هي "لا أريد أن أكتب عبارة if أو حلقة". يؤدي هذا إلى إزالة بعض عبارات if البسيطة جدًا ، ولكنه يضيف عبئًا معرفيًا ويجعل من السهل تفويت نقاط الخروج لوظيفة ما.

أريد فقط أن أشير إلى أنه لا يمكنك استخدام هذا بشكل رئيسي وقد يكون مربكًا للمستخدمين الجدد أو عند التدريس. من الواضح أن هذا ينطبق على أي وظيفة لا تُرجع خطأ ولكن أعتقد أن main هي وظيفة خاصة لأنها تظهر في العديد من الأمثلة ..

func main() {
    f := try(os.Open("foo.txt"))
    defer f.Close()
}

لست متأكدًا من قبول تجربة الذعر بشكل رئيسي أيضًا.

بالإضافة إلى ذلك ، لن يكون مفيدًا بشكل خاص في الاختبارات ( func TestFoo(t* testing.T) ) وهو أمر مؤسف :(

المشكلة التي أواجهها هي أنها تفترض أنك تريد دائمًا إرجاع الخطأ عند حدوثه. عندما ترغب في إضافة سياق إلى الخطأ وإعادته أو ربما تريد فقط التصرف بشكل مختلف عند حدوث خطأ. ربما هذا يعتمد على نوع الخطأ الذي تم إرجاعه.

أفضل شيئًا يشبه المحاولة / الصيد الذي قد يبدو

بافتراض أن foo() معرف كـ

func foo() (int, error) {}

يمكنك بعد ذلك أن تفعل

n := try(foo()) {
    case FirstError:
        // do something based on FirstError
    case OtherError:
        // do something based on OtherError
    default:
        // default behavior for any other error
}

الذي يترجم إلى

n, err := foo()
if errors.Is(err, FirstError) {
    // do something based on FirstError
if errors.Is(err, OtherError) {
    // do something based on OtherError
} else {
    // default behavior for any other error
}

بالنسبة لي ، يعد التعامل مع الأخطاء أحد أهم أجزاء قاعدة التعليمات البرمجية.
بالفعل الكثير من كود go هو if err != nil { return err } ، ويعيد خطأ من أعماق المكدس دون إضافة سياق إضافي ، أو حتى (ربما) أسوأ إضافة سياق عن طريق إخفاء الخطأ الأساسي مع التفاف fmt.Errorf .

يبدو أن توفير كلمة رئيسية جديدة من نوع السحر لا تفعل شيئًا سوى استبدال if err != nil { return err } طريقًا خطيرًا.
الآن سيتم تغليف كل التعليمات البرمجية في مكالمة لتجربتها. هذا جيد إلى حد ما (على الرغم من أن سهولة القراءة) للشفرة التي تتعامل فقط مع أخطاء داخل الحزمة مثل:

func foo() error {
  /// stuff
  try(bar())
  // more stuff
}

لكنني أزعم أن المثال المعطى مروّع نوعًا ما ويترك المتصل يحاول فهم خطأ عميق حقًا في المكدس ، مثل معالجة الاستثناءات إلى حد كبير.
بالطبع ، كل هذا متروك للمطور للقيام بالشيء الصحيح هنا ، لكنه يمنح المطور طريقة رائعة لعدم الاهتمام بأخطائه مع ربما "سنصلح هذا لاحقًا" (ونعلم جميعًا كيف ستسير الأمور ).

أتمنى أن ننظر إلى المشكلة من منظور مختلف عن * "كيف يمكننا تقليل التكرار" والمزيد حول "كيف يمكننا جعل معالجة الأخطاء (المناسبة) أبسط والمطورين أكثر إنتاجية".
يجب أن نفكر في كيفية تأثير ذلك على تشغيل كود الإنتاج.

* ملاحظة: هذا لا يقلل التكرار في الواقع ، فقط يغير ما يتم تكراره ، مع جعل الكود أقل قابلية للقراءة لأن كل شيء مغلف بـ try() .

نقطة أخيرة: قراءة الاقتراح في البداية يبدو لطيفًا ، ثم تبدأ في الدخول في جميع المشاكل (على الأقل تلك المدرجة) وهي مثل "حسنًا ، هذا كثير جدًا".


أدرك أن الكثير من هذا أمر شخصي ، لكنه شيء أهتم به. هذه الدلالات مهمة للغاية.
ما أريد رؤيته هو طريقة لجعل كتابة رمز مستوى الإنتاج والحفاظ عليه أبسط بحيث يمكنك أيضًا ارتكاب أخطاء "صحيحة" حتى بالنسبة لكود مستوى POC / العرض التوضيحي.

نظرًا لأن سياق الخطأ يبدو أنه سمة متكررة ...

الفرضية: تُرجع معظم دالات Go (T, error) مقابل (T1, T2, T3, error)

ماذا لو ، بدلاً من تعريف try على أنه try(T1, T2, T3, error) (T1, T2, T3) قمنا بتعريفه على أنه
try(func (args) (T1, T2, T3, error))(T1, T2, T3) ؟ (هذا تقدير تقريبي)

وهو ما يعني أن البنية النحوية لاستدعاء try هي دائمًا الوسيطة الأولى التي تمثل تعبيرًا يُرجع قيمًا متعددة ، وآخرها خطأ.

بعد ذلك ، مثل make ، يفتح هذا الباب لصيغة مكونة من وسيطتين للمكالمة ، حيث تكون الوسيطة الثانية هي سياق المحاولة (على سبيل المثال ، سلسلة ثابتة ، سلسلة بها %v ، دالة تأخذ وسيطة خطأ وتعيد خطأ آخر وما إلى ذلك.)

لا يزال هذا يسمح بالتسلسل لحالة (T, error) ولكن لم يعد بإمكانك سلسلة عمليات إرجاع متعددة والتي لا تكون IMO مطلوبة عادةً.

@ cpuguy83 إذا قرأت الاقتراح فسترى أنه لا يوجد شيء يمنعك من تغليف الخطأ. في الواقع ، هناك طرق متعددة للقيام بذلك مع الاستمرار في استخدام try . يبدو أن الكثير من الناس يفترضون ذلك لسبب ما.

if err != nil { return err } متساوي مع عبارة "سنصلح هذا لاحقًا" مثل try باستثناء المزيد من الإزعاج عند إنشاء النماذج الأولية.

لا أعرف كيف أن وجود الأشياء داخل زوج من الأقواس يكون أقل قابلية للقراءة من خطوات الوظيفة التي تكون كل أربعة أسطر من النموذج المعياري أيضًا.

سيكون من الجيد أن أشرت إلى بعض "المشاكل" المعينة التي أزعجتك لأن هذا هو الموضوع.

يبدو أن قابلية القراءة تمثل مشكلة ولكن ماذا عن تقديم try () بحيث تبرز ، شيء مثل:

f := try(
    os.Open("file.txt")
)

تضمين التغريدة

المشكلة التي أواجهها هي أنها تفترض أنك تريد دائمًا إرجاع الخطأ عند حدوثه.

أنا أعترض. يفترض أنك تريد أن تفعل ذلك في كثير من الأحيان بما يكفي لتبرير اختصار لذلك. إذا لم تقم بذلك ، فلن يعيقك التعامل مع الأخطاء بشكل واضح.

عندما ترغب في إضافة سياق إلى الخطأ وإعادته أو ربما تريد فقط التصرف بشكل مختلف عند حدوث خطأ.

يصف الاقتراح نمطًا لإضافة سياق على مستوى الكتلة إلى الأخطاء. أشار josharian إلى وجود خطأ في الأمثلة ، رغم أنه ليس من الواضح ما هي أفضل طريقة لتجنبه. لقد كتبت بعض الأمثلة على طرق التعامل معها.

للحصول على سياق خطأ أكثر تحديدًا ، مرة أخرى ، يقوم try بعمل شيء ما ، وإذا كنت لا تريد هذا الشيء ، فلا تستخدم try .

boomlinde بالضبط وجهة نظري. يحاول هذا الاقتراح حل حالة استخدام فردية بدلاً من توفير أداة لحل المشكلة الأكبر المتعلقة بمعالجة الأخطاء. أعتقد أن السؤال الأساسي هو بالضبط ما أشرت إليه.

يفترض أنك تريد أن تفعل ذلك في كثير من الأحيان بما يكفي لتبرير اختصار لذلك.

في رأيي وخبرتي ، حالة الاستخدام هذه هي أقلية صغيرة ولا تستدعي صياغة مختصرة.

كما أن أسلوب استخدام defer للتعامل مع الأخطاء به مشكلات من حيث أنه يفترض أنك تريد التعامل مع جميع الأخطاء المحتملة بنفس الطريقة. لا يمكن إلغاء كشف defer .

defer fmt.HandleErrorf(&err, “foobar”)

n := try(foo())

x : try(foo2())

ماذا لو أردت معالجة أخطاء مختلفة للأخطاء التي قد يتم إرجاعها من foo() مقابل foo2() ؟

تضمين التغريدة

ماذا لو أردت معالجة أخطاء مختلفة للأخطاء التي قد يتم إرجاعها من foo () مقابل foo2 ()؟

ثم تستخدم شيئًا آخر. هذه هي النقطة التي أثارهاboomlinde .

ربما لا ترى حالة الاستخدام هذه شخصيًا في كثير من الأحيان ، ولكن العديد من الأشخاص يفعلون ذلك ، وإضافة try لا يؤثر عليك حقًا. في الواقع ، كلما كانت حالة الاستخدام أندر بالنسبة لك كلما قل تأثيرها عليك حيث تمت إضافة try .

تضمين التغريدة

f := try(os.Open("/foo"))
data := try(ioutil.ReadAll(f))
try(send(data))

(نعم أفهم أن هناك ReadFile وأن هذا المثال بالذات ليس أفضل طريقة لنسخ البيانات في مكان ما ، وليس النقطة)

يتطلب هذا مزيدًا من الجهد للقراءة لأنه يتعين عليك تحليل المحاولة المضمنة. يتم تغليف منطق التطبيق في مكالمة أخرى.
أود أن أزعم أيضًا أن معالج الأخطاء defer هنا لن يكون جيدًا باستثناء مجرد التفاف الخطأ برسالة جديدة ... وهو أمر لطيف ولكن هناك المزيد للتعامل مع الأخطاء أكثر من تسهيل الأمر على الإنسان لقراءة ما حدث.

في حالة الصدأ ، يكون العامل على الأقل هو postfix (تمت إضافة ? إلى نهاية المكالمة) والذي لا يضع عبئًا إضافيًا لاستكشاف المنطق الفعلي.

التحكم في التدفق القائم على التعبير

قد تكون panic وظيفة أخرى للتحكم في التدفق ، لكنها لا تُرجع قيمة ، مما يجعلها بيانًا فعالاً. قارن هذا بـ try ، وهو تعبير يمكن أن يحدث في أي مكان.

recover له قيمة ويؤثر على التحكم في التدفق ، ولكن يجب أن يحدث في بيان defer . هذه defer s عادةً ما تكون حرفية وظيفية ، recover يُطلق عليها مرة واحدة فقط ، وبالتالي فإن recover يحدث أيضًا بشكل فعال كإفادة. مرة أخرى ، قارن هذا بـ try والذي يمكن أن يحدث في أي مكان.

أعتقد أن هذه النقاط تعني أن try يجعل من الصعب بشكل كبير متابعة تدفق التحكم بطريقة لم نقم بها من قبل ، كما أشرنا من قبل ، لكنني لم أر الفرق بين العبارات والتعبيرات أشار.


اقتراح آخر

السماح بعبارات مثل

if err != nil {
    return nil, 0, err
}

ليتم تنسيقها على سطر واحد بواسطة gofmt عندما تحتوي الكتلة فقط على عبارة return ولا تحتوي هذه العبارة على أسطر جديدة. علي سبيل المثال:

if err != nil { return nil, 0, err }

المنطق

  • لا يتطلب أي تغييرات لغوية
  • قاعدة التنسيق بسيطة وواضحة
  • يمكن تصميم القاعدة للاشتراك حيث يحتفظ gofmt بأسطر جديدة إذا كانت موجودة بالفعل (مثل البنية الحرفية). يسمح الاشتراك أيضًا للكاتب بالتركيز على بعض معالجة الأخطاء
  • إذا لم يتم الاشتراك ، فيمكن نقل الكود تلقائيًا إلى النمط الجديد باستدعاء gofmt
  • إنها فقط لكشوفات الحساب return ، لذلك لن يتم إساءة استخدامها في رمز الجولف دون داع
  • يتفاعل جيدًا مع التعليقات التي تصف سبب حدوث بعض الأخطاء وسبب إعادتها. يؤدي استخدام العديد من التعبيرات المتداخلة try معالجة هذا الأمر بشكل سيء
  • يقلل المساحة الرأسية لمعالجة الأخطاء بنسبة 66٪
  • لا يوجد تدفق تحكم قائم على التعبير
  • تتم قراءة الكود أكثر بكثير مما هو مكتوب ، لذلك يجب تحسينه للقارئ. الكود المتكرر الذي يشغل مساحة أقل مفيد للقارئ ، حيث يميل try أكثر نحو الكاتب
  • لقد اقترح الأشخاص بالفعل try الموجود في أسطر متعددة. على سبيل المثال هذا التعليق أو هذا التعليق الذي يقدم أسلوبًا مثل
f, err := os.Open(file)
try(maybeWrap(err))
  • يزيل نمط "try on its own line" أي غموض حول القيمة التي يتم إرجاعها err . لذلك ، أظن أن هذا النموذج سيستخدم بشكل شائع. السماح بمحاذاة واحدة إذا كانت الكتل هي نفس الشيء تقريبًا ، باستثناء أنها أيضًا واضحة حول ماهية القيم المرتجعة
  • لا يروج لاستخدام المرتجعات المسماة أو التغليف غير الواضح المستند إلى defer . كلاهما يرفع حاجز التفاف الأخطاء ، وقد يتطلب الأول تغيير godoc
  • لا داعي لإجراء مناقشة حول وقت استخدام try مقابل استخدام معالجة الأخطاء التقليدية
  • لا يمنع عمل try أو أي شيء آخر في المستقبل. قد يكون التغيير إيجابيًا حتى إذا تم قبول try
  • لا يوجد تفاعل سلبي مع مكتبة testing أو وظائف main . في الواقع ، إذا كان الاقتراح يسمح بأي جملة مفردة مسطرة بدلاً من مجرد إرجاع ، فقد يقلل من استخدام المكتبات القائمة على التأكيد. انصح
value, err := something()
if err != nil { t.Fatal(err) }
  • لا يوجد تفاعل سلبي مع التحقق من أخطاء معينة. انصح
n, err := src.Read(buf)
if err == io.EOF { return nil }
else if err != nil { return err }

باختصار ، هذا الاقتراح له تكلفة صغيرة ، ويمكن تصميمه ليكون قابلاً للاشتراك ، ولا يستبعد أي تغييرات أخرى لأنه الأسلوب فقط ، ويقلل من ألم قراءة التعليمات البرمجية المطولة لمعالجة الخطأ مع الحفاظ على كل شيء واضحًا. أعتقد أنه يجب اعتبارها على الأقل خطوة أولى قبل الدخول في try .


استدار بعض الأمثلة

من https://github.com/golang/go/issues/32437#issuecomment -498941435

مع المحاولة

func NewThing(thingy *foo.Thingy, db *sql.DB, client pb.Client) (*Thing, error) {
        try(dbfile.RunMigrations(db, dbMigrations))
        t := &Thing{
                thingy:  thingy,
                scanner: try(newScanner(thingy, db, client)),
        }
        t.initOtherThing()
        return t, nil
}

مع هذا

func NewThing(thingy *foo.Thingy, db *sql.DB, client pb.Client) (*Thing, error) {
        err := dbfile.RunMigrations(db, dbMigrations))
        if err != nil { return nil, fmt.Errorf("running migrations: %v", err) }

        t := &Thing{thingy: thingy}
        t.scanner, err = newScanner(thingy, db, client)
        if err != nil { return nil, fmt.Errorf("creating scanner: %v", err) }

        t.initOtherThing()
        return t, nil
}

إنها منافسة في استخدام المساحة بينما لا تزال تسمح بإضافة سياق إلى الأخطاء.

من https://github.com/golang/go/issues/32437#issuecomment -499007288

مع المحاولة

func (c *Config) Build() error {
    pkgPath := try(c.load())
    b := bytes.NewBuffer(nil)
    try(emplates.ExecuteTemplate(b, "main", c))
    buf := try(format.Source(b.Bytes()))
    target := fmt.Sprintf("%s.go", filename(pkgPath))
    try(ioutil.WriteFile(target, buf, 0644))
    // ...
}

مع هذا

func (c *Config) Build() error {
    pkgPath, err := c.load()
    if err != nil { return nil, errors.WithMessage(err, "load config dir") }

    b := bytes.NewBuffer(nil)
    err = templates.ExecuteTemplate(b, "main", c)
    if err != nil { return nil, errors.WithMessage(err, "execute main template") }

    buf, err := format.Source(b.Bytes())
    if err != nil { return nil, errors.WithMessage(err, "format main template") }

    target := fmt.Sprintf("%s.go", filename(pkgPath))
    err = ioutil.WriteFile(target, buf, 0644)
    if err != nil { return nil, errors.WithMessagef(err, "write file %s", target) }
    // ...
}

استخدم التعليق الأصلي tryf افتراضيًا لإرفاق التنسيق الذي تمت إزالته. ليس من الواضح أفضل طريقة لإضافة جميع السياقات المميزة ، وربما لا يمكن تطبيق try حتى.

يارب احفظها
بالنسبة لي هو أكثر قابلية للقراءة مع try . في هذا المثال قرأت "افتح ملفًا ، واقرأ كل البايت ، وأرسل البيانات". من خلال معالجة الأخطاء المعتادة ، سأقرأ "افتح ملفًا ، وتحقق مما إذا كان هناك خطأ ، ومعالجة الخطأ تفعل ذلك ، ثم اقرأ جميع وحدات البايت ، وتحقق الآن مما إذا كانت هناك أشياء قد حدثت ..." أعلم أنه يمكنك إجراء مسح ضوئي من خلال err != nil s ، ولكن بالنسبة لي ، فإن try أسهل فقط لأنني عندما أراه أعرف السلوك على الفور: إرجاع إذا أخطأ! = لا شيء. إذا كان لديك فرع يجب أن أرى ما يفعله. يمكن أن تفعل أي شيء.

أود أن أزعم أيضًا أن معالج تأجيل الخطأ هنا لن يكون جيدًا باستثناء مجرد التفاف الخطأ برسالة جديدة

أنا متأكد من أن هناك أشياء أخرى يمكنك القيام بها في المؤجل ، ولكن بغض النظر ، فإن try مخصص للحالة العامة البسيطة على أي حال. في أي وقت تريد أن تفعل شيئًا أكثر ، هناك دائمًا معالجة جيدة لأخطاء ol 'Go. هذا لن ينتهي.

zeebo Yep ، أنا مع ذلك.
استخدمت مقالة kungfusheep التحقق من الخطأ في سطر واحد مثل هذا وخرجت من تجربته. ثم بمجرد أن أنقذ ، وسعته الحكومة إلى ثلاثة أسطر وهو أمر محزن. يتم تعريف العديد من الوظائف في stdlib في سطر واحد مثل هذا ، لذلك فاجأني أن gofmt ستوسع ذلك.

تضمين التغريدة

لقد قرأت الكثير من كود go. أحد أفضل الأشياء المتعلقة باللغة هي السهولة التي تأتي من معظم التعليمات البرمجية التي تتبع أسلوبًا معينًا (شكرًا gofmt).
لا أريد قراءة مجموعة من الأكواد ملفوفة بـ try(f()) .
هذا يعني أنه سيكون هناك تباعد في أسلوب / ممارسة الكود ، أو نصوص مثل "أوه كان يجب أن تستخدم try() هنا" (وهو الأمر الذي لا أحبه مرة أخرى ، وهذا هو الهدف مني والآخرين الذين علقوا على هذا الاقتراح).

إنه ليس أفضل من الناحية الموضوعية من if err != nil { return err } ، فقط أقل للكتابة.


شيء أخير:

إذا قرأت الاقتراح ، فسترى أنه لا يوجد ما يمنعك من ذلك

هل يمكننا الامتناع عن مثل هذه اللغة من فضلك؟ بالطبع قرأت الاقتراح. لقد صادف أنني قرأته الليلة الماضية ثم علقت هذا الصباح بعد التفكير فيه ولم أشرح التفاصيل الدقيقة لما قصدته.
هذه نبرة عدائية بشكل لا يصدق.

يارب احفظها
رجل وحدة المعالجة المركزية السيئ الخاص بي. لم أقصد ذلك بهذه الطريقة.

وأعتقد أنك يجب أن تشير إلى أن الكود الذي يستخدم try سيبدو مختلفًا تمامًا عن الكود الذي لا يفعل ذلك ، لذا يمكنني أن أتخيل أن ذلك سيؤثر على تجربة تحليل هذا الرمز ، لكن لا يمكنني الموافقة تمامًا على هذا الاختلاف يعني الأسوأ في هذه الحالة ، على الرغم من أنني أفهم أنك شخصيًا لا تحبه تمامًا كما أحبها شخصيًا. أشياء كثيرة في Go بهذه الطريقة. أما ما يخبرك النسال أن تفعله فهو مسألة أخرى تمامًا ، على ما أعتقد.

تأكد من أنها ليست أفضل من الناحية الموضوعية. كنت أعبر عن أنه كان أكثر قابلية للقراءة بالنسبة لي . لقد قمت بصياغة ذلك بعناية.

مرة أخرى ، آسف لسبر مثل هذا. على الرغم من أن هذه حجة إلا أنني لم أقصد استعداءك.

https://github.com/golang/go/issues/32437#issuecomment -498908380

لا أحد سوف يجعلك تستخدم المحاولة.

بتجاهل البراعة ، أعتقد أن هذه طريقة مموجة يدويًا لرفض انتقادات التصميم.

بالتأكيد ، لست مضطرًا لاستخدامه. لكن أي شخص أكتب معه رمز يمكنه استخدامه ويجبرني على محاولة فك تشفير try(try(try(to()).parse().this)).easily()) . إنه مثل القول

لن يجعلك أحد تستخدم الواجهة الفارغة {}.

على أي حال ، Go صارم للغاية بشأن البساطة: gofmt يجعل كل الكود يبدو بنفس الطريقة. يبقى المسار السعيد يسارًا وأي شيء قد يكون مكلفًا أو مفاجئًا يكون واضحًا . try كما هو مقترح هو تحول 180 درجة من هذا. البساطة! = موجزة.

على الأقل ، يجب أن تكون try كلمة رئيسية ذات قيم.

ليس من المفترض أن يكون أفضل من if err != nil { return err } ، فقط أقل للكتابة.

هناك اختلاف موضوعي واحد بين الاثنين: try(Foo()) تعبير. بالنسبة للبعض ، يعتبر هذا الاختلاف جانبًا سلبيًا (النقد try(strconv.Atoi(x))+try(strconv.Atoi(y)) ). بالنسبة للآخرين ، يعتبر هذا الاختلاف اتجاهًا إيجابيًا لنفس السبب. ما زلت ليس أفضل أو أسوأ من الناحية الموضوعية - لكنني أيضًا لا أعتقد أنه يجب إزالة الاختلاف تحت السجادة والادعاء بأنه "أقل من الكتابة" لا ينصف الاقتراح.

@ elagergren-spideroak يصعب القول إن try مزعج لرؤية نفس واحد ثم القول إنه ليس صريحًا في اليوم التالي. يجب عليك اختيار واحد.

من الشائع رؤية وسيطات الوظيفة يتم وضعها في المتغيرات المؤقتة أولاً. أنا متأكد من أنه سيكون أكثر شيوعًا أن نرى

this := try(to()).parse().this
that := try(this.easily())

من مثالك.

try عدم القيام بأي شيء هو الطريق السعيد ، لذلك يبدو كما هو متوقع. في الطريق التعيس كل ما يفعله هو العودة. رؤية أن هناك try يكفي لجمع تلك المعلومات. ليس هناك أي شيء مكلف للرجوع من وظيفة أيضًا ، لذلك من هذا الوصف لا أعتقد أن try يقوم بـ 180

josharian فيما يتعلق بتعليقك في https://github.com/golang/go/issues/32437#issuecomment -498941854 ، لا أعتقد أن هناك خطأ تقييم مبكر هنا.

تأجيل fmt.HandleErrorf (& err، “foobar:٪ v”، err)

تم تمرير القيمة غير المعدلة البالغة err إلى HandleErrorf ، وتم تمرير مؤشر إلى err . نتحقق مما إذا كان err هو nil (باستخدام المؤشر). إذا لم يكن الأمر كذلك ، نقوم بتنسيق السلسلة باستخدام القيمة غير المعدلة وهي err . ثم قمنا بتعيين err على قيمة الخطأ المنسق باستخدام المؤشر.

Merovius الاقتراح هو في الحقيقة مجرد بناء جملة ماكرو السكر ، لذلك سينتهي به الأمر حول ما يعتقده الناس أنه يبدو أجمل أو أنه سيسبب أقل مشكلة. إذا كنت تعتقد لا ، من فضلك اشرح لي. لهذا السبب أنا شخصيا. إنها إضافة رائعة دون إضافة أي كلمات رئيسية من وجهة نظري.

ianlancetaylor ، أعتقد أن josharian صحيح: القيمة "غير المعدلة" لـ err هي القيمة في الوقت الذي يتم فيه دفع defer إلى المكدس ، وليست القيمة (المقصودة على الأرجح) err تم تعيينها بواسطة try قبل العودة.

المشكلة الأخرى التي أواجهها مع try هي أنها تجعل الأمر أسهل بكثير على الناس لإلقاء المزيد من المنطق في سطر واحد. هذه هي مشكلتي الرئيسية مع معظم اللغات الأخرى ، هي أنها تجعل من السهل حقًا وضع مثل 5 تعبيرات في سطر واحد ، ولا أريد ذلك.

this := try(to()).parse().this
that := try(this.easily())

^ ^ حتى هذا فظيع تمامًا. السطر الأول ، يجب أن أقفز ذهابًا وإيابًا لأقوم بمطابقة أقواس في رأسي. حتى السطر الثاني الذي هو في الواقع بسيط للغاية ... من الصعب حقًا قراءته.
يصعب قراءة الوظائف المتداخلة.

parser, err := to()
if err != nil {
    return err
}
this := parser.parse().this
that, err := this.easily()
if err != nil {
    return err
}

^ ^ هذا هو IMO أسهل بكثير وأفضل. إنه بسيط للغاية وواضح. نعم ، إنها سطور أكثر بكثير من التعليمات البرمجية ، لا يهمني. من الواضح جدا.

bcmillsjosharian آه ، بالطبع ، شكرا. لذلك يجب أن يكون

defer func() { fmt.HandleErrorf(&err, “foobar: %v”, err) }()

ليس لطيفا. ربما يجب أن يمرر fmt.HandleErrorf قيمة الخطأ ضمنيًا باعتبارها الوسيطة الأخيرة بعد كل شيء.

لقد حصلت هذه القضية على الكثير من التعليقات بسرعة كبيرة ، ويبدو لي أن العديد منها يكرر التعليقات التي تم الإدلاء بها بالفعل. بالطبع لا تتردد في التعليق ، لكني أود أن أقترح برفق أنه إذا كنت تريد إعادة ذكر نقطة تم توضيحها بالفعل ، فعليك القيام بذلك باستخدام الرموز التعبيرية لـ GitHub ، بدلاً من تكرار النقطة. شكرا.

ianlancetaylor إذا أرسل fmt.HandleErrorf خطأ كأول وسيطة بعد التنسيق ، فسيكون التنفيذ أفضل وسيكون المستخدم قادرًا على الرجوع إليه بواسطة %[1]v دائمًا.

natefinch أتفق تماما.

أتساءل عما إذا كان نهج أسلوب الصدأ سيكون أكثر قبولا؟
لاحظ أن هذا ليس اقتراحًا فقط أفكر فيه ...

this := to()?.parse().this
that := this.easily()?

في النهاية ، أعتقد أن هذا أجمل ، ولكن (يمكن استخدام ! أو أي شيء آخر أيضًا ...) ، ولكن لا يزال لا يصلح مشكلة التعامل مع الأخطاء بشكل جيد.


بالطبع يحتوي الصدأ أيضًا على try() تمامًا مثل هذا ، ولكن ... نمط الصدأ الآخر.

ليس من المفترض أن يكون أفضل من if err != nil { return err } ، فقط أقل للكتابة.

هناك اختلاف موضوعي واحد بين الاثنين: try(Foo()) تعبير. بالنسبة للبعض ، يعتبر هذا الاختلاف جانبًا سلبيًا (النقد try(strconv.Atoi(x))+try(strconv.Atoi(y)) ). بالنسبة للآخرين ، يعتبر هذا الاختلاف اتجاهًا إيجابيًا لنفس السبب. ما زلت ليس أفضل أو أسوأ من الناحية الموضوعية - لكنني أيضًا لا أعتقد أنه يجب إزالة الاختلاف تحت السجادة والادعاء بأنه "أقل من الكتابة" لا ينصف الاقتراح.

هذا هو أحد أكبر أسباب إعجابي بهذا النحو ؛ يتيح لي استخدام دالة إرجاع الخطأ كجزء من تعبير أكبر دون الحاجة إلى تسمية جميع النتائج الوسيطة. في بعض المواقف يكون تسميتها أمرًا سهلاً ، لكن في حالات أخرى لا يوجد اسم ذي معنى أو غير زائد عن الحاجة لمنحهم ، وفي هذه الحالة أفضل عدم منحهم اسمًا على الإطلاق.

تضمين التغريدة

بالضبط وجهة نظري. يحاول هذا الاقتراح حل حالة استخدام فردية بدلاً من توفير أداة لحل المشكلة الأكبر المتعلقة بمعالجة الأخطاء. أعتقد أن السؤال الأساسي هو بالضبط ما أشرت إليه.

ما الذي قلته بالتحديد وهذا هو بالضبط وجهة نظرك؟ يبدو لي أنك أسأت فهم وجهة نظري بشكل أساسي إذا كنت تعتقد أننا نتفق.

في رأيي وخبرتي ، حالة الاستخدام هذه هي أقلية صغيرة ولا تستدعي صياغة مختصرة.

في مصدر Go ، هناك آلاف الحالات التي يمكن معالجتها بواسطة try خارج الصندوق حتى لو لم تكن هناك طريقة لإضافة سياق إلى الأخطاء. إذا كان قاصرًا ، فلا يزال سببًا شائعًا للشكوى.

أيضًا ، فإن أسلوب استخدام التأجيل للتعامل مع الأخطاء به مشكلات من حيث أنه يفترض أنك تريد التعامل مع جميع الأخطاء المحتملة بنفس الطريقة. لا يمكن إلغاء تأجيل البيانات.

وبالمثل ، فإن طريقة استخدام + للتعامل مع الحساب تفترض أنك لا تريد الطرح ، لذا فأنت لا تريد ذلك إذا لم ترغب في ذلك. السؤال المثير للاهتمام هو ما إذا كان سياق الخطأ على مستوى الكتلة يمثل على الأقل نمطًا شائعًا.

ماذا لو أردت معالجة أخطاء مختلفة للأخطاء التي قد يتم إرجاعها من foo () مقابل foo2 ()

مرة أخرى ، فأنت لا تستخدم try . ثم لا تكسب شيئًا من try ، لكنك أيضًا لا تخسر شيئًا.

يارب احفظها

أتساءل عما إذا كان نهج أسلوب الصدأ سيكون أكثر قبولا؟

يقدم الاقتراح حجة ضد هذا.

في هذه المرحلة ، أعتقد أن وجود try{}catch{} أكثر قابلية للقراءة: upside_down_face:

  1. إن استخدام الواردات المسماة للتغلب على حالات الزاوية defer ليس فقط مروعًا لأشياء مثل godoc ، ولكن الأهم من ذلك أنه عرضة للخطأ. لا يهمني أنه يمكنني إنهاء الأمر برمته بدولار func() آخر للتغلب على المشكلة ، إنها مجرد أشياء أخرى أحتاج إلى وضعها في الاعتبار ، وأعتقد أنها تشجع على "ممارسة سيئة".
  2. لا أحد سوف يجعلك تستخدم المحاولة.

    هذا لا يعني أنه حل جيد ، فأنا أوضح نقطة أن الفكرة الحالية بها عيب في التصميم وأطلب معالجتها بطريقة أقل عرضة للخطأ.

  3. أعتقد أن أمثلة مثل try(try(try(to()).parse().this)).easily()) غير واقعية ، ويمكن فعل ذلك بالفعل مع وظائف أخرى وأعتقد أنه سيكون من العدل لأولئك الذين يراجعون الكود أن يطلبوا تقسيمها.
  4. ماذا لو كان لدي 3 أماكن يمكن أن أخطئ فيها وأريد أن أغلق كل مكان على حدة؟ try() يجعل هذا الأمر صعبًا للغاية ، في الواقع try() يثبط بالفعل أخطاء التغليف نظرًا لصعوبة ذلك ، ولكن إليك مثال على ما أعنيه:

    func before() error {
      x, err := foo()
      if err != nil {
        wrap(err, "error on foo")
      }
      y, err := bar(x)
      if err != nil {
        wrapf(err, "error on bar with x=%v", x)
      }
      fmt.Println(y)
      return nil
    }
    
    func after() (err error) {
      defer fmt.HandleErrorf(&err, "something failed but I don't know where: %v", err)
      x := try(foo())
      y := try(bar(x))
      fmt.Println(y)
      return nil
    }
    
  5. مرة أخرى ، فأنت لا تستخدم try . ثم لا تكسب شيئًا من try ، لكنك أيضًا لا تخسر شيئًا.

    لنفترض أن التفاف الأخطاء بسياق مفيد يعد ممارسة جيدة ، فسيتم اعتبار try() ممارسة سيئة لأنه لا يضيف أي سياق. هذا يعني أن try() هي ميزة لا يرغب أحد في استخدامها وتصبح ميزة يتم استخدامها نادرًا جدًا لدرجة أنها قد لا تكون موجودة.

    بدلاً من مجرد قول "حسنًا ، إذا لم تعجبك ، فلا تستخدمه وتصمت" (هذه هي الطريقة التي تقرأ بها) ، أعتقد أنه سيكون من الأفضل محاولة معالجة ما يفكر فيه كثير من الاستخدامات في وجود خطأ في التصميم. هل يمكننا بدلاً من ذلك مناقشة ما يمكن تعديله من التصميم المقترح بحيث يتم التعامل مع مخاوفنا بطريقة أفضل؟

boomlinde النقطة التي نتفق عليها هي أن هذا الاقتراح يحاول حل حالة استخدام ثانوية وحقيقة أنه "إذا لم تكن بحاجة إليه ، فلا تستخدمه" هي الحجة الأساسية التي تدعم هذه النقطة. كما ذكر @ elagergren-spideroak ، فإن هذه الحجة لا تعمل لأنه حتى لو لم أرغب في استخدامها ، فإن الآخرين سيفعلون ذلك مما يجبرني على استخدامها. وفقًا لمنطق حجتك ، يجب أن يكون لـ Go أيضًا بيان ثلاثي. وإذا كنت لا تحب العبارات الثلاثية ، فلا تستخدمها.

إخلاء المسؤولية - أعتقد أن Go يجب أن يكون لديه بيان ثلاثي ، لكن بالنظر إلى أن نهج Go لميزات اللغة هو عدم تقديم ميزات يمكن أن تجعل قراءة الكود أكثر صعوبة ، فلا ينبغي.

شيء آخر يحدث لي: أرى الكثير من الانتقادات على أساس فكرة أن وجود try قد يشجع المطورين على التعامل مع الأخطاء بلا مبالاة. لكن في رأيي هذا صحيح أكثر بالنسبة للغة الحالية ؛ إن النموذج المعياري لمعالجة الأخطاء مزعج بدرجة كافية لدرجة أنه يشجع المرء على ابتلاع أو تجاهل بعض الأخطاء لتجنبها. على سبيل المثال ، لقد كتبت أشياء مثل هذه عدة مرات:

func exists(filename string) bool {
  _, err := os.Stat(filename)
  return err == nil
}

لكي تتمكن من كتابة if exists(...) { ... } ، على الرغم من أن هذا الرمز يتجاهل بصمت بعض الأخطاء المحتملة. إذا كان لديّ try ، فربما لن أزعج نفسي للقيام بذلك وأعيد (bool, error) .

كونك فوضوياً هنا ، سأطرح فكرة إضافة دالة مضمنة ثانية تسمى catch والتي ستتلقى وظيفة تأخذ خطأ وتعيد خطأ مكتوبًا ، ثم إذا كان catch لاحق يطلق عليه من شأنه الكتابة فوق المعالج. علي سبيل المثال:

func catch(handler func(err error) error) {
  // .. impl ..
}

الآن ، ستكون هذه الوظيفة المضمنة أيضًا دالة تشبه الماكرو والتي من شأنها معالجة الخطأ التالي الذي سيتم إرجاعه بواسطة try مثل هذا:

func wrapf(format string, ...values interface{}) func(err error) error {
  // user defined
  return func(err error) error {
    return fmt.Errorf(format + ": %v", ...append(values, err))
  }
}
func sample() {
  catch(wrapf("something failed in foo"))
  try(foo()) // "something failed in foo: <error>"
  x := try(foo2()) // "something failed in foo: <error>"
  // Subsequent calls for catch overwrite the handler
  catch(wrapf("something failed in bar with x=%v", x))
  try(bar(x)) // "something failed in bar with x=-1: <error>"
}

هذا أمر رائع لأنني أستطيع التفاف الأخطاء بدون defer والذي يمكن أن يكون عرضة للخطأ ما لم نستخدم قيم إرجاع مسماة أو التفاف مع func آخر ، فهو أيضًا لطيف لأن defer سيضيف نفس معالج الأخطاء لـ جميع الأخطاء حتى لو كنت أرغب في التعامل مع اثنين منهم بشكل مختلف. يمكنك أيضًا استخدامه كما تراه مناسبًا ، على سبيل المثال:

func foo(a, b string) (int64, error) {
  return try(strconv.Atoi(a)) + try(strconv.Atoi(b))
}
func withContext(a, b string) (int64, error) {
  catch(func (err error) error {
    return fmt.Errorf("can't parse a: %s, b: %s, err: %v", a, b, err)
  })
  return try(strconv.Atoi(a)) + try(strconv.Atoi(b))
}
func moreExplicitContext(a, b string) (int64, error) {
  catch(func (err error) error {
    return fmt.Errorf("can't parse a: %s, err: %v", a, err)
  })
  x := try(strconv.Atoi(a))
  catch(func (err error) error {
    return fmt.Errorf("can't parse b: %s, err: %v", b, err)
  })
  y := try(strconv.Atoi(b))
  return x + y
}
func withHelperWrapf(a, b string) (int64, error) {
  catch(wrapf("can't parse a: %s", a))
  x := try(strconv.Atoi(a))
  catch(wrapf("can't parse b: %s", b))
  y := try(strconv.Atoi(b))
  return x + y
}
func before(a, b string) (int64, error) {
  x, err := strconv.Atoi(a)
  if err != nil {
    return 0,  fmt.Errorf("can't parse a: %s, err: %v", a, err)
  }
  y, err := strconv.Atoi(b)
  if err != nil {
    return 0,  fmt.Errorf("can't parse b: %s, err: %v", b, err)
  }
  return x + y
}

وما زلت في حالة مزاجية فوضوية (لمساعدتك على التعاطف) إذا كنت لا تحب catch ، فلن تضطر إلى استخدامه.

الآن ... أنا لا أعني أنها الجملة الأخيرة ، لكن يبدو أنها ليست مفيدة للمناقشة ، IMO شديدة العدوانية.
ومع ذلك ، إذا سلكنا هذا الطريق ، أعتقد أنه قد يكون لدينا أيضًا try{}catch(error err){} بدلاً من ذلك: stuck_out_tongue:

راجع أيضًا # 27519 - نموذج الخطأ # id / catch

لا أحد سوف يجعلك تستخدم المحاولة.

بتجاهل البراعة ، أعتقد أن هذه طريقة مموجة يدويًا لرفض انتقادات التصميم.

آسف ، لم يكن glib هو نيتي.

ما أحاول قوله هو أن try لا يُقصد به أن يكون حلاً بنسبة 100٪. هناك العديد من نماذج معالجة الأخطاء التي لا يتم التعامل معها بشكل جيد بواسطة try . على سبيل المثال ، إذا كنت بحاجة إلى إضافة سياق يعتمد على موقع الاتصال إلى الخطأ. يمكنك دائمًا الرجوع إلى استخدام if err != nil { للتعامل مع تلك الحالات الأكثر تعقيدًا.

إنها بالتأكيد حجة صحيحة مفادها أن try لا يمكنه التعامل مع X ، في حالات مختلفة من X. ولكن غالبًا ما يعني التعامل مع الحالة X جعل الآلية أكثر تعقيدًا. هناك مقايضة هنا ، التعامل مع X من ناحية ولكن يعقد آلية كل شيء آخر. يعتمد ما نفعله جميعًا على مدى شيوع X ، ومقدار المضاعفات التي قد تتطلبها للتعامل مع X.

لذا من خلال "لن يجعلك أحد تستخدم المحاولة" ، أعني أنني أعتقد أن المثال المعني هو 10٪ ، وليس 90٪. هذا التأكيد مطروح بالتأكيد للنقاش ، ويسعدني سماع الحجج المضادة. ولكن في النهاية سيتعين علينا رسم الخط في مكان ما ونقول "نعم ، try لن يتعامل مع هذه الحالة. سيتعين عليك استخدام أسلوب معالجة الأخطاء القديم. معذرة."

ليس الأمر أن "المحاولة لا تستطيع التعامل مع هذه الحالة المحددة لمعالجة الأخطاء" هي المشكلة ، إنها "المحاولة تشجعك على عدم تغليف أخطائك". أجبرتك فكرة check-handle على كتابة بيان إرجاع ، لذا فإن كتابة التفاف للخطأ كان تافهًا للغاية.

بموجب هذا الاقتراح ، تحتاج إلى استخدام عائد مسمى defer ، وهو ليس بديهيًا ويبدو مبتذلًا للغاية.

أجبرتك فكرة check-handle على كتابة بيان إرجاع ، لذا فإن كتابة التفاف للخطأ كان تافهًا للغاية.

هذا ليس صحيحًا - في مسودة التصميم ، كل دالة تقوم بإرجاع خطأ لها معالج افتراضي يقوم بإرجاع الخطأ فقط.

بناءً على النقطة الوقحة في Goodwine ، لا تحتاج حقًا إلى وظائف منفصلة مثل HandleErrorf إذا كان لديك وظيفة جسر واحد مثل

func handler(err *error, handle func(error) error) {
  // nil handle is treated as the identity
  if *err != nil && handle != nil {
    *err = handle(*err)
  }
}

الذي قد تستخدمه مثل

defer handler(&err, func(err error) error {
  if errors.Is(err, io.EOF) {
    return nil
  }
  return fmt.Errorf("oops: %w", err)
})

يمكنك جعل handler نفسها شبه سحرية مدمجة مثل try .

إذا كان الأمر سحرًا ، فيمكن أن تأخذ حجةها الأولى ضمنيًا - السماح باستخدامها حتى في الوظائف التي لا تذكر عائدها error ، مما يؤدي إلى استبعاد أحد الجوانب الأقل حظًا في الاقتراح الحالي مع تقليله روائح وخطأ عرضة لتزيين الأخطاء. بالطبع ، هذا لا يقلل من المثال السابق كثيرًا:

defer handler(func(err error) error {
  if errors.Is(err, io.EOF) {
    return nil
  }
  return fmt.Errorf("oops: %w", err)
})

إذا كان الأمر سحريًا بهذه الطريقة ، فيجب أن يكون خطأ في وقت التجميع إذا تم استخدامه في أي مكان باستثناء الوسيطة لـ defer . يمكنك أن تخطو خطوة أبعد وتجعلها تؤجل ضمنيًا ، لكن defer handler يقرأ بشكل جيد جدًا.

نظرًا لأنه يستخدم defer ، يمكنه الاتصال بـ handle func كلما كان هناك خطأ غير معدوم يتم إرجاعه ، مما يجعله مفيدًا حتى بدون try حيث يمكنك إضافة

defer handler(wrapErrWithPackageName)

في الجزء العلوي إلى fmt.Errorf("mypkg: %w", err) كل شيء.

يمنحك هذا الكثير من عرض check / handle ولكنه يعمل مع التأجيل بشكل طبيعي (وبشكل صريح) مع التخلص من الحاجة ، في معظم الحالات ، لتسمية صراحة err عودة try إنه ماكرو مباشر نسبيًا (أتخيل) يمكن تنفيذه بالكامل في الواجهة الأمامية.

هذا ليس صحيحًا - في مسودة التصميم ، كل دالة تقوم بإرجاع خطأ لها معالج افتراضي يقوم بإرجاع الخطأ فقط.

يا سيئة ، أنت على صواب.

أعني أنني أعتقد أن المثال المعني هو 10٪ وليس 90٪. هذا التأكيد مطروح بالتأكيد للنقاش ، ويسعدني سماع الحجج المضادة. ولكن في النهاية سيتعين علينا رسم الخط في مكان ما ونقول "نعم ، لن تحاول المحاولة للتعامل مع هذه الحالة. سيتعين عليك استخدام أسلوب معالجة الأخطاء القديم. معذرة."

متفق عليه ، رأيي هو أنه يمكن رسم هذا الخط عند التحقق من EOF أو ما شابه ، وليس عند التغليف. ولكن ربما إذا كانت الأخطاء تحتوي على سياق أكثر ، فلن تكون هذه مشكلة بعد الآن.

هل يمكن أن يقوم try() بالالتفاف التلقائي للأخطاء بسياق مفيد للتصحيح؟ على سبيل المثال ، إذا أصبح xerrors errors ، يجب أن تحتوي الأخطاء على شيء يشبه تتبع المكدس الذي يمكن أن يضيفه try() ، أليس كذلك؟ إذا كان الأمر كذلك ، فربما يكون ذلك كافياً 🤔

إذا كانت الأهداف (اقرأ https://github.com/golang/proposal/blob/master/design/32437-try-builtin.md):

  • القضاء على النمطي
  • الحد الأدنى من التغييرات اللغوية
  • تغطي "السيناريوهات الأكثر شيوعًا"
  • إضافة القليل من التعقيد إلى اللغة

أود أن آخذ الاقتراح وأعطيه زاوية وأسمح بترحيل رمز "الخطوات الصغيرة" لجميع أسطر الشفرة المليارات من هناك.

بدلا من المقترح:

func printSum(a, b string) error {
        defer fmt.HandleErrorf(&err, "sum %s %s: %v", a,b, err) 
        x := try(strconv.Atoi(a))
        y := try(strconv.Atoi(b))
        fmt.Println("result:", x + y)
        return nil
}

نحن نقدر:

func printSum(a, b string) error {
        var err ErrHandler{HandleFunc : twoStringsErr("printSum",a,b)} 
        x, err.Error := strconv.Atoi(a)
        y,err.Error := strconv.Atoi(b)
        fmt.Println("result:", x + y)
        return nil
}

ماذا سنكسب؟
يمكن تضمين twoStringsErr للطباعة ، أو معالج عام يعرف كيفية التقاط الأخطاء (في هذه الحالة مع معلمتين من سلسلة) - لذلك إذا كان لدي نفس التواقيع الوظيفية المتكررة المستخدمة في العديد من وظائفي ، فلن أحتاج إلى إعادة كتابة كل معالج زمن
بالطريقة نفسها ، يمكنني تمديد نوع ErrHandler بالطريقة التالية:

type ioErrHandler ErrHandler
func (i ErrHandler) Handle() ...{

}

أو

type parseErrHandler ErrHandler
func (p parseErrHandler) Handle() ...{

}

أو

type str2IntErrHandler ErrHandler
func (s str2IntErrHandler) Handle() ...{

}

واستخدم هذا في جميع أنحاء الكود الخاص بي:

func printSum(a, b string) error {
        var pErr str2IntErrHandler 
        x, err.Error := strconv.Atoi(a)
        y,err.Error := strconv.Atoi(b)
        fmt.Println("result:", x + y)
        return nil
}

لذا ، فإن الحاجة الفعلية هي تطوير مشغل عندما يخطئ ، ويتم تعيين الخطأ على أنه لا شيء
باستخدام هذه الطريقة يمكننا أيضًا:

func (s str2IntErrHandler) Handle() bool{
   **return false**
}

والذي من شأنه أن يخبر وظيفة الاستدعاء بالاستمرار بدلاً من العودة

واستخدم معالجات أخطاء مختلفة في نفس الوظيفة:

func printSum(a, b string) error {
        var pErr str2IntErrHandler 
        var oErr overflowError 
        x, err.Error := strconv.Atoi(a)
        y,err.Error := strconv.Atoi(b)
        fmt.Println("result:", x + y)
        totalAsByte,oErr := sumBytes(x,y)
        sunAsByte,oErr := subtractBytes(x,y)
        return nil
}

إلخ.

تجاوز الأهداف مرة أخرى

  • القضاء على المتداول - تم
  • الحد الأدنى من التغييرات اللغوية - تم
  • تغطي "السيناريوهات الأكثر شيوعًا" - أكثر من IMO المقترح
  • إضافة القليل جدا من التعقيد للغة - وحي
    بالإضافة إلى - ترحيل رمز أسهل من
x, err := strconv.Atoi(a)

ل

x, err.Error := strconv.Atoi(a)

وفي الواقع - سهولة قراءة أفضل (IMO ، مرة أخرى)

guybrand أنت آخر ملتزم بهذا الموضوع المتكرر (الذي أحبه).

راجع https://github.com/golang/go/wiki/Go2ErrorHandlingFeedback#recurring -themes

guybrand يبدو هذا اقتراحًا مختلفًا تمامًا ؛ أعتقد أنه يجب عليك تقديمه باعتباره مشكلته الخاصة حتى يتمكن هذا الشخص من التركيز على مناقشة اقتراح griesemer .

natefinch توافق. أعتقد أن هذا موجه أكثر نحو تحسين التجربة أثناء كتابة Go بدلاً من تحسين القراءة. أتساءل عما إذا كانت وحدات ماكرو IDE أو المقتطفات يمكنها حل المشكلة دون أن تصبح هذه ميزة للغة.

@نبيذ جيد

لنفترض أن التفاف الأخطاء بسياق مفيد يعد ممارسة جيدة ، فسيتم اعتبار try() ممارسة سيئة لأنه لا يضيف أي سياق. هذا يعني أن try() هي ميزة لا يرغب أحد في استخدامها وتصبح ميزة يتم استخدامها نادرًا جدًا لدرجة أنها قد لا تكون موجودة.

كما هو مذكور في الاقتراح (كما هو موضح بالمثال) ، لا يمنعك try بشكل أساسي من إضافة سياق. أود أن أقول إن الطريقة التي يُقترح بها ، إضافة سياق إلى الأخطاء متعامدة تمامًا معها. يتم تناول هذا بشكل خاص في الأسئلة الشائعة الخاصة بالاقتراح.

أدرك أن try لن يكون مفيدًا إذا كان ضمن دالة واحدة إذا كان هناك العديد من السياقات المختلفة التي تريد إضافتها إلى أخطاء مختلفة من استدعاءات الوظائف. ومع ذلك ، أعتقد أيضًا أن شيئًا ما في السياق العام HandleErrorf يغطي مساحة كبيرة من الاستخدام لأن إضافة سياق على مستوى الوظيفة فقط إلى الأخطاء ليس بالأمر غير المعتاد.

بدلاً من مجرد قول "حسنًا ، إذا لم تعجبك ، فلا تستخدمه وتصمت" (هذه هي الطريقة التي تقرأ بها) ، أعتقد أنه سيكون من الأفضل محاولة معالجة ما يفكر فيه كثير من الاستخدامات في وجود خطأ في التصميم.

إذا كانت هذه هي الطريقة التي تقرأ بها أعتذر. وجهة نظري ليست أنه يجب عليك التظاهر بأنه غير موجود إذا لم يعجبك. من الواضح أن هناك حالات يكون فيها try غير مجدية وأنه لا يجب عليك استخدامها في مثل هذه الحالات ، والتي أعتقد بالنسبة لهذا الاقتراح أنها تحقق توازنًا جيدًا بين KISS والمنفعة العامة. لم أكن أعتقد أنني لم أكن واضحًا بشأن هذه النقطة.

شكرا للجميع على ردود الفعل غزيرة حتى الآن ؛ هذا مفيد للغاية.
ها هي محاولتي في الملخص الأولي ، للحصول على شعور أفضل بالتعليقات. أعتذر مقدمًا عن أي شخص فاتني أو أسيء تقديمه ؛ آمل أن أكون قد فهمت الفكرة العامة بشكل صحيح.

0) على الجانب الإيجابي ، عبرrasky وadg و eandre و @ dpinela وآخرين صراحةً عن سعادتهم لتبسيط الكود الذي يوفره try .

1) يبدو أن الشاغل الأكثر أهمية هو أن try لا يشجع أسلوب معالجة الأخطاء بشكل جيد ولكنه بدلاً من ذلك يروج "للخروج السريع". ( agnivade و @ peterbourgon و @ politician و @ a8m و eandre و prologic و @ kungfusheep و @ cpuguy وآخرين أعربوا عن قلقهم حيال ذلك.)

2) كثير من الناس لا يحبون فكرة المضمنة ، أو بناء جملة الوظيفة التي تأتي معها لأنها تخفي return . سيكون من الأفضل استخدام كلمة رئيسية. ( sheerun ، Redundancy،dolmen، @ komuw،RobertGrantEllis ، @ elagergren -spideroak). يمكن أيضًا التغاضي عن try بسهولة (@ peterbourgon) ، خاصةً لأنه يمكن أن يظهر في التعبيرات التي قد تكون متداخلة بشكل عشوائي. تشعر natefinch بالقلق من أن try يجعل الأمر "سهلًا للغاية لإلقاء الكثير في سطر واحد" ، وهو أمر نحاول عادةً تجنبه في Go. أيضًا ، قد لا يكون دعم IDE للتأكيد على try كافيًا (dominikh) ؛ يحتاج try إلى "الوقوف بمفرده".

3) بالنسبة للبعض ، فإن الوضع الراهن لبيانات if الصريحة ليست مشكلة ، فهم سعداء بها ( bitfield ، @ marwan-at-work،natefinch). من الأفضل أن يكون لديك طريقة واحدة فقط للقيام بالأشياء (gbbr) ؛ وبيانات if الصريحة أفضل من العبارات الضمنية return ( DavexPro ، hmage ، prologic ،natefinch).
على نفس المنوال ، فإنmattn تشعر بالقلق إزاء "الربط الضمني" لنتيجة الخطأ try - الاتصال غير مرئي بشكل واضح في الكود.

4) استخدام try سيجعل من الصعب تصحيح التعليمات البرمجية ؛ على سبيل المثال ، قد يكون من الضروري إعادة كتابة تعبير try مرة أخرى في عبارة if فقط حتى يمكن إدراج عبارات التصحيح ( deanveloper ، typeless ، @ networkimprov ، آخرون).

5) هناك بعض القلق بشأن استخدام المرتجعات المسماة ( buchanae ،adg).

قدم العديد من الأشخاص اقتراحات لتحسين أو تعديل الاقتراح:

6) اختار البعض فكرة معالج الأخطاء الاختياري (beoran) أو سلسلة التنسيق المقدمة إلى try ( unexge ، @ a8m ، eandre ،gotwarlost) لتشجيع المعالجة الجيدة للأخطاء.

7) اقترح pierrec أن gofmt يمكنه تنسيق تعبيرات try بشكل مناسب لجعلها أكثر وضوحًا.
بدلاً من ذلك ، يمكن للمرء أن يجعل الكود الموجود أكثر ضغطًا من خلال السماح لـ gofmt بتنسيق كشوفات حساب if للتحقق من وجود أخطاء في سطر واحد (zeebo).

8) يجادل @ marwan-at-work بأن try ينقل ببساطة معالجة الأخطاء من عبارات $ # $ if try . بدلاً من ذلك ، إذا أردنا حل المشكلة فعليًا ، يجب على Go "امتلاك" معالجة الأخطاء بجعلها ضمنية حقًا. يجب أن يكون الهدف هو جعل معالجة الأخطاء (المناسبة) أبسط وجعل المطورين أكثر إنتاجية (cpuguy).

9) أخيرًا ، لا يحب بعض الأشخاص الاسم try ( beoran ، HiImJC ،dolmen) أو يفضلون رمزًا مثل ? ( @ twisted1919، leaxoy ، آخرون) .

بعض التعليقات على هذه التعليقات (مرقمة وفقًا لذلك):

0) شكرا لردود الفعل الإيجابية! :-)

1) سيكون من الجيد معرفة المزيد عن هذا القلق. إن أسلوب الترميز الحالي الذي يستخدم عبارات if لاختبار الأخطاء واضح بقدر ما يمكن أن يكون. من السهل جدًا إضافة معلومات إضافية إلى الخطأ ، على أساس فردي (لكل if ). غالبًا ما يكون من المنطقي معالجة جميع الأخطاء المكتشفة في دالة بطريقة موحدة ، والتي يمكن إجراؤها باستخدام defer - وهذا ممكن بالفعل الآن. إنها حقيقة أننا نمتلك بالفعل جميع الأدوات للتعامل مع الأخطاء بشكل جيد في اللغة ، وأن مشكلة بنية المعالج التي لا تكون متعامدة مع defer ، هي التي دفعتنا إلى ترك آلية جديدة لزيادة الأخطاء فقط. .

2) هناك بالطبع إمكانية استخدام كلمة رئيسية أو بناء جملة خاص بدلاً من استخدام مدمج. لن تكون الكلمة الأساسية الجديدة متوافقة مع الإصدارات السابقة. عامل جديد قد يكون ، لكنه يبدو أقل وضوحا. يناقش الاقتراح التفصيلي الإيجابيات والسلبيات المختلفة بإسهاب. لكن ربما أخطأنا في الحكم على هذا.

3) سبب هذا الاقتراح هو أن معالجة الأخطاء (على وجه التحديد الكود المعياري المرتبط) قد تم ذكرها كمسألة مهمة في Go (بجانب نقص الأدوية العامة) من قبل مجتمع Go. هذا الاقتراح يعالج بشكل مباشر القلق المعياري. إنها لا تفعل أكثر من حل الحالة الأساسية لأن أي حالة أكثر تعقيدًا يتم التعامل معها بشكل أفضل مع ما لدينا بالفعل. لذلك ، في حين أن عددًا كبيرًا من الأشخاص سعداء بالوضع الراهن ، هناك (ربما) مجموعة كبيرة بنفس القدر من الأشخاص الذين يرغبون في اتباع نهج أكثر بساطة مثل try ، مدركين جيدًا أن هذا "عادل" التجميل اللغوى.

4) نقطة التصحيح هي مصدر قلق صحيح. إذا كانت هناك حاجة لإضافة رمز بين اكتشاف خطأ و return ، فإن الاضطرار إلى إعادة كتابة تعبير try في عبارة if قد يكون أمرًا مزعجًا.

5) قيم الإرجاع المسماة: الوثيقة التفصيلية تناقش هذا باستفاضة. إذا كان هذا هو مصدر القلق الرئيسي بشأن هذا الاقتراح ، فأنا أعتقد أننا في وضع جيد.

6) وسيطة المعالج الاختيارية لـ try : الوثيقة التفصيلية تناقش هذا أيضًا. انظر قسم تكرارات التصميم.

7) استخدام gofmt لتنسيق تعبيرات try بحيث تكون مرئية بشكل إضافي سيكون بالتأكيد خيارًا. ولكنه قد ينتقص من بعض مزايا try عند استخدامه في تعبير.

8) لقد نظرنا في النظر إلى المشكلة من وجهة نظر معالجة الخطأ ( handle ) وليس من وجهة نظر اختبار الخطأ ( try ). على وجه التحديد ، نظرنا بإيجاز فقط في تقديم مفهوم معالج الأخطاء (على غرار مسودة التصميم الأصلية المقدمة في Gophercon العام الماضي). كان التفكير هو أنه إذا (وفقط إذا) تم التصريح عن معالج ، في تعيينات متعددة القيم حيث تكون القيمة الأخيرة من النوع error ، يمكن ببساطة ترك هذه القيمة بعيدًا في مهمة. سيتحقق المترجم ضمنيًا مما إذا كان لا شيء ، وإذا كان الأمر كذلك ، يتفرع إلى المعالج. هذا من شأنه أن يجعل معالجة الأخطاء الصريحة تختفي تمامًا ويشجع الجميع على كتابة معالج بدلاً من ذلك. بدا هذا وكأنه نهج متطرف لأنه سيكون ضمنيًا تمامًا - حقيقة أن الشيك يحدث سيكون غير مرئي.

9) هل لي أن أقترح ألا نتخلى عن الاسم بالدراجة في هذه المرحلة. بمجرد تسوية جميع المخاوف الأخرى ، يكون الوقت أفضل لضبط الاسم.

هذا لا يعني أن المخاوف غير صحيحة - فالردود أعلاه توضح ببساطة تفكيرنا الحالي. للمضي قدمًا ، سيكون من الجيد التعليق على مخاوف جديدة (أو أدلة جديدة تدعم هذه المخاوف) - مجرد إعادة ذكر ما قيل بالفعل لا يوفر لنا مزيدًا من المعلومات.

وأخيرًا ، يبدو أن ليس كل من علق على القضية قد قرأ المستند التفصيلي. يرجى القيام بذلك قبل التعليق لتجنب تكرار ما قيل بالفعل. شكرا.

هذا ليس تعليقًا على الاقتراح ، ولكنه تقرير خطأ مطبعي. لم يتم إصلاحه منذ نشر الاقتراح الكامل ، لذلك اعتقدت أنني سأذكره:

func try(t1 T1, t1 T2, … tn Tn, te error) (T1, T2, … Tn)

يجب ان يكون:

func try(t1 T1, t2 T2, … tn Tn, te error) (T1, T2, … Tn)

هل سيكون من المفيد تحليل كود Go المتاح علنًا لبيانات التحقق من الأخطاء لمحاولة اكتشاف ما إذا كانت معظم عمليات التحقق من الأخطاء متكررة حقًا أو إذا كانت عمليات التحقق المتعددة في نفس الوظيفة تضيف معلومات سياقية مختلفة في معظم الحالات؟ سيكون الاقتراح منطقيًا للغاية بالنسبة للحالة الأولى ولكنه لن يساعد الحالة الثانية. في الحالة الأخيرة ، سيستمر الأشخاص في استخدام if err != nil أو التخلي عن إضافة سياق إضافي ، واستخدام try() واللجوء إلى إضافة سياق خطأ شائع لكل دالة والتي قد تكون IMO ضارة. مع ميزات قيم الخطأ القادمة ، أعتقد أننا نتوقع أن يقوم الأشخاص بتغليف الأخطاء بمزيد من المعلومات في كثير من الأحيان. ربما أساءت فهم الاقتراح ولكن AFAIU ، هذا يساعد في تقليل النموذج المعياري فقط عندما يجب تغليف جميع الأخطاء من وظيفة واحدة بطريقة واحدة بالضبط ولا يساعد إذا كانت الوظيفة تتعامل مع خمسة أخطاء قد تحتاج إلى لفها بشكل مختلف. لست متأكدًا من مدى شيوع مثل هذه الحالات في البرية (شائعة جدًا في معظم مشاريعي) ولكني قلق من أن try() قد يشجع الناس على استخدام أغلفة شائعة لكل وظيفة حتى عندما يكون من المنطقي التفاف أخطاء مختلفة بشكل مختلف.

مجرد تعليق سريع مدعوم ببيانات مجموعة عينة صغيرة:

نقترح وظيفة مضمنة جديدة تسمى try ، مصممة خصيصًا للتخلص من الصيغة المعيارية إذا كانت العبارات مرتبطة عادةً بمعالجة الأخطاء في Go

Iff هذه هي المشكلة الأساسية التي يتم حلها من خلال هذا الاقتراح ، أجد أن هذا "النموذج المعياري" لا يمثل سوى 1.4٪ من الكود الخاص بي عبر العشرات من المشاريع مفتوحة المصدر المتاحة للجمهور والتي يبلغ مجموعها حوالي 60 ألفًا من SLOC.

هل تشعر بالفضول إذا كان لدى أي شخص آخر نفس الإحصائيات؟

في قاعدة بيانات أكبر بكثير مثل Go نفسها والتي يبلغ مجموعها حوالي 1.6M SLOC ، يصل هذا إلى حوالي 0.5 ٪ من قاعدة الشفرة التي تحتوي على خطوط مثل if err != nil .

هل هذه حقًا هي المشكلة الأكثر تأثيرًا التي يجب حلها باستخدام Go 2؟

شكرًا جزيلاً لك griesemer على قضاء الوقت في استعراض أفكار الجميع وتقديم الأفكار بشكل صريح. أعتقد أنه يساعد حقًا في إدراك أن المجتمع يُسمع في هذه العملية.

  1. اقترح pierrec أنه يمكن لـ gofmt تنسيق تجربة التعبيرات بشكل مناسب لجعلها أكثر وضوحًا.
    بدلاً من ذلك ، يمكن للمرء أن يجعل الكود الحالي أكثر إحكاما من خلال السماح لـ gofmt بتنسيق عبارات if للتحقق من الأخطاء في سطر واحد (zeebo).
  1. سيكون استخدام gofmt لتنسيق تعبيرات try بحيث تكون مرئية بشكل إضافي خيارًا بالتأكيد. ولكنه قد ينتقص من بعض مزايا try عند استخدامه في تعبير.

هذه أفكار قيّمة حول طلب gofmt لتنسيق try ، لكنني مهتم إذا كانت هناك أي أفكار على وجه الخصوص حول gofmt السماح بفحص كشف الحساب if أن يكون الخطأ سطرًا واحدًا. تم تجميع العرض بتنسيق try ، لكنني أعتقد أنه شيء متعامد تمامًا. شكرا.

griesemer أشكرك على العمل الرائع من خلال جميع التعليقات والإجابة على معظم التعليقات إن لم يكن كلها 🎉

هناك شيء واحد لم يتم تناوله في ملاحظاتك وهو فكرة استخدام جزء الأدوات / الفحص من لغة Go لتحسين تجربة معالجة الأخطاء ، بدلاً من تحديث بناء جملة Go.

على سبيل المثال ، مع هبوط LSP الجديد ( gopls ) ، يبدو أنه مكان مثالي لتحليل توقيع الوظيفة والعناية بأخطاء معالجة الأخطاء للمطور ، مع التفاف وفحص مناسبين أيضًا.

griesemer أنا متأكد من أن هذا لم يتم التفكير فيه جيدًا ، لكنني حاولت تعديل اقتراحك أقرب إلى شيء سأكون مرتاحًا له هنا: https://www.reddit.com/r/golang/comments/bwvyhe / offer_a_builtin_go_error_check_function_try / eq22bqa؟ utm_source = share & utm_medium = web2x

zeebo سيكون من السهل إنشاء تنسيق $ gofmt if err != nil { return ...., err } على سطر واحد. من المفترض أنه سيكون فقط لهذا النوع المحدد من نمط if ، وليس كل كشوف الحساب "القصيرة" if ؟

على نفس المنوال ، كانت هناك مخاوف من أن يكون try غير مرئي لأنه على نفس السطر مثل منطق الأعمال. لدينا كل هذه الخيارات:

النمط الحالي:

a, b, c, ... err := BusinessLogic(...)
if err != nil {
   return ..., err
}

سطر واحد if :

a, b, c, ... err := BusinessLogic(...)
if err != nil { return ..., err }

try في سطر منفصل (!):

a, b, c, ... err := BusinessLogic(...)
try(err)

try كما هو مقترح:

a, b, c := try(BusinessLogic(...))

يبدو السطر الأول والأخير هو الأكثر وضوحًا (بالنسبة لي) ، خاصةً بمجرد استخدام أحدهما للتعرف على try على أنه ما هو عليه. مع السطر الأخير ، يتم التحقق من الخطأ بشكل صريح ، ولكن نظرًا لأنه (عادةً) ليس الإجراء الرئيسي ، فهو أكثر قليلاً في الخلفية.

@ marwan-at-work لست متأكدًا مما تقترحه من أن الأدوات مناسبة لك. هل تقترح عليهم إخفاء معالجة الخطأ بطريقة ما؟

تضمين التغريدة

guybrand يبدو هذا اقتراحًا مختلفًا تمامًا ؛ أعتقد أنه يجب عليك تقديمه باعتباره مشكلته الخاصة حتى يتمكن هذا الشخص من التركيز على مناقشة اقتراح griesemer .

IMO اقتراحي يختلف فقط في بناء الجملة ، وهذا يعني:

  • الأهداف متشابهة في المحتوى والأولوية.
  • تتشابه فكرة التقاط كل خطأ ضمن السطر الخاص به وبالتالي (إن لم يكن لا شيء) للخروج من الوظيفة أثناء المرور عبر دالة معالج (pseudo asm - إنها "jnz" و "call").
  • هذا يعني أيضًا أن عدد الخطوط في جسم الوظيفة (بدون المؤجل) والتدفق سيبدو متشابهين تمامًا (وبالتالي من المحتمل أن يتحول AST إلى نفس الشيء أيضًا)

لذا فإن الفرق الرئيسي هو ما إذا كنا نلف استدعاء الوظيفة الأصلي بـ try (func ()) الذي سيحلل دائمًا var الأخير إلى jnz المكالمة أو نستخدم قيمة الإرجاع الفعلية للقيام بذلك.

أعلم أنه يبدو مختلفًا ، لكنه في الواقع متشابه جدًا في المفهوم.
من ناحية أخرى - إذا جربت المحاولة المعتادة .... اصطاد بالعديد من اللغات الشبيهة بـ C - فسيكون ذلك تطبيقًا مختلفًا تمامًا ، وقابلية قراءة مختلفة ، إلخ.

ومع ذلك ، أفكر بجدية في كتابة اقتراح ، شكرًا على الفكرة.

تضمين التغريدة

لست متأكدًا مما تقترحه من أن الأدوات مناسبة لك. هل تقترح عليهم إخفاء معالجة الخطأ بطريقة ما؟

العكس تمامًا: أقترح أن gopls يمكنه اختياريًا كتابة الخطأ الذي يعالج النموذج المعياري لك.

كما ذكرت في تعليقك الأخير:

سبب هذا الاقتراح هو أن معالجة الأخطاء (على وجه التحديد الرمز المعياري المرتبط) قد تم ذكرها كمسألة مهمة في Go (بجانب نقص الأدوية العامة) من قبل مجتمع Go

لذا فإن جوهر المشكلة هو أن المبرمج ينتهي بكتابة الكثير من التعليمات البرمجية المعيارية. لذا فإن القضية تتعلق بالكتابة وليس القراءة. لذلك ، اقتراحي هو: دع الكمبيوتر (الأدوات / gopls) يقوم بالكتابة للمبرمج عن طريق تحليل توقيع الوظيفة ووضع شروط معالجة الأخطاء المناسبة.

علي سبيل المثال:

// user begins to write this function: 
func openFile(path string) ([]byte, error) {
  file, err := os.Open(path)
  defer file.Close()
  bts, err := ioutil.ReadAll(file)
  return bts, nil
}

ثم يقوم المستخدم بتشغيل الأداة ، ربما فقط عن طريق حفظ الملف (على غرار طريقة عمل gofmt / goimports عادةً) وسيقوم gopls بالنظر في هذه الوظيفة ، وتحليل توقيع الإرجاع الخاص به وزيادة الرمز ليكون هذا:

// user has triggered the tool (by saving the file, or code action)
func openFile(path string) ([]byte, error) {
  file, err := os.Open(path)
  if err != nil {
    return nil, fmt.Errorf("openFile: %w", err)
  }
  defer file.Close()
  bts, err := ioutil.ReadAll(file)
  if err != nil {
    return nil, fmt.Errorf("openFile: %w", err)
  }
  return bts, nil
}

بهذه الطريقة ، نحصل على أفضل ما في العالمين: نحصل على سهولة القراءة / الوضوح لنظام معالجة الأخطاء الحالي ، ولم يكتب المبرمج أي خطأ في معالجة النموذج المعياري. والأفضل من ذلك ، يمكن للمستخدم المضي قدمًا وتعديل كتل معالجة الأخطاء لاحقًا للقيام بسلوك مختلف: يمكن أن يفهم gopls أن الكتلة موجودة ، ولن تقوم بتعديلها.

كيف تعرف الأداة أنني أنوي التعامل مع err لاحقًا في الوظيفة بدلاً من العودة مبكرًا؟ وإن كان ذلك نادرًا ، لكن رمزًا كتبته مع ذلك.

أعتذر إذا تم طرح هذا من قبل ، لكن لم أجد أي ذكر له.

try(DoSomething()) يقرأ جيدًا بالنسبة لي ، وهو أمر منطقي: الكود يحاول فعل شيء ما. يشعر try(err) ، OTOH ، بالارتياح قليلاً ، من حيث المعنى: كيف يمكن للمرء تجربة الخطأ؟ في رأيي ، يمكن للمرء _ اختبار_ أو _ التحقق من_ خطأ ، ولكن _ المحاولة_ لا يبدو أنه صحيح.

إنني أدرك أن السماح بـ try(err) مهم لأسباب تتعلق بالاتساق: أفترض أنه سيكون من الغريب أن يعمل try(DoSomething()) ، لكن err := DoSomething(); try(err) لم ينجح. ومع ذلك ، يبدو أن try(err) يبدو محرجًا بعض الشيء على الصفحة. لا يمكنني التفكير في أي وظائف مضمنة أخرى يمكن جعلها تبدو غريبة بهذه السهولة.

ليس لدي أي اقتراحات محددة بشأن هذا الموضوع ، لكنني مع ذلك أردت أن أبدي هذه الملاحظة.

تضمين التغريدة في الواقع ، كان الاقتراح يجب أن يكون مقابل return فقط ، لكنني أظن أن السماح لأي عبارة واحدة بأن تكون سطرًا واحدًا سيكون أمرًا جيدًا. على سبيل المثال ، في الاختبار ، يمكن للمرء ، دون أي تغييرات في مكتبة الاختبار ، أن يكون لديه

if err != nil { t.Fatal(err) }

يبدو السطر الأول والأخير هو الأكثر وضوحًا (بالنسبة لي) ، خاصةً بمجرد استخدام المرء للتعرف على المحاولة كما هي. مع السطر الأخير ، يتم التحقق من الخطأ بشكل صريح ، ولكن نظرًا لأنه (عادةً) ليس الإجراء الرئيسي ، فهو أكثر قليلاً في الخلفية.

مع السطر الأخير ، يتم إخفاء بعض التكلفة. إذا كنت ترغب في وضع تعليق توضيحي على الخطأ ، والذي أعتقد أن المجتمع قاله بصوت عالٍ إنه أفضل الممارسات المرغوبة ويجب تشجيعه ، فسيتعين على المرء تغيير توقيع الوظيفة لتسمية الحجج ويأمل أن يتم تطبيق defer واحد على كل خروج في جسم الوظيفة ، وإلا فإن try ليس له قيمة ؛ ربما سلبية بسبب سهولة.

ليس لدي المزيد لأضيفه أنني أعتقد أنه لم يُقال بالفعل.


لم أرَ كيف أجيب على هذا السؤال من مستند التصميم. ماذا يفعل هذا الرمز:

func foo() (err error) {
    src := try(getReader())
    if src != nil {
        n, err := src.Read(nil)
        if err == io.EOF {
            return nil
        }
        try(err)
        println(n)
    }
    return nil
}

ما أفهمه هو أنه سيتم إلغاء الأمر

func foo() (err error) {
    tsrc, te := getReader()
    if err != nil {
        err = te
        return
    }
    src := tsrc

    if src != nil {
        n, err := src.Read(nil)
        if err == io.EOF {
            return nil
        }

        terr := err
        if terr != nil {
            err = terr
            return
        }

        println(n)
    }
    return nil
}

الذي فشل في التحويل البرمجي لأن err ظل مظللًا أثناء إرجاع عارية. ألن يتم تجميع هذا؟ إذا كان الأمر كذلك ، فهذا إخفاق دقيق للغاية ، ولا يبدو أنه من غير المرجح أن يحدث. إذا لم يكن الأمر كذلك ، فسيكون هناك المزيد من السكر.

@ marwan-at-work

كما ذكرت في تعليقك الأخير:

سبب هذا الاقتراح هو أن معالجة الأخطاء (على وجه التحديد الرمز المعياري المرتبط) قد تم ذكرها كمسألة مهمة في Go (بجانب نقص الأدوية العامة) من قبل مجتمع Go

لذا فإن جوهر المشكلة هو أن المبرمج ينتهي بكتابة الكثير من التعليمات البرمجية المعيارية. لذا فإن القضية تتعلق بالكتابة وليس القراءة.

أعتقد أن الأمر في الواقع هو العكس تمامًا - بالنسبة لي ، فإن أكبر إزعاج من الخطأ الحالي في معالجة النموذج المعياري ليس الاضطرار إلى كتابته كثيرًا ، بل كيف ينثر المسار السعيد للوظيفة عموديًا عبر الشاشة ، مما يجعل من الصعب فهمه عند ومضة. يظهر التأثير بشكل خاص في كود الإدخال / الإخراج الثقيل ، حيث يوجد عادةً كتلة من الصيغة المعيارية بين كل عمليتين. حتى الإصدار المبسط من CopyFile يأخذ 20 سطرًا تقريبًا على الرغم من أنه لا يؤدي سوى خمس خطوات: المصدر المفتوح ، وتأجيل إغلاق المصدر ، والوجهة المفتوحة ، ونسخ المصدر -> الوجهة ، وإغلاق الوجهة.

هناك مشكلة أخرى تتعلق بالصياغة الحالية ، وهي ، كما أشرت سابقًا ، أنه إذا كان لديك سلسلة من العمليات يمكن لكل منها إرجاع خطأ ، فإن الصيغة الحالية تجبرك على إعطاء أسماء لجميع النتائج الوسيطة ، حتى لو كنت تفضل ذلك اترك بعض مجهول. عندما يحدث هذا ، فإنه يضر أيضًا بالقراءة لأنه يتعين عليك قضاء دورات دماغية في تحليل هذه الأسماء ، على الرغم من أنها ليست مفيدة للغاية.

أحب try على سطر منفصل.
وآمل أن تحدد وظيفة handler بشكل مستقل.

func try(error, optional func(error)error)
func (p *pgStore) DoWork() error {
    tx, err := p.handle.Begin()
    try(err)

    handle := func(err error) error {
        tx.Rollback()
        return err
    }

    var res int64
    _, err = tx.QueryRow(`INSERT INTO table (...) RETURNING c1`, ...).Scan(&res)
    try(err, handle)

    _, err = tx.Exec(`INSERT INTO table2 (...) VALUES ($1)`, res)
    try(err, handle)

    return tx.Commit()
}

zeebo : الأمثلة التي قدمتها هي ترجمة 1: 1. الأول (التقليدي if ) لم يعالج الخطأ ، وكذلك الآخرون. إذا كان أول من تعامل مع الخطأ ، وإذا كان هذا هو المكان الوحيد الذي يتم فيه التحقق من الخطأ في دالة ، فقد يكون المثال الأول (باستخدام if ) هو الخيار المناسب لكتابة الكود. إذا كان هناك العديد من عمليات التحقق من الأخطاء ، وكلها تستخدم نفس معالجة الأخطاء (التفاف) ، على سبيل المثال لأنها تضيف جميعًا معلومات حول الوظيفة الحالية ، يمكن للمرء استخدام عبارة defer للتعامل مع الأخطاء كلها في مكان واحد. اختياريًا ، يمكن للمرء إعادة كتابة if في try 's (أو تركهم وشأنهم). إذا كان هناك العديد من الأخطاء التي يجب التحقق منها ، وجميعهم يتعاملون مع الأخطاء بشكل مختلف (والذي قد يكون علامة على أن اهتمام الوظيفة واسع جدًا وأنه قد يلزم تقسيمها) ، فإن استخدام if 's هو الطريق للذهاب. نعم ، هناك أكثر من طريقة لفعل الشيء نفسه ، والاختيار الصحيح يعتمد على الكود وكذلك على الذوق الشخصي. بينما نسعى جاهدين في Go من أجل "طريقة واحدة للقيام بشيء واحد" ، فإن هذا بالطبع ليس هو الحال بالفعل ، خاصة بالنسبة للبنى المشتركة. على سبيل المثال ، عندما يصبح التسلسل if - else - if طويلًا جدًا ، أحيانًا يكون switch أكثر ملاءمة. في بعض الأحيان ، يعبر إعلان المتغير var x int عن النية بشكل أفضل من x := 0 ، وهكذا دواليك (على الرغم من أن هذا لا يسعد الجميع).

فيما يتعلق بسؤالك حول "إعادة الكتابة": لا ، لن يكون هناك خطأ في التجميع. لاحظ أن إعادة الكتابة تحدث داخليًا (وقد تكون أكثر كفاءة مما يوحي به نمط الكود) ، ولا داعي للمترجم للشكوى من عودة مظللة. في المثال الخاص بك ، قمت بتحديد متغير err محلي في نطاق متداخل. سيظل بإمكان try الوصول المباشر إلى المتغير err بالطبع. قد تبدو إعادة الكتابة أكثر مثل هذا تحت الأغطية.

[محرر] ملاحظة: أفضل إجابة هي: try ليست عودة عارية (على الرغم من أن إعادة الكتابة تبدو مثلها). بعد كل شيء ، يعطي المرء صراحة try وسيطة تحتوي (أو) الخطأ الذي تم إرجاعه إذا لم يكن nil . خطأ الظل للعوائد المجردة هو خطأ في المصدر (وليس الترجمة الأساسية للمصدر. لا يحتاج المترجم إلى الخطأ.

إذا لم يكن نوع الإرجاع النهائي للوظيفة الشاملة من نوع الخطأ ، فهل يمكننا الذعر؟

سيجعل الجهاز المدمج أكثر تنوعًا (مثل إرضاء قلقي في # 32219)

pjebs تم النظر في هذا واتخاذ قرار ضده. يرجى قراءة مستند التصميم التفصيلي (الذي يشير صراحة إلى مشكلتك في هذا الموضوع).

أريد أيضًا أن أشير إلى أن try () يتم التعامل معه كتعبير ، على الرغم من أنه يعمل كعبارة إرجاع. نعم ، أعلم أن المحاولة مبنية على الماكرو ولكن معظم المستخدمين سيستخدمون هذا مثل البرمجة الوظيفية ، على ما أعتقد.

func doSomething() (error, error, error, error, error) {
   ...
}
try(try(try(try(try(doSomething)))))

يقول التصميم أنك استكشفت استخدام panic بدلاً من العودة مع الخطأ.

أنا ألقي الضوء على اختلاف دقيق:

افعل بالضبط ما ينص عليه اقتراحك الحالي ، باستثناء إزالة القيود التي يجب أن تحتوي الوظيفة الشاملة على نوع إرجاع نهائي من النوع error .

إذا لم يكن نوع الإرجاع النهائي error => الذعر
إذا كنت تستخدم إعلانات المتغير على مستوى الحزمة => الذعر (يزيل الحاجة إلى اصطلاح MustXXX( ) )

بالنسبة لاختبارات الوحدة ، تغيير متواضع في اللغة.

mattn ، أشك بشدة في أن أي عدد كبير من الأشخاص سيكتب رمزًا من هذا القبيل.

pjebs ، تلك الدلالات - الذعر إذا لم يكن هناك خطأ ينتج عن الوظيفة الحالية - هو بالضبط ما يناقشه مستند التصميم في https://github.com/golang/proposal/blob/master/design/32437-try-builtin. مناقشة.

علاوة على ذلك ، في محاولة لجعل المحاولة مفيدة ليس فقط داخل الوظائف مع نتيجة خطأ ، تعتمد دلالات المحاولة على السياق: إذا تم استخدام المحاولة على مستوى الحزمة ، أو إذا تم استدعاؤها داخل دالة بدون نتيجة خطأ ، حاول الذعر عند مواجهة خطأ. (جانبا ، بسبب هذه الخاصية ، تم استدعاء المضمن يجب بدلاً من المحاولة في هذا الاقتراح.) يبدو أن المحاولة (أو يجب) التصرف بهذه الطريقة الحساسة للسياق أمرًا طبيعيًا ومفيدًا أيضًا: سيسمح بإلغاء العديد من الوظائف المساعدة المعرفة من قبل المستخدم المستخدمة حاليًا في تعبيرات التهيئة المتغيرة على مستوى الحزمة. سيفتح أيضًا إمكانية استخدام اختبارات الوحدة التجريبية عبر حزمة الاختبار.

ومع ذلك ، تم اعتبار حساسية السياق الخاصة بالمحاولة محفوفة بالمخاطر: على سبيل المثال ، يمكن أن يتغير سلوك الوظيفة التي تحتوي على استدعاءات المحاولة بصمت (من احتمال الذعر إلى عدم الذعر ، والعكس صحيح) إذا تمت إضافة نتيجة خطأ أو إزالتها من التوقيع. بدت هذه الخاصية خطيرة للغاية. كان الحل الواضح هو تقسيم وظيفة المحاولة إلى وظيفتين منفصلتين ، يجب أن تجربهما (تشبه إلى حد بعيد ما اقترحته المشكلة رقم 31442). لكن هذا كان سيتطلب وظيفتين مدمجتين جديدتين ، مع محاولة الاتصال المباشر فقط بالحاجة الفورية لدعم معالجة الأخطاء بشكل أفضل.

pjebs هذا _ بالضبط _ ما رأيناه في اقتراح سابق (انظر المستند التفصيلي ، قسم تكرارات التصميم ، الفقرة الرابعة):

علاوة على ذلك ، في محاولة لجعل المحاولة مفيدة ليس فقط داخل الوظائف مع نتيجة خطأ ، تعتمد دلالات المحاولة على السياق: إذا تم استخدام المحاولة على مستوى الحزمة ، أو إذا تم استدعاؤها داخل دالة بدون نتيجة خطأ ، حاول الذعر عند مواجهة خطأ. (جانبا ، بسبب تلك الخاصية ، تم استدعاء المضمن يجب بدلاً من المحاولة في هذا الاقتراح.)

كان الإجماع (الداخلي لفريق Go Team) هو أنه سيكون من try أن تعتمد على السياق وتتصرف بشكل مختلف تمامًا. على سبيل المثال ، يمكن أن تؤدي إضافة نتيجة خطأ إلى وظيفة (أو إزالتها) إلى تغيير سلوك الوظيفة بصمت من الذعر إلى عدم الذعر (أو العكس).

griesemer شكرًا على التوضيح حول إعادة الكتابة. أنا سعيد لأنه سيتم تجميعها.

أفهم أن الأمثلة كانت ترجمات لم تشرح الأخطاء. لقد حاولت القول إن try يجعل من الصعب عمل تعليقات توضيحية جيدة للأخطاء في المواقف الشائعة ، وأن التعليق التوضيحي للخطأ مهم جدًا للمجتمع. كان جزء كبير من التعليقات حتى الآن يستكشف طرقًا لإضافة دعم أفضل للتعليقات التوضيحية إلى try .

حول الاضطرار إلى التعامل مع الأخطاء بشكل مختلف ، لا أوافق على أنها علامة على أن اهتمام الوظيفة واسع جدًا. لقد كنت أترجم بعض الأمثلة على الكود الحقيقي المطالب به من التعليقات وأضعها في قائمة منسدلة أسفل تعليقي الأصلي ، والمثال في https://github.com/golang/go/issues/32437#issuecomment - 499007288 أعتقد أنه يوضح حالة شائعة بشكل جيد:

func (c *Config) Build() error {
    pkgPath, err := c.load()
    if err != nil { return nil, errors.WithMessage(err, "load config dir") }

    b := bytes.NewBuffer(nil)
    err = templates.ExecuteTemplate(b, "main", c)
    if err != nil { return nil, errors.WithMessage(err, "execute main template") }

    buf, err := format.Source(b.Bytes())
    if err != nil { return nil, errors.WithMessage(err, "format main template") }

    target := fmt.Sprintf("%s.go", filename(pkgPath))
    err = ioutil.WriteFile(target, buf, 0644)
    if err != nil { return nil, errors.WithMessagef(err, "write file %s", target) }
    // ...
}

الغرض من هذه الوظيفة هو تنفيذ قالب على بعض البيانات في ملف. لا أعتقد أنه يجب تقسيمها ، وسيكون من المؤسف أن كل هذه الأخطاء قد اكتسبت للتو السطر الذي تم إنشاؤه بناءً عليه من إرجاء. قد يكون هذا مناسبًا للمطورين ، لكنه أقل فائدة للمستخدمين.

أعتقد أنه أيضًا إشارة إلى مدى دقة أخطاء defer wrap(&err, "message: %v", err) وكيف تعثروا حتى مع مبرمجي Go ذوي الخبرة.


لتلخيص حجتي : أعتقد أن التعليق التوضيحي للخطأ أكثر أهمية من التحقق من الخطأ المستند إلى التعبير ، ويمكننا الحصول على قدر كبير من تقليل الضوضاء من خلال السماح بفحص الأخطاء المستند إلى العبارات ليكون سطرًا واحدًا بدلاً من ثلاثة. شكرا.

griesemer آسف لقد قرأت قسمًا مختلفًا ناقش الذعر ولم أشاهد مناقشة المخاطر.

zeebo شكرا على هذا المثال. يبدو أن استخدام عبارة if هو الخيار الصحيح تمامًا في هذه الحالة. لكن النقطة المأخوذة ، قد يؤدي تنسيق if إلى سطر واحد إلى تبسيط هذا قليلاً.

أود أن أطرح مرة أخرى فكرة المعالج كوسيطة ثانية لـ try ، لكن مع إضافة أن وسيطة المعالج _ مطلوبة_ ، لكن لا يمكن. هذا يجعل معالجة الخطأ هو الخيار الافتراضي ، بدلاً من الاستثناء. في الحالات التي تريد فيها حقًا تمرير الخطأ دون تغيير ، ما عليك سوى توفير قيمة صفرية للمعالج وسيتصرف try تمامًا كما هو الحال في الاقتراح الأصلي ، لكن حجة الصفر ستعمل كإشارة بصرية إلى أن لا يتم معالجة الخطأ. سيكون من الأسهل الإمساك به أثناء مراجعة الكود.

file := try(os.Open("my_file.txt"), nil)

ماذا يجب أن يحدث إذا تم توفير المعالج ولكنه لا شيء؟ هل يجب محاولة الذعر أو التعامل معه على أنه معالج أخطاء غائب؟

كما هو مذكور أعلاه ، سيتصرف try وفقًا للاقتراح الأصلي. لن يكون هناك شيء مثل معالج الأخطاء الغائب ، فقط لا شيء.

ماذا لو تم استدعاء المعالج بخطأ غير صفري ثم قام بإرجاع نتيجة صفرية؟ هل هذا يعني أن الخطأ "ملغى"؟ أم يجب أن تعود وظيفة التضمين بخطأ لا شيء؟

أعتقد أن وظيفة التضمين ستعود مع وجود خطأ صفري. قد يكون الأمر محيرًا للغاية إذا كان بإمكان try متابعة التنفيذ أحيانًا حتى بعد تلقيه قيمة خطأ غير صفرية. سيسمح هذا للمعالجات بـ "الاهتمام" بالخطأ في بعض الظروف. قد يكون هذا السلوك مفيدًا في دالة نمط "get or create" ، على سبيل المثال.

func getOrCreateObject(obj *object) error {
    defaultObjectHandler := func(err error) error {
        if err == ObjectDoesNotExistErr {
            *obj = object{}
            return nil
        }
        return fmt.Errorf("getting or creating object: %v", err)
    }

    *obj = try(db.getObject(), defaultObjectHandler)
}

ولم يكن واضحًا أيضًا ما إذا كان السماح بمعالج أخطاء اختياري سيؤدي إلى تجاهل المبرمجين لمعالجة الأخطاء بشكل كامل. سيكون من السهل أيضًا إجراء معالجة مناسبة للأخطاء في كل مكان ولكن تفوت أي محاولة. وهكذا دواليك.

أعتقد أن كلا من هذه المخاوف يتم تخفيفها من خلال جعل المعالج حجة مطلوبة وغير قابلة للتشغيل. يتطلب من المبرمجين اتخاذ قرار واعي وصريح بأنهم لن يتعاملوا مع أخطائهم.

كمكافأة ، أعتقد أن طلب معالج الأخطاء لا يشجع أيضًا try s المتداخلة بشدة لأنها أقل إيجازًا. قد يرى البعض في هذا جانبًا سلبيًا ، لكنني أعتقد أنه مفيد.

velovix أحب الفكرة ، لكن لماذا يجب أن يكون معالج الأخطاء مطلوبًا؟ ألا يمكن أن يكون nil افتراضيًا؟ لماذا نحتاج إلى "دليل بصري"؟

griesemer ماذا لو تم تبني فكرة velovix ولكن مع builtin تحتوي على وظيفة محددة مسبقًا تحول الخطأ إلى الذعر وقمنا بإزالة مطلب أن دالة over-arching لها قيمة إرجاع خطأ؟

الفكرة هي ، إذا كانت الوظيفة الشاملة لا تُرجع الخطأ ، فإن استخدام try بدون معالج الأخطاء يعد خطأ في وقت الترجمة.

يمكن أيضًا استخدام معالج الأخطاء للالتفاف على الخطأ الذي سيتم إرجاعه قريبًا باستخدام مكتبات متنوعة وما إلى ذلك في موقع الخطأ ، بدلاً من defer في الجزء العلوي الذي يعدل خطأ تم إرجاعه مسمى.

تضمين التغريدة

لماذا يجب أن يكون معالج الأخطاء مطلوبًا؟ لا يمكن أن يكون لا شيء افتراضيا؟ لماذا نحتاج إلى "دليل بصري"؟

هذا هو لمعالجة المخاوف التي

  1. اقتراح try كما هو الآن قد يثني الناس عن تقديم سياق لأخطائهم لأن القيام بذلك ليس واضحًا تمامًا.

إن وجود معالج في المقام الأول يجعل توفير السياق أسهل ، ووجود المعالج وسيطة مطلوبة يرسل رسالة: الحالة الشائعة والموصى بها هي معالجة الخطأ أو تحديد سياقه بطريقة ما ، وليس مجرد تمريره إلى المكدس. إنه يتماشى مع التوصية العامة من مجتمع Go.

  1. قلق من وثيقة الاقتراح الأصلي. نقلته في تعليقي الأول:

ولم يكن واضحًا أيضًا ما إذا كان السماح بمعالج أخطاء اختياري سيؤدي إلى تجاهل المبرمجين لمعالجة الأخطاء بشكل كامل. سيكون من السهل أيضًا إجراء معالجة مناسبة للأخطاء في كل مكان ولكن تفوت أي محاولة. وهكذا دواليك.

الاضطرار إلى تمرير nil صريح يجعل من الصعب نسيان التعامل مع الخطأ بشكل صحيح. عليك أن تقرر صراحة عدم معالجة الخطأ بدلاً من القيام بذلك ضمنيًا عن طريق ترك حجة.

مزيد من التفكير في العودة المشروطة المذكورة بإيجاز على https://github.com/golang/go/issues/32437#issuecomment -498947603.
يبدو
return if f, err := os.Open("/my/file/path"); err != nil
سيكون أكثر توافقًا مع كيفية ظهور Go في if .

إذا أضفنا قاعدة لبيان return if فإن ذلك
عندما لا يكون تعبير الشرط الأخير (مثل err != nil ) موجودًا ،والمتغير الأخير للإعلان في عبارة return if هو من النوع error ،ثم ستتم مقارنة قيمة المتغير الأخير تلقائيًا بـ nil كشرط ضمني.

ثم يمكن اختصار العبارة return if إلى:
return if f, err := os.Open("my/file/path")

وهو قريب جدًا من نسبة التشويش التي يوفرها try .
إذا قمنا بتغيير return if إلى try ، فسيصبح
try f, err := os.Open("my/file/path")
يصبح مرة أخرى مشابهًا للصيغ الأخرى المقترحة لـ try في هذا الموضوع ، على الأقل من الناحية التركيبية.
شخصيا ، ما زلت أفضل return if على try في هذه الحالة لأنه يجعل نقاط الخروج من الوظيفة واضحة للغاية. على سبيل المثال ، عند تصحيح الأخطاء ، غالبًا ما أبرز الكلمة الأساسية return داخل المحرر لتحديد جميع نقاط الخروج لوظيفة كبيرة.

لسوء الحظ ، لا يبدو أنه يساعد بشكل كافٍ في إزعاج إدراج تسجيل تصحيح الأخطاء أيضًا.
ما لم نسمح أيضًا بكتلة body مقابل return if ، مثل
إبداعي:

        return if f, err := os.Open("my/path") 

عند التصحيح:

-       return if f, err := os.Open("my/path") 
+       return if f, err := os.Open("my/path") {
+               fmt.Printf("DEBUG: os.Open: %s\n", err)
+       }

أفترض أن معنى كتلة الجسم البالغة return if واضح. سيتم تنفيذه قبل defer والعودة.

ومع ذلك ، ليس لدي شكاوى من نهج معالجة الأخطاء الحالي في Go.
أنا أكثر قلقًا بشأن كيفية تأثير إضافة معالجة الأخطاء الجديدة على جودة Go الحالية.

velovix لقد أحببنا تمامًا فكرة try مع وظيفة معالج صريحة كوسيطة ثانية. ولكن كان هناك الكثير من الأسئلة التي لم يكن لها إجابات واضحة ، كما ينص مستند التصميم. لقد أجبت على بعضها بطريقة تبدو معقولة لك. من المحتمل جدًا (وكانت تلك تجربتنا داخل فريق Go Team) ، أن يعتقد شخص آخر أن الإجابة الصحيحة مختلفة تمامًا. على سبيل المثال ، أنت تعلن أنه يجب تقديم وسيطة المعالج دائمًا ، ولكن يمكن أن تكون nil ، لتوضيح ذلك ، فنحن لا نهتم بمعالجة الخطأ. الآن ماذا يحدث إذا قدم المرء قيمة دالة (ليست قيمة حرفية nil ) ، وقيمة هذه الوظيفة (مخزنة في متغير) تصادف أنها لا شيء؟ قياسا على القيمة الصريحة nil ، لا يلزم أي معالجة. لكن قد يجادل آخرون بأن هذا خطأ في الكود. أو ، بدلاً من ذلك ، يمكن للمرء أن يسمح بحجج معالِج لا قيمة له ، ولكن بعد ذلك قد تعالج الوظيفة أخطاء معالجة غير متسقة في بعض الحالات وليس في حالات أخرى ، وليس من الواضح بالضرورة من الكود الذي يفعله المرء ، لأنه يبدو كما لو كان المعالج موجودًا دائمًا . حجة أخرى هي أنه من الأفضل أن يكون لديك إعلان عالي المستوى لمعالج الأخطاء لأن ذلك يوضح تمامًا أن الوظيفة تتعامل مع الأخطاء. ومن هنا جاء defer . ربما هناك المزيد.

سيكون من الجيد معرفة المزيد عن هذا القلق. إن أسلوب الترميز الحالي الذي يستخدم عبارات if لاختبار الأخطاء واضح بقدر الإمكان. من السهل جدًا إضافة معلومات إضافية إلى الخطأ ، على أساس فردي (لكل حالة). غالبًا ما يكون من المنطقي معالجة جميع الأخطاء المكتشفة في وظيفة بطريقة موحدة ، والتي يمكن إجراؤها من خلال التأجيل - وهذا ممكن بالفعل الآن. إن حقيقة أننا نمتلك بالفعل جميع الأدوات للتعامل مع الأخطاء بشكل جيد في اللغة ، ومشكلة بنية المعالج التي لا تكون متعامدة للتأجيل ، هي التي دفعتنا إلى ترك آلية جديدة فقط لزيادة الأخطاء.

griesemer - IIUC ، أنت تقول أنه بالنسبة لسياقات الخطأ المعتمدة على موقع الاستدعاء ، فإن عبارة if الحالية جيدة. حيث إن هذه الوظيفة الجديدة try مفيدة للحالات التي يكون فيها التعامل مع أخطاء متعددة في مكان واحد مفيدًا.

أعتقد أن القلق كان أنه في حين أن القيام ببساطة بـ if err != nil { return err} قد يكون جيدًا في بعض الحالات ، فإنه يوصى عادةً بتزيين الخطأ قبل العودة. ويبدو أن هذا الاقتراح يعالج الاقتراح السابق ولا يفعل الكثير للأخير. مما يعني أنه سيتم تشجيع الأشخاص على استخدام نمط الإرجاع السهل.

agnivade أنت محق ، هذا الاقتراح لا يفعل شيئًا بالضبط للمساعدة في زخرفة الخطأ (لكن للتوصية باستخدام defer ). أحد الأسباب هو أن آليات اللغة لهذا موجودة بالفعل. بمجرد طلب زخرفة الخطأ ، خاصةً على أساس الخطأ الفردي ، فإن المقدار الإضافي من النص المصدر لرمز الزخرفة يجعل if أقل صعوبة بالمقارنة. إنها الحالات التي لا يلزم فيها الزخرفة ، أو حيث تكون الزخرفة دائمًا كما هي ، حيث تصبح اللوحة المعيارية مصدر إزعاج مرئي ثم تنتقص من الرمز المهم.

يتم تشجيع الناس بالفعل على استخدام نمط الإرجاع السهل ، try أو بدون try ، هناك القليل للكتابة. تعال إلى التفكير في الأمر ، الطريقة الوحيدة لتشجيع زخرفة الأخطاء هي جعلها إلزامية ، لأنه بغض النظر عن الدعم اللغوي المتاح ، فإن أخطاء التزيين ستتطلب المزيد من العمل.

تتمثل إحدى طرق تحسين الصفقة في السماح فقط بشيء مثل try (أو أي تدوين اختصار مماثل) _ if_ يتم توفير معالج صريح (ربما فارغ) في مكان ما (لاحظ أن تصميم المسودة الأصلي لم يكن به مثل هذا شرط إما).

لست متأكدًا من أننا نريد الذهاب بعيدًا. اسمحوا لي أن أعيد التأكيد على أن الكثير من الشفرات الدقيقة تمامًا ، على سبيل المثال الأجزاء الداخلية للمكتبة ، لا تحتاج إلى تزيين الأخطاء في كل مكان. من الجيد نشر الأخطاء وتزيينها قبل مغادرتها لنقاط دخول واجهة برمجة التطبيقات ، على سبيل المثال. (في الواقع ، فإن تزيينها في كل مكان لن يؤدي إلا إلى أخطاء زخرفية زائدة ، والتي ، مع إخفاء الجناة الحقيقيين ، تجعل من الصعب تحديد الأخطاء المهمة ؛ مثل الكثير من التسجيل المطول المفرط يمكن أن يجعل من الصعب رؤية ما يحدث بالفعل).

أعتقد أنه يمكننا أيضًا إضافة وظيفة catch ، والتي ستكون زوجًا رائعًا ، لذلك:

func a() int {
  x := randInt()
  // let's assume that this is what recruiters should "fix" for us
  // or this happens in 3rd-party package.
  if x % 1337 != 0 {
    panic("not l33t enough")
  }
  return x
}

func b() error {
  // if a() panics, then x = 0, err = error{"not l33t enough"}
  x, err := catch(a())
  if err != nil {
    return err
  }
  sendSomewhereElse(x)
  return nil
}

// which could be simplified even further

func c() error {
  x := try(catch(a()))
  sendSomewhereElse(x)
  return nil
}

في هذا المثال ، قد يؤدي catch() recover() إلى حالة من الذعر و return ..., panicValue .
بالطبع ، لدينا حالة ركنية واضحة حيث لدينا func ، والتي تُرجع أيضًا خطأ. في هذه الحالة ، أعتقد أنه سيكون من المناسب مجرد تمرير قيمة الخطأ.

لذلك ، في الأساس ، يمكنك بعد ذلك استخدام catch () لاستعادة () الذعر بالفعل وتحويلها إلى أخطاء.
هذا يبدو مضحكًا جدًا بالنسبة لي ، لأن Go ليس لديه استثناءات في الواقع ، ولكن في هذه الحالة لدينا نمط محاولة () - catch () أنيق جدًا ، وهذا أيضًا لا ينبغي أن يفجر قاعدة التعليمات البرمجية بالكامل بشيء مثل Java ( catch(Throwable) في الرئيسي + throws LiterallyAnything ). يمكنك بسهولة معالجة ذعر شخص ما مثل تلك الأخطاء المعتادة. لدي حاليًا حوالي 6 مليون + LoC in Go في مشروعي الحالي ، وأعتقد أن هذا من شأنه أن يبسط الأمور على الأقل بالنسبة لي.

griesemer شكرًا على تلخيص المناقشة.

ألاحظ أن هناك نقطة واحدة مفقودة: جادل بعض الأشخاص بأنه يجب علينا الانتظار مع هذه الميزة حتى نحصل على الأدوية الجنيسة ، والتي نأمل أن تسمح لنا بحل هذه المشكلة بطريقة أكثر أناقة.

علاوة على ذلك ، أحب اقتراح velovix ، وبينما أقدر أن هذا يثير بعض الأسئلة كما هو موضح في المواصفات ، أعتقد أنه يمكن الإجابة عليها بسهولة بطريقة معقولة ، كما فعلvelovix بالفعل.

علي سبيل المثال:

  • ماذا يحدث إذا قدم المرء قيمة دالة (وليس قيمة حرفية صفرية) ، وكانت قيمة هذه الوظيفة (مخزنة في متغير) صفرًا؟ => لا تتعامل مع الخطأ ، الفترة. يكون هذا مفيدًا في حالة اعتماد معالجة الأخطاء على السياق وتم تعيين متغير المعالج اعتمادًا على ما إذا كانت معالجة الخطأ مطلوبة أم لا. إنه ليس خطأ ، بل هو ميزة. :)

  • حجة أخرى هي أنه من الأفضل أن يكون لديك إعلان عالي المستوى لمعالج الأخطاء لأن ذلك يوضح تمامًا أن الوظيفة تتعامل مع الأخطاء. => لذا حدد معالج الخطأ في الجزء العلوي من الوظيفة كدالة إغلاق مسماة واستخدمها ، لذلك من الواضح أيضًا أنه يجب معالجة الخطأ. هذه ليست مشكلة خطيرة ، بل هي أكثر من متطلبات الأسلوب.

ما هي المخاوف الأخرى التي كانت هناك؟ أنا متأكد من أنه يمكن الرد عليهم جميعًا بطريقة مماثلة بطريقة معقولة.

أخيرًا ، كما تقول ، "تتمثل إحدى طرق تحسين الصفقة في السماح فقط بشيء مثل المحاولة (أو أي تدوين اختصار مماثل) إذا تم توفير معالج واضح (ربما فارغ) في مكان ما". أعتقد أنه إذا كان علينا المضي قدمًا في هذا الاقتراح ، فيجب أن نأخذه في الواقع "إلى هذا الحد" ، لتشجيع معالجة الخطأ "الصريح أفضل من الضمني".

تضمين التغريدة

الآن ماذا يحدث إذا قدم المرء قيمة دالة (وليس قيمة حرفية صفرية) ، وقيمة هذه الوظيفة (مخزنة في متغير) تصادف أنها لا شيء؟ عن طريق القياس مع القيمة الصريحة للصفر ، لا يلزم أي معالجة. لكن قد يجادل آخرون بأن هذا خطأ في الكود.

من الناحية النظرية ، يبدو هذا وكأنه مسك محتمل ، على الرغم من أنني أجد صعوبة في تصور موقف معقول حيث ينتهي الأمر بالمعالج إلى أن يكون معدومًا عن طريق الصدفة. أتخيل أن المعالجات ستأتي غالبًا إما من وظيفة الأداة المساعدة المحددة في مكان آخر ، أو كإغلاق محدد في الوظيفة نفسها. لا يحتمل أن يصبح أي منهما معدومًا بشكل غير متوقع. يمكن أن يكون لديك سيناريو نظريًا حيث يتم تمرير وظائف المعالج كوسيطات لوظائف أخرى ، ولكن في نظري يبدو الأمر بعيد المنال. ربما هناك نمط مثل هذا لست على علم به.

حجة أخرى هي أنه من الأفضل أن يكون لديك إعلان عالي المستوى لمعالج الأخطاء لأن ذلك يوضح تمامًا أن الوظيفة تتعامل مع الأخطاء. ومن هنا جاء defer .

كما ذكر beoran ، فإن تعريف المعالج على أنه إغلاق بالقرب من الجزء العلوي من الوظيفة سيبدو مشابهًا جدًا في الأسلوب ، وهذا ما أتوقعه شخصيًا أن يستخدم الأشخاص المعالجات بشكل شائع. على الرغم من أنني أقدر الوضوح الذي اكتسبته حقيقة أن جميع الوظائف التي تتعامل مع الأخطاء ستستخدم defer ، فقد يصبح أقل وضوحًا عندما تحتاج الوظيفة إلى التمحور في إستراتيجية معالجة الأخطاء الخاصة بها في منتصف الطريق أسفل الوظيفة. بعد ذلك ، سيكون هناك defer s للنظر إليهما وسيتعين على القارئ التفكير في كيفية تفاعلهما مع بعضهما البعض. هذا موقف أعتقد فيه أن حجة المعالج ستكون أكثر وضوحًا وراحة ، وأعتقد أن هذا سيكون سيناريو شائعًا نسبيًا.

هل من الممكن جعله يعمل بدون أقواس؟

أي شيء مثل:
a := try func(some)

Cyberax - كما ذكرنا سابقًا ، من الضروري جدًا قراءة مستند التصميم بعناية قبل النشر. نظرًا لأن هذه مشكلة ذات حركة مرور عالية ، فقد اشترك فيها الكثير من الأشخاص.

يناقش المستند العوامل مقابل الوظائف بالتفصيل.

أنا أحب هذا أكثر بكثير مما أحببت إصدار أغسطس.

أعتقد أن الكثير من التعليقات السلبية ، التي لا تتعارض تمامًا مع عمليات الإرجاع بدون الكلمة الرئيسية return ، يمكن تلخيصها في نقطتين:

  1. لا يحب الأشخاص معلمات النتائج المسماة ، والتي ستكون مطلوبة في معظم الحالات
  2. لا يشجع على إضافة سياق مفصل للأخطاء

انظر على سبيل المثال:

نقض هذين الاعتراضين على التوالي:

  1. "قررنا أن [معلمات النتائج المسماة] كانت جيدة"
  2. "لن يجعلك أحد تستخدم try " / لن يكون مناسبًا لـ 100٪ من الحالات

ليس لدي حقًا ما أقوله عن 1 (لا أشعر بقوة تجاه ذلك). ولكن فيما يتعلق بـ 2 ، فقد لاحظت أن الاقتراح المهيب لم يكن به هذه المشكلة ، كما أن معظم المقترحات المضادة لا تحتوي أيضًا على هذه المشكلة.

على وجه الخصوص ، لم يواجه الاقتراح المضاد tryf (الذي تم نشره بشكل مستقل مرتين في سلسلة الرسائل هذه) ولا الاقتراح المضاد try(X, handlefn) (الذي كان جزءًا من تكرارات التصميم) هذه المشكلة.

أعتقد أنه من الصعب المجادلة بأن try ، كما هو ، سوف يدفع الناس بعيدًا عن تزيين الأخطاء بسياق ذي صلة ونحو زخرفة خطأ عامة واحدة لكل وظيفة.

لهذه الأسباب أعتقد أن الأمر يستحق محاولة معالجة هذه المشكلة وأريد اقتراح حل ممكن:

  1. حاليًا لا يمكن أن تكون المعلمة defer سوى استدعاء دالة أو طريقة. اسمح لـ defer أن يكون لديك أيضًا اسم دالة أو دالة حرفية ، على سبيل المثال
defer func(...) {...}
defer packageName.functionName
  1. عندما يواجه الذعر أو التأجيل هذا النوع من التأجيل ، فسوف يستدعيون الوظيفة لتمرير القيمة الصفرية لجميع معلماتهم

  2. السماح لـ try بالحصول على أكثر من معلمة واحدة

  3. عندما يصادف try النوع الجديد من التأجيل ، فإنه سوف يستدعي الوظيفة التي تمرر مؤشرًا إلى قيمة الخطأ كمعامل أول متبوعًا بجميع المعلمات الخاصة بـ try ، باستثناء المعلمة الأولى.

على سبيل المثال ، معطى:

func errorfn() error {
    return errors.New("an error")
}


func f(fail bool) {
    defer func(err *error, a, b, c int) {
        fmt.Printf("a=%d b=%d c=%d\n", a, b, c)
    }
    if fail {
        try(errorfn, 1, 2, 3)
    }
}

سيحدث ما يلي:

f(false)        // prints "a=0 b=0 c=0"
f(true)         // prints "a=1 b=2 c=3"

يمكن إعادة كتابة الكود الموجود في https://github.com/golang/go/issues/32437#issuecomment -499309304 بواسطة zeebo على النحو التالي:

func (c *Config) Build() error {
    defer func(err *error, msg string, args ...interface{}) {
        if *err == nil || msg == "" {
            return
        }
        *err = errors.WithMessagef(err, msg, args...)
    }
    pkgPath := try(c.load(), "load config dir")

    b := bytes.NewBuffer(nil)
    try(templates.ExecuteTemplate(b, "main", c), "execute main template")

    buf := try(format.Source(b.Bytes()), "format main template")

    target := fmt.Sprintf("%s.go", filename(pkgPath))
    try(ioutil.WriteFile(target, buf, 0644), "write file %s", target)
    // ...
}

وتعريف ErrorHandlef على أنه:

func HandleErrorf(err *error, format string, args ...interface{}) {
        if *err != nil && format != "" {
                *err = fmt.Errorf(format + ": %v", append(args, *err)...)
        }
}

كل شخص يسعى للحصول عليه بعد tryf مجانًا ، بدون سحب سلاسل تنسيق نمط fmt إلى اللغة الأساسية.

هذه الميزة متوافقة مع الإصدارات السابقة لأن defer لا يسمح بتعبيرات الوظيفة كوسيطة لها. لا يقدم كلمات رئيسية جديدة.
التغييرات التي يجب إجراؤها لتنفيذه ، بالإضافة إلى تلك الموضحة في https://github.com/golang/proposal/blob/master/design/32437-try-builtin.md ، هي:

  1. تعليم المحلل اللغوي حول النوع الجديد من التأجيل
  2. قم بتغيير مدقق النوع للتحقق من أن جميع المؤجِّلات التي لها وظيفة كمعامل (بدلاً من استدعاء) داخل دالة لها نفس التوقيع أيضًا
  3. قم بتغيير مدقق النوع للتحقق من أن المعلمات التي تم تمريرها إلى try تطابق توقيع الوظائف التي تم تمريرها إلى defer
  4. تغيير الخلفية (؟) لإنشاء استدعاء deferproc المناسب
  5. قم بتغيير تنفيذ try لنسخ وسيطاته إلى وسيطات المكالمة المؤجلة عندما يواجه استدعاء مؤجل بواسطة النوع الجديد من المؤجل.

بعد تعقيدات مسودة التصميم check/handle ، فوجئت بسرور لرؤية هذه الأرض المقترحة الأكثر بساطة وواقعية على الرغم من أنني أشعر بخيبة أمل لأنه كان هناك الكثير من التراجع ضدها.

من المسلم به أن الكثير من ردود الأفعال تأتي من أشخاص سعداء تمامًا بالإسهاب الحالي (موقف معقول تمامًا يجب اتخاذه) والذين من المفترض ألا يرحبوا بأي اقتراح لتخفيف ذلك. بالنسبة لبقيتنا ، أعتقد أن هذا الاقتراح يصل إلى النقطة المثالية المتمثلة في كونه بسيطًا ومريحًا ، ولا يحاول القيام بالكثير من العمل والتعامل بشكل جيد مع تقنيات معالجة الأخطاء الحالية التي يمكنك دائمًا التراجع عنها إذا كان try لم يفعل ما تريده بالضبط.

فيما يتعلق ببعض النقاط المحددة:

  1. الشيء الوحيد الذي لا يعجبني في الاقتراح هو الحاجة إلى وجود معلمة إرجاع خطأ مسمى عند استخدام defer ولكن بعد أن قلت ذلك ، لا يمكنني التفكير في أي حل آخر لا يتعارض معه الطريقة التي تعمل بها بقية اللغة. لذلك أعتقد أنه سيتعين علينا فقط قبول هذا إذا تم اعتماد الاقتراح.

  2. من المؤسف أن try لا يعمل بشكل جيد مع حزمة الاختبار للوظائف التي لا تُرجع قيمة الخطأ. سيكون الحل المفضل لدي لهذا هو الحصول على وظيفة مضمنة ثانية (ربما ptry أو must ) والتي دائمًا ما تصيب بالذعر بدلاً من إرجاعها عند مواجهة خطأ غير معدوم وبالتالي يمكن أن تكون كذلك تستخدم مع الوظائف المذكورة أعلاه (بما في ذلك main ). على الرغم من رفض هذه الفكرة في التكرار الحالي للاقتراح ، فقد شكلت انطباعًا بأنها كانت "دعوة قريبة" وبالتالي قد تكون مؤهلة لإعادة النظر فيها.

  3. أعتقد أنه سيكون من الصعب على الناس فهم ما يفعله go try(f) أو defer try(f) ولذلك من الأفضل منعهم تمامًا.

  4. أتفق مع أولئك الذين يعتقدون أن تقنيات معالجة الأخطاء الحالية ستبدو أقل طولًا إذا لم يقم go fmt بإعادة كتابة سطر واحد if كشوفات الحساب. أنا شخصياً أفضل قاعدة بسيطة وهي أن هذا سيسمح به لأي _ بيان واحد if سواء كان الأمر يتعلق بمعالجة الأخطاء أم لا. في الواقع ، لم أتمكن أبدًا من فهم سبب عدم السماح بذلك حاليًا عند كتابة وظائف أحادية السطر حيث يتم وضع الجسم على نفس السطر الذي يُسمح فيه بالإعلان.

في حالة أخطاء التزيين

func myfunc()( err error){
try(thing())
defer func(){
err = errors.Wrap(err,"more context")
}()
}

هذا يبدو أكثر استطالة وألمًا من النماذج الحالية ، وليس موجزًا ​​مثل الفحص / المقبض. يعتبر متغير try غير المغلف () أكثر إيجازًا ، لكن يبدو أن الأشخاص سينتهي بهم الأمر باستخدام مزيج من المحاولة وإرجاع الخطأ العادي. لست متأكدًا من أنني أحب فكرة المزج بين المحاولة وإرجاع الخطأ البسيط ، لكنني بيعت تمامًا لأخطاء التزيين (وأتطلع إلى Is / As). اجعلني أعتقد أنه في حين أن هذا أنيق من الناحية التركيبية ، فأنا لست متأكدًا من أنني أرغب في استخدامه بالفعل. شعرت بفحص / مقبض بشيء أود أن أحتضنه بشكل أكثر شمولاً.

أنا حقًا أحب بساطة هذا ونهج "افعل شيئًا واحدًا جيدًا". في مترجم GoAWK الخاص بي سيكون مفيدًا للغاية - لدي حوالي 100 if err != nil { return nil } تركيبات يمكن تبسيطها وترتيبها ، وهذا في قاعدة بيانات صغيرة إلى حد ما.

لقد قرأت تبرير الاقتراح لجعله مدمجًا وليس كلمة رئيسية ، ويتلخص ذلك في عدم الاضطرار إلى ضبط المحلل اللغوي. لكن ليس هذا قدرًا ضئيلًا نسبيًا من الألم للمترجمين وكتاب الأدوات ، في حين أن الحصول على أقواس إضافية و this-look-like-a-function-but-not read the issues سيكون شيئًا كل المبرمجين و الكود- القراء يجب أن يتحملوا. في رأيي ، الحجة (عذر؟ :-) القائلة بأن "ولكن panic() يتحكم في التدفق" لا تقطعها ، لأن الذعر والتعافي بطبيعتهما استثنائيان ، بينما try() سوف أن تكون معالجة الأخطاء الطبيعية والتحكم في التدفق.

سأقدر ذلك بالتأكيد حتى لو استمر هذا كما هو ، ولكن أفضل تفضيلي هو أن يكون تدفق التحكم الطبيعي واضحًا ، على سبيل المثال ، يتم عبر كلمة رئيسية.

أنا أؤيد هذا الاقتراح. إنه يتجنب أكبر حجوزاتي بشأن الاقتراح السابق: عدم التعامد البالغ handle بالنسبة إلى defer .

أود أن أذكر جانبين لا أعتقد أنه تم تسليط الضوء عليهما أعلاه.

أولاً ، على الرغم من أن هذا الاقتراح لا يجعل من السهل إضافة نص خطأ خاص بالسياق إلى خطأ ، فإنه _ يفعل _ يجعل من السهل إضافة معلومات تتبع أخطاء إطار المكدس إلى خطأ: https://play.golang.org/p / YL1MoqR08E6

ثانيًا ، يمكن القول إن try هو حل عادل لمعظم المشاكل الكامنة وراء https://github.com/golang/go/issues/19642. لأخذ مثال من هذه المشكلة ، يمكنك استخدام try لتجنب كتابة جميع قيم الإرجاع في كل مرة. من المحتمل أن يكون هذا مفيدًا أيضًا عند إرجاع أنواع البنى حسب القيمة بأسماء طويلة.

func (f *Font) viewGlyphData(b *Buffer, x GlyphIndex) (buf []byte, offset, length uint32, err error) {
    xx := int(x)
    if f.NumGlyphs() <= xx {
        try(ErrNotFound)
    }
    i := f.cached.locations[xx+0]
    j := f.cached.locations[xx+1]
    if j < i {
        try(errInvalidGlyphDataLength)
    }
    if j-i > maxGlyphDataLength {
        try(errUnsupportedGlyphDataLength)
    }
    buf, err = b.view(&f.src, int(i), int(j-i))
    return buf, i, j - i, err
}

أنا أحب هذا الاقتراح أيضا.

ولدي طلب.

مثل make ، هل يمكننا السماح لـ try بأخذ عدد متغير من المعلمات

  • حاول (و):
    على النحو الوارد أعلاه.
    قيمة خطأ الإرجاع إلزامية (كمعامل إرجاع آخر).
    نموذج الاستخدام الأكثر شيوعًا
  • حاول (f، doPanic bool):
    على النحو الوارد أعلاه ، ولكن إذا كنت تفعل ذلك ، فهلع (يخطئ) بدلاً من العودة.
    في هذا الوضع ، ليس من الضروري إرجاع قيمة الخطأ.
  • حاول (f، fn):
    على النحو الوارد أعلاه ، ولكن اتصل fn (يخطئ) قبل العودة.
    في هذا الوضع ، ليس من الضروري إرجاع قيمة الخطأ.

بهذه الطريقة ، يمكن أن يتعامل مع جميع حالات الاستخدام ، بينما لا يزال صريحًا. مزاياه:

  • صريح دائمًا - لا داعي للاستدلال على الذعر أو ضبط الخطأ والعودة
  • يدعم معالج خاص بالسياق (لكن بدون سلسلة معالج)
  • يدعم حالات الاستخدام التي لا يوجد فيها متغير إرجاع خطأ
  • يجب أن يدعم (...) دلالات

في حين أن التكرار if err !=nil { return ... err } هو بالتأكيد تلعثم قبيح ، فأنا مع هؤلاء
الذين يعتقدون أن اقتراح try () منخفض للغاية فيما يتعلق بسهولة القراءة وغير صريح إلى حد ما.
استخدام المرتجعات المسماة مشكلة أيضًا.

إذا كان هذا النوع من الترتيب مطلوبًا ، فلماذا لا يكون السكر النحوي هو try(err) لـ
if err !=nil { return err } :

file, err := os.Open("file.go")
try(err)

بالنسبة

file, err := os.Open("file.go")
if err != nil {
   return err
}

وإذا كان هناك أكثر من قيمة إرجاع واحدة ، فقد يكون try(err) return t1, ... tn, err
حيث t1، ... tn هي القيم الصفرية لقيم الإرجاع الأخرى.

يمكن أن يغني هذا الاقتراح عن الحاجة إلى قيم الإرجاع المسماة ويكون ،
في رأيي ، أسهل في الفهم وأكثر قابلية للقراءة.

أفضل من ذلك ، أعتقد أنه سيكون:

file, try(err) := os.Open("file.go")

او حتى

file, err? := os.Open("file.go")

هذا الأخير متوافق مع الإصدارات السابقة (؟ غير مسموح به حاليًا في المعرفات).

(يرتبط هذا الاقتراح بـ https://github.com/golang/go/wiki/Go2ErrorHandlingFeedback#recurring-themes. ولكن تبدو أمثلة السمات المتكررة مختلفة لأن ذلك كان في مرحلة كان لا يزال فيها المقبض الصريح قيد المناقشة بدلاً من ترك هذا إلى تأجيل.)

شكرًا لفريق Go على هذا الاقتراح الدقيق والمثير للاهتمام.

تعليق rogpeppe إذا قام try بإضافة إطار المكدس تلقائيًا ، وليس أنا ، فأنا موافق على عدم تشجيع إضافة السياق.

aarzilli - وفقًا لاقتراحك ، هل شرط التأجيل إلزامي في كل مرة نعطي فيها معلمات إضافية لـ tryf ؟

ماذا يحدث إذا فعلت

try(ioutil.WriteFile(target, buf, 0644), "write file %s", target)

ولا تكتب وظيفة مؤجلة؟

تضمين التغريدة

ماذا يحدث إذا فعلت (...) ولم أكتب وظيفة مؤجلة؟

خطأ مطبعي.

في رأيي ، استخدام try لتجنب كتابة جميع قيم الإرجاع هو في الواقع مجرد ضربة أخرى ضده.

func (f *Font) viewGlyphData(b *Buffer, x GlyphIndex) (buf []byte, offset, length uint32, err error) {
    xx := int(x)
    if f.NumGlyphs() <= xx {
        try(ErrNotFound)
    }
    //...

أتفهم تمامًا الرغبة في تجنب الاضطرار إلى كتابة return nil, 0, 0, ErrNotFound ، لكنني أفضل حل هذه المشكلة بطريقة أخرى.

كلمة try لا تعني "رجوع". وهذه هي الطريقة التي يتم استخدامها بها هنا. أفضل في الواقع تغيير الاقتراح بحيث لا يمكن أن يأخذ $ # try error قيمة $ # $ 5 $ # $ مباشرة ، لأنني لا أريد أبدًا أن يكتب أي شخص رمزًا كهذا ^^. يقرأ خطأ . إذا عرضت هذا الرمز على مبتدئ ، فلن يكون لديهم أدنى فكرة عما كانت تفعله هذه المحاولة.

إذا أردنا طريقة لإرجاع القيم الافتراضية وقيمة الخطأ بسهولة ، فلنحل ذلك بشكل منفصل. ربما آخر مثل بنيت

return default(ErrNotFound)

على الأقل هذا يقرأ مع نوع من المنطق.

لكن دعونا لا نسيء استخدام try لحل مشكلة أخرى.

natefinch إذا تم تسمية try المضمن باسم check كما هو الحال في الاقتراح الأصلي ، فسيكون check(err) الذي يقرأ أفضل بكثير ، imo.

إذا وضعنا ذلك جانبًا ، فأنا لا أعرف ما إذا كان كتابة try(err) يعد إساءة حقًا. إنه يقع خارج التعريف بشكل نظيف. ولكن ، من ناحية أخرى ، هذا يعني أيضًا أن هذا قانوني:

a, b := try(1, f(), err)

أعتقد أن مشكلتي الرئيسية مع try هي أنه في الحقيقة مجرد panic يرتفع مستوى واحدًا فقط ... إلا أنه على عكس الذعر ، فهو تعبير ، وليس بيان ، لذا يمكنك الاختباء في منتصف بيان في مكان ما. هذا يكاد يجعل الأمر أسوأ من الذعر.

natefinch إذا كنت تصوره على أنه ذعر يرتفع مستوى واحدًا ثم تقوم بأشياء أخرى ، فإنه يبدو فوضويًا جدًا. ومع ذلك ، أنا أتخيلها بشكل مختلف. الدالات التي ترجع الأخطاء في Go تقوم بإرجاع نتيجة بشكل فعال، للاقتراض بشكل فضفاض من مصطلحات Rust. try هي أداة تقوم بفك ضغط النتيجة وإما إرجاع "نتيجة خطأ" إذا كان error != nil ، أو فك جزء T من النتيجة إذا كان error == nil .

بالطبع ، في Go ليس لدينا في الواقع كائنات ناتجة ، لكنها في الواقع نفس النمط ويبدو أن try يبدو وكأنه تدوين طبيعي لهذا النمط. أعتقد أن أي حل لهذه المشكلة سيتعين عليه تدوين بعض جوانب معالجة الأخطاء ، try أن الأمر يبدو معقولًا بالنسبة لي. أقترح أنا والآخرون توسيع قدرة try قليلاً لتتناسب بشكل أفضل مع أنماط معالجة أخطاء Go الحالية ، لكن المفهوم الأساسي يظل كما هو.

ugorji يبدو المتغير try(f, bool) الذي تقترحه مثل must من # 32219.

ugorji يبدو المتغير try(f, bool) الذي تقترحه مثل must من # 32219.

نعم إنه كذلك. لقد شعرت للتو أنه يمكن التعامل مع جميع الحالات الثلاثة بوظيفة مدمجة فردية ، وتلبية جميع حالات الاستخدام بأناقة.

نظرًا لأن try() سحري بالفعل ، ويدرك القيمة المرجعة للخطأ ، فهل يمكن زيادته لإرجاع مؤشر إلى هذه القيمة عند استدعائه في النموذج الصفري (بدون وسيطة)؟ سيؤدي ذلك إلى التخلص من الحاجة إلى عمليات الإرجاع المسماة ، وأعتقد أنه يساعد في الربط المرئي للمكان المتوقع أن يأتي الخطأ منه في البيانات المؤجلة. علي سبيل المثال:

func foo() error {
  defer fmt.HandleErrorf(try(), "important foo context info")
  try(bar())
  try(baz())
  try(etc())
}

تضمين التغريدة
أعتقد أن منطقية على try(f, bool) ستجعل من الصعب قراءتها ومن السهل تفويتها. يعجبني اقتراحك ولكن بالنسبة لحالة الذعر ، أعتقد أنه يمكن ترك ذلك للمستخدمين لكتابة ذلك داخل المعالج من الرمز النقطي الثالث ، على سبيل المثال try(f(), func(err error) { panic('at the disco'); }) ، وهذا يجعله أكثر وضوحًا للمستخدمين من try(f(), true) المخفي

تضمين التغريدة
أعتقد أن منطقية على try(f, bool) ستجعل من الصعب قراءتها ومن السهل تفويتها. يعجبني اقتراحك ولكن بالنسبة لحالة الذعر ، أعتقد أنه يمكن ترك ذلك للمستخدمين لكتابة ذلك داخل المعالج من الرمز النقطي الثالث ، على سبيل المثال try(f(), func(err error) { panic('at the disco'); }) ، وهذا يجعله أكثر وضوحًا للمستخدمين من try(f(), true) المخفي

لمزيد من التفكير ، أميل إلى الاتفاق مع موقفك ومنطقك ، وما زال يبدو أنيقًا كخط واحد.

@ patrick-nyt لا يزال مؤيدًا آخر لتركيب التخصيص _ لإجراء اختبار صفري ، في https://github.com/golang/go/issues/32437#issuecomment -499533464

يظهر هذا المفهوم في 13 إجابة منفصلة لاقتراح الفحص / التعامل
https://github.com/golang/go/wiki/Go2ErrorHandlingFeedback#recurring -المواضيع

f, ?return := os.Open(...)
f, ?panic  := os.Open(...)

لماذا ا؟ لأنه يقرأ مثل Go 1 ، في حين أن try() و check لا يفعل ذلك.

يبدو أن أحد الاعتراضات على try هو أنه تعبير. افترض بدلاً من ذلك أن هناك بيان postfix ? يعني إرجاع إذا لم يكن لا شيء. إليك نموذج الكود القياسي (بافتراض إضافة الحزمة المؤجلة المقترحة ):

func CopyFile(src, dst string) error {
    var err error // Don't need a named return because err is explicitly named
    defer deferred.Annotate(&err, "copy %s %s", src, dst)

    r, err := os.Open(src)
    err?
    defer deferred.AnnotatedExec(&err, r.Close)

    w, err := os.Create(dst)
    err?
    defer deferred.AnnotatedExec(&err, r.Close)

    defer deferred.Cond(&err, func(){ os.Remove(dst) })
    _, err = io.Copy(w, r)

    return err
}

مثال pgStore:

func (p *pgStore) DoWork() error {
    tx, err := p.handle.Begin()
    err?

    defer deferred.Cond(&err, func(){ tx.Rollback() })

    var res int64 
    err = tx.QueryRow(`INSERT INTO table (...) RETURNING c1`, ...).Scan(&res)
    // tricky bit: this would not change the value of err 
    // but the deferred.Cond would still be triggered by err being set before
    deferred.Format(err, "insert table")?

    _, err = tx.Exec(`INSERT INTO table2 (...) VALUES ($1)`, res)
    deferred.Format(err, "insert table2")?

    return tx.Commit()
}

أحب هذا منjargv :

نظرًا لأن try () سحري بالفعل ، وإدراكًا لقيمة إرجاع الخطأ ، فهل يمكن زيادتها لإرجاع مؤشر إلى تلك القيمة عند استدعائها في النموذج الفارغ (وسيطة صفرية)؟ هذا من شأنه أن يلغي الحاجة إلى أسماء العائدات

ولكن بدلاً من التحميل الزائد على الاسم try بناءً على عدد args ، أعتقد أنه يمكن أن يكون هناك سحر آخر مدمج ، على سبيل المثال reterr أو شيء من هذا القبيل.

لقد أطلعت على بعض الحزم المستخدمة كثيرًا ، أبحث عن كود go الذي "يعاني" من معالجة خاطئة ولكن يجب أن يكون مدروسًا جيدًا قبل كتابته ، في محاولة لمعرفة ما سيفعله "السحر" في المحاولة المقترحة ().
حاليًا ، ما لم أساء فهم الاقتراح ، فإن العديد من هؤلاء (على سبيل المثال ليس معالجة الأخطاء الأساسية الفائقة) لن يكسبوا الكثير ، أو سيضطرون إلى الاستمرار في أسلوب معالجة الخطأ "القديم".
مثال من net / http / request.go:

func (r *Request) write(w io.Writer, usingProxy bool, extraHeaders Header, waitForContinue func() bool) (err error) {
"

trace := httptrace.ContextClientTrace(r.Context())
if trace != nil && trace.WroteRequest != nil {
    defer func() {
        trace.WroteRequest(httptrace.WroteRequestInfo{
            Err: err,
        })
    }()
}

// Find the target host. Prefer the Host: header, but if that
// is not given, use the host from the request URL.
//
// Clean the host, in case it arrives with unexpected stuff in it.
host := cleanHost(r.Host)
if host == "" {
    if r.URL == nil {
        return errMissingHost
    }
    host = cleanHost(r.URL.Host)
}

// According to RFC 6874, an HTTP client, proxy, or other
// intermediary must remove any IPv6 zone identifier attached
// to an outgoing URI.
host = removeZone(host)

ruri := r.URL.RequestURI()
if usingProxy && r.URL.Scheme != "" && r.URL.Opaque == "" {
    ruri = r.URL.Scheme + "://" + host + ruri
} else if r.Method == "CONNECT" && r.URL.Path == "" {
    // CONNECT requests normally give just the host and port, not a full URL.
    ruri = host
    if r.URL.Opaque != "" {
        ruri = r.URL.Opaque
    }
}
if stringContainsCTLByte(ruri) {
    return errors.New("net/http: can't write control character in Request.URL")
}
// TODO: validate r.Method too? At least it's less likely to
// come from an attacker (more likely to be a constant in
// code).

// Wrap the writer in a bufio Writer if it's not already buffered.
// Don't always call NewWriter, as that forces a bytes.Buffer
// and other small bufio Writers to have a minimum 4k buffer
// size.
var bw *bufio.Writer
if _, ok := w.(io.ByteWriter); !ok {
    bw = bufio.NewWriter(w)
    w = bw
}

_, err = fmt.Fprintf(w, "%s %s HTTP/1.1\r\n", valueOrDefault(r.Method, "GET"), ruri)
if err != nil {
    return err
}

// Header lines
_, err = fmt.Fprintf(w, "Host: %s\r\n", host)
if err != nil {
    return err
}
if trace != nil && trace.WroteHeaderField != nil {
    trace.WroteHeaderField("Host", []string{host})
}

// Use the defaultUserAgent unless the Header contains one, which
// may be blank to not send the header.
userAgent := defaultUserAgent
if r.Header.has("User-Agent") {
    userAgent = r.Header.Get("User-Agent")
}
if userAgent != "" {
    _, err = fmt.Fprintf(w, "User-Agent: %s\r\n", userAgent)
    if err != nil {
        return err
    }
    if trace != nil && trace.WroteHeaderField != nil {
        trace.WroteHeaderField("User-Agent", []string{userAgent})
    }
}

// Process Body,ContentLength,Close,Trailer
tw, err := newTransferWriter(r)
if err != nil {
    return err
}
err = tw.writeHeader(w, trace)
if err != nil {
    return err
}

err = r.Header.writeSubset(w, reqWriteExcludeHeader, trace)
if err != nil {
    return err
}

if extraHeaders != nil {
    err = extraHeaders.write(w, trace)
    if err != nil {
        return err
    }
}

_, err = io.WriteString(w, "\r\n")
if err != nil {
    return err
}

if trace != nil && trace.WroteHeaders != nil {
    trace.WroteHeaders()
}

// Flush and wait for 100-continue if expected.
if waitForContinue != nil {
    if bw, ok := w.(*bufio.Writer); ok {
        err = bw.Flush()
        if err != nil {
            return err
        }
    }
    if trace != nil && trace.Wait100Continue != nil {
        trace.Wait100Continue()
    }
    if !waitForContinue() {
        r.closeBody()
        return nil
    }
}

if bw, ok := w.(*bufio.Writer); ok && tw.FlushHeaders {
    if err := bw.Flush(); err != nil {
        return err
    }
}

// Write body and trailer
err = tw.writeBody(w)
if err != nil {
    if tw.bodyReadError == err {
        err = requestBodyReadError{err}
    }
    return err
}

if bw != nil {
    return bw.Flush()
}
return nil

}
"

أو كما هو مستخدم في اختبار شامل مثل pprof / profile / profile_test.go:
"
func checkAggregation (prof * Profile، a * aggTest) خطأ {
// تحقق من حفظ العدد الإجمالي لعينات الصفوف.
الإجمالي: = int64 (0)

samples := make(map[string]bool)
for _, sample := range prof.Sample {
    tb := locationHash(sample)
    samples[tb] = true
    total += sample.Value[0]
}

if total != totalSamples {
    return fmt.Errorf("sample total %d, want %d", total, totalSamples)
}

// Check the number of unique sample locations
if a.rows != len(samples) {
    return fmt.Errorf("number of samples %d, want %d", len(samples), a.rows)
}

// Check that all mappings have the right detail flags.
for _, m := range prof.Mapping {
    if m.HasFunctions != a.function {
        return fmt.Errorf("unexpected mapping.HasFunctions %v, want %v", m.HasFunctions, a.function)
    }
    if m.HasFilenames != a.fileline {
        return fmt.Errorf("unexpected mapping.HasFilenames %v, want %v", m.HasFilenames, a.fileline)
    }
    if m.HasLineNumbers != a.fileline {
        return fmt.Errorf("unexpected mapping.HasLineNumbers %v, want %v", m.HasLineNumbers, a.fileline)
    }
    if m.HasInlineFrames != a.inlineFrame {
        return fmt.Errorf("unexpected mapping.HasInlineFrames %v, want %v", m.HasInlineFrames, a.inlineFrame)
    }
}

// Check that aggregation has removed finer resolution data.
for _, l := range prof.Location {
    if !a.inlineFrame && len(l.Line) > 1 {
        return fmt.Errorf("found %d lines on location %d, want 1", len(l.Line), l.ID)
    }

    for _, ln := range l.Line {
        if !a.fileline && (ln.Function.Filename != "" || ln.Line != 0) {
            return fmt.Errorf("found line %s:%d on location %d, want :0",
                ln.Function.Filename, ln.Line, l.ID)
        }
        if !a.function && (ln.Function.Name != "") {
            return fmt.Errorf(`found file %s location %d, want ""`,
                ln.Function.Name, l.ID)
        }
    }
}

return nil

}
"
هذان مثالان يمكنني التفكير فيهما حيث يمكن للمرء أن يقول: "أرغب في خيار أفضل لمعالجة الأخطاء"

هل يمكن لأي شخص أن يوضح كيف يمكن أن تتحسن هذه باستخدام try ()؟

أنا أؤيد هذا الاقتراح في الغالب.

اهتمامي الرئيسي ، الذي تمت مشاركته مع العديد من المعلقين ، هو حول معلمات النتائج المسماة. يشجع الاقتراح الحالي بالتأكيد المزيد من استخدام معلمات النتائج المسماة وأعتقد أن هذا سيكون خطأ. لا أعتقد أن هذه مجرد مسألة أسلوب كما ينص الاقتراح: النتائج المسماة هي ميزة خفية للغة والتي ، في كثير من الحالات ، تجعل الكود أكثر عرضة للخطأ أو أقل وضوحًا. بعد حوالي 8 سنوات من قراءة وكتابة كود Go ، لا أستخدم سوى معلمات النتائج المسماة لغرضين:

  • توثيق معلمات النتيجة
  • معالجة قيمة نتيجة (عادةً error ) داخل مؤجل

لمهاجمة هذه المشكلة من اتجاه جديد ، إليك فكرة لا أعتقد أنها تتوافق عن كثب مع أي شيء تمت مناقشته في مستند التصميم أو سلسلة تعليقات المشكلة هذه. دعنا نسميها "مؤجلون الخطأ":

السماح باستخدام التأجيل لاستدعاء الوظائف بمعامل خطأ ضمني.

لذلك إذا كان لديك وظيفة

func f(err error, t1 T1, t2 T2, ..., tn Tn) error

بعد ذلك ، في دالة g حيث نوع معلمة النتيجة الأخيرة error (على سبيل المثال ، أي دالة يتم فيها استخدام try ) ، استدعاء لـ f يمكن تأجيلها على النحو التالي:

func g() (R0, R0, ..., error) {
    defer f(t0, t1, ..., tn) // err is implicit
}

دلالات إرجاء الخطأ هي:

  1. تم استدعاء الاستدعاء المؤجل لـ f بمعامل النتيجة الأخير g كمعامل إدخال أول f
  2. يتم استدعاء f فقط إذا لم يكن هذا الخطأ صفريًا
  3. تم تعيين نتيجة f لمعامل النتيجة الأخير g

لذا لاستخدام مثال من مستند تصميم معالجة الأخطاء القديم ، باستخدام ميزة إرجاء الخطأ والمحاولة ، يمكننا القيام بذلك

func printSum(a, b string) error {
    defer func(err error) error {
        return fmt.Errorf("printSum(%q + %q): %v", a, b, err)
    }()
    x := try(strconv.Atoi(a))
    y := try(strconv.Atoi(b))
    fmt.Println("result:", x+y)
    return nil
}

إليك كيفية عمل HandleErrorf:

func printSum(a, b string) error {
    defer handleErrorf("printSum(%q + %q)", a, b)
    x := try(strconv.Atoi(a))
    y := try(strconv.Atoi(b))
    fmt.Println("result:", x+y)
    return nil
}

func handleErrorf(err error, format string, args ...interface{}) error {
    return fmt.Errorf(format+": %v", append(args, err)...)
}

إحدى الحالات التي يجب حلها هي كيفية التعامل مع الحالات التي يكون من الغامض فيها أي شكل من أشكال التأجيل نستخدمه. أعتقد أن هذا يحدث فقط مع الوظائف (غير العادية جدًا) ذات التوقيعات مثل هذا:

func(error, ...error) error

يبدو من المعقول أن نقول إن هذه الحالة يتم التعامل معها بطريقة غير مؤجلة للخطأ (وهذا يحافظ على التوافق مع الإصدارات السابقة).


بالتفكير في هذه الفكرة في اليومين الماضيين ، فهي سحرية بعض الشيء ، لكن تجنب معلمات النتائج المسماة ميزة كبيرة لصالحها. نظرًا لأن try يشجع على المزيد من استخدام defer للتلاعب بالأخطاء ، فمن المنطقي أن defer يمكن تمديده ليناسب هذا الغرض بشكل أفضل. أيضًا ، هناك تماثل معين بين try و مؤجل الخطأ.

أخيرًا ، تعد أدوات تأجيل الأخطاء مفيدة اليوم حتى بدون محاولة ، لأنها تحل محل استخدام معلمات النتائج المسماة لمعالجة عوائد الخطأ. على سبيل المثال ، إليك نسخة معدلة من بعض التعليمات البرمجية الحقيقية:

// GetMulti retrieves multiple files through the cache at once and returns its
// results as a slice parallel to the input.
func (c *FileCache) GetMulti(keys []string) (_ []*File, err error) {
    files := make([]*file, len(keys))

    defer func() {
        if err != nil {
            // Return any successfully retrieved files.
            for _, f := range files {
                if f != nil {
                    c.put(f)
                }
            }
        }
    }()

    // ...
}

مع ميزة "تأجيل الخطأ" ، يصبح هذا:

// GetMulti retrieves multiple files through the cache at once and returns its
// results as a slice parallel to the input.
func (c *FileCache) GetMulti(keys []string) ([]*File, error) {
    files := make([]*file, len(keys))

    defer func(err error) error {
        // Return any successfully retrieved files.
        for _, f := range files {
            if f != nil {
                c.put(f)
            }
        }
        return err
    }()

    // ...
}

beoran فيما يتعلق بتعليقك أننا يجب أن ننتظر الأدوية. لن تساعد العوامل العامة هنا - يرجى قراءة الأسئلة الشائعة .

فيما يتعلق باقتراحاتك بشأن السلوك الافتراضي لـ velovix try الوسيطتين: كما قلت سابقًا ، فإن فكرتك عن الخيار المعقول الواضح هي كابوس شخص آخر.

هل لي أن أقترح أن نواصل هذه المناقشة بمجرد تطور إجماع واسع على أن try مع معالج خطأ واضح هو فكرة أفضل من الحد الأدنى الحالي try . في هذه المرحلة ، من المنطقي مناقشة النقاط الدقيقة لمثل هذا التصميم.

(أحب أن يكون لدي معالج ، لهذا الأمر. إنه أحد مقترحاتنا السابقة. وإذا اعتمدنا try كما هو ، فلا يزال بإمكاننا التحرك نحو try مع معالج في الأمام -طريقة متوافقة - على الأقل إذا كان المعالج اختياريًا. ولكن دعنا نتخذ خطوة واحدة في كل مرة.)

aarzilli شكرا على اقتراحك .

طالما أن أخطاء التزيين اختيارية ، فسيميل الناس إلى عدم القيام بذلك (إنه عمل إضافي بعد كل شيء). انظر أيضا تعليقي هنا .

لذلك ، لا أعتقد أن الأشخاص المقترحين try _discourages_ الناس من أخطاء التزيين (إنهم محبطون بالفعل حتى مع if للسبب أعلاه) ؛ هذا أن try لا يشجع على ذلك.

(إحدى طرق تشجيعه هي ربطه بـ try : لا يمكن للمرء استخدام try إلا إذا قام أيضًا بتزيين الخطأ ، أو رفضه صراحة.)

لكن عد إلى اقتراحاتك: أعتقد أنك تقدم الكثير من الآلات هنا. تغيير دلالات defer فقط لجعله يعمل بشكل أفضل لـ try ليس شيئًا نود التفكير فيه ما لم تكن هذه التغييرات defer مفيدة بطريقة أكثر عمومية. أيضًا ، يربط اقتراحك defer مع try وبالتالي يجعل هاتين الآليتين أقل تعامدًا ؛ شيء نريد تجنبه.

ولكن الأهم من ذلك ، أشك في أنك سترغب في إجبار الجميع على كتابة defer فقط حتى يتمكنوا من استخدام try . لكن بدون القيام بذلك ، سنعود إلى المربع الأول: سوف يميل الناس نحو عدم تزيين الأخطاء.

(أحب أن يكون لدي معالج ، لهذا الأمر. إنه أحد مقترحاتنا السابقة. وإذا تبنينا المحاولة كما هي ، فلا يزال بإمكاننا التحرك نحو محاولة مع معالج بطريقة متوافقة مع الأمام - على الأقل إذا كان المعالج اختياري. ولكن دعنا نتخذ خطوة واحدة في كل مرة.)

بالتأكيد ، ربما يكون النهج متعدد الخطوات هو السبيل للذهاب. إذا أضفنا وسيطة معالج اختيارية في المستقبل ، فيمكن إنشاء الأدوات لتحذير الكاتب من try غير المعالج بنفس روح الأداة errcheck . بغض النظر ، أنا أقدر ملاحظاتك!

alanfo شكرا لملاحظاتك الإيجابية.

فيما يتعلق بالنقاط التي ذكرتها:

1) إذا كانت المشكلة الوحيدة مع try هي حقيقة أنه سيتعين على المرء تسمية إرجاع خطأ حتى نتمكن من تزيين خطأ عبر defer ، أعتقد أننا جيدون. إذا تبين أن تسمية النتيجة مشكلة حقيقية ، فيمكننا معالجتها. إحدى الآليات البسيطة التي يمكنني التفكير فيها ستكون متغيرًا مُعلنًا مسبقًا وهو اسم مستعار لنتيجة خطأ (فكر في الأمر على أنه يحمل الخطأ الذي تسبب في أحدث try ). قد تكون هناك أفكار أفضل. لم نقترح هذا لأن هناك آلية بالفعل في اللغة ، وهي تسمية النتيجة.
2) try والاختبار: يمكن معالجة هذا وجعله يعمل. انظر الوثيقة المفصلة.
3) هذا تم تناوله صراحة في الوثيقة التفصيلية.
4) أقر.

benhoyt شكرا لملاحظاتك الإيجابية.

إذا كانت الحجة الرئيسية ضد هذا الاقتراح هي حقيقة أن try مضمّن ، فنحن في مكان رائع. استخدام المدمج هو ببساطة حل عملي لمشكلة التوافق مع الإصدارات السابقة (يحدث أنه لا يتسبب في عمل إضافي للمحلل ، والأدوات وما إلى ذلك - ولكن هذه مجرد فائدة جانبية لطيفة ، وليس السبب الأساسي). هناك أيضًا بعض الفوائد من الاضطرار إلى كتابة أقواس ، وقد تمت مناقشة ذلك بالتفصيل في مستند التصميم (قسم خصائص التصميم المقترح).

كل ما قيل ، إذا كان استخدام أداة عرض مدمجة هي أداة العرض ، فيجب أن نأخذ في الاعتبار الكلمة الأساسية try . لن تكون متوافقة مع الإصدارات السابقة على الرغم من وجود رمز موجود لأن الكلمة الأساسية قد تتعارض مع المعرفات الحالية.

(لكي تكتمل ، هناك أيضًا خيار عامل مثل ? ، والذي سيكون متوافقًا مع الإصدارات السابقة. ومع ذلك ، لا يبدو لي أفضل خيار للغة مثل Go. ولكن مرة أخرى ، إذا كان هذا هو كل ما يتطلبه الأمر لجعل try مستساغًا ، فربما يجب أن نفكر فيه.)

ugorji شكرا لملاحظاتك الإيجابية.

يمكن تمديد try ليأخذ وسيطة إضافية. نفضل أن تأخذ وظيفة فقط بتوقيع func (error) error . إذا كنت تريد الذعر ، فمن السهل توفير وظيفة مساعد من سطر واحد:

func doPanic(err error) error { panic(err) }

من الأفضل الحفاظ على تصميم بسيط try .

@ patrick-nyt ماذا تقترح :

file, err := os.Open("file.go")
try(err)

سيكون ممكنا مع الاقتراح الحالي.

dpinela ، ugorji يُرجى أيضًا قراءة مستند التصميم حول موضوع must مقابل try . من الأفضل أن تبقي try بسيطًا قدر الإمكان. must هو "نمط" شائع في تعبيرات التهيئة ، ولكن ليس هناك حاجة ماسة إلى "إصلاح" ذلك.

jargv شكرا لاقتراحك . هذه فكرة مثيرة للاهتمام (انظر أيضًا تعليقي هنا على هذا الموضوع). كي تختصر:

  • try(x) يعمل على النحو المقترح
  • يقوم try() بإرجاع *error للإشارة إلى نتيجة الخطأ

ستكون هذه بالفعل طريقة أخرى للوصول إلى النتيجة دون الحاجة إلى تسميتها.

cespare يبدو اقتراح jargv بالنسبة لي أبسط مما تقترحه . إنه يحل نفس مشكلة الوصول إلى الخطأ الناتج. ما رأيك؟

حسب https://github.com/golang/go/issues/32437#issuecomment -499320588:

func doPanic (خطأ خطأ) خطأ {ذعر (يخطئ)}

أتوقع أن تكون هذه الوظيفة شائعة جدًا. هل يمكن تحديد ذلك مسبقًا في "مدمج" (أو في مكان آخر في حزمة قياسية مثل errors

سيء للغاية لدرجة أنك لا تتوقع أن الأدوية الجنيسة قوية بما يكفي لتنفيذها
حاول ، كنت آمل في الواقع أنه سيكون من الممكن القيام بذلك.

نعم ، يمكن أن يكون هذا الاقتراح خطوة أولى ، على الرغم من أنني لا أرى فائدة كبيرة فيه
هو نفسي كما هو عليه الآن.

من المؤكد أن هذه القضية ربما تركز كثيرًا على البدائل التفصيلية ،
لكنه يظهر أن العديد من المشاركين ليسوا سعداء تمامًا
هو - هي. ما يبدو أنه ينقصه هو إجماع واسع حول هذا الاقتراح ...

المرجع vr 7 jun. 2019 01:04 schreef pj [email protected] :

Asper # 32437 (تعليق)
https://github.com/golang/go/issues/32437#issuecomment-499320588 :

func doPanic (خطأ خطأ) خطأ {ذعر (يخطئ)}

أتوقع أن تكون هذه الوظيفة شائعة جدًا. هل يمكن أن يكون هذا محددًا مسبقًا
في "مدمج"؟

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/golang/go/issues/32437؟email_source=notifications&email_token=AAARM6OOOLLYO5ZCE6VVL2TPZGJWRA5CNFSM4HTGCZ72YY3PNVWWK3TUL52HS4DFVREX99G43VMVBWLNM79
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/AAARM6K5AOR2DES4QDTNLSTPZGJWRANCNFSM4HTGCZ7Q
.

pjebs ، لقد كتبت الدالة المكافئة عشرات المرات. عادةً ما أسميها "orDie" أو "check". الأمر بسيط للغاية ، ولا توجد حاجة حقيقية لجعله جزءًا من المكتبة القياسية. بالإضافة إلى أشخاص مختلفين قد يرغبون في التسجيل أو أي شيء آخر قبل الإنهاء.

beoran ربما يمكنك توسيع العلاقة بين الأدوية الجنيسة ومعالجة الأخطاء. عندما أفكر فيهم ، يبدو أنهم شيئان مختلفان. علم الوراثة ليس كل ما يمكنه معالجة جميع مشاكل اللغة. إنها القدرة على كتابة وظيفة واحدة يمكن أن تعمل على أنواع متعددة.

يحاول اقتراح معالجة الأخطاء المحدد هذا تقليل الصيغة المعيارية عن طريق تقديم دالة معرّفة مسبقًا try التي تغير التحكم في التدفق في بعض الظروف. علم الوراثة لن يغير تدفق السيطرة. لذلك أنا حقًا لا أرى العلاقة.

كان رد فعلي الأولي على هذا هو 👎 كما تخيلت أن التعامل مع العديد من الاستدعاءات المعرضة للخطأ داخل إحدى الوظائف سيجعل معالجة الخطأ defer مربكًا. بعد قراءة الاقتراح بأكمله ، قلبت رد فعلي على ❤️ و حيث علمت أنه لا يزال من الممكن تحقيق ذلك مع تعقيد منخفض نسبيًا.

carlmjohnson نعم انها بسيطة ولكن ...

لقد كتبت الدالة المكافئة عشرات المرات.

مزايا الوظيفة المُعلنة مسبقًا هي:

  1. يمكننا سطر واحد
  2. لا نحتاج إلى إعادة التصريح عن الخطأ => وظيفة الذعر في كل حزمة نستخدمها ، أو الحفاظ على موقع مشترك لها. نظرًا لأنه من المحتمل أن يكون الجميع في مجتمع Go ، فإن "الحزمة القياسية" هي الموقع المشترك لها.

griesemer مع متغير معالج الأخطاء لاقتراح المحاولة الأصلي ، لم يعد مطلب الوظيفة الشاملة لإرجاع الخطأ مطلوبًا الآن.

عندما استفسرت عن ذلك لأول مرة عن الخطأ => الذعر ، أشير إلى أن الاقتراح اعتبره أمرًا خطيرًا للغاية (لسبب وجيه). ولكن إذا استخدمنا try() بدون معالج أخطاء في سيناريو لا تقوم فيه الوظيفة الشاملة بإرجاع خطأ ، فإن تحويلها إلى خطأ في وقت الترجمة يخفف من القلق الذي تمت مناقشته في الاقتراح

pjebs لم تكن متطلبات الوظيفة الشاملة لإرجاع خطأ مطلوبة في التصميم الأصلي _if_ تم توفير معالج خطأ. لكنه مجرد تعقيد آخر لـ try . من الأفضل _ كثيرًا_ إبقائها بسيطة. بدلاً من ذلك ، سيكون من الواضح أن يكون لديك وظيفة must منفصلة ، والتي دائمًا ما تنزعج من الخطأ (ولكن بخلاف ذلك مثل try ). ثم يتضح ما يحدث في الكود وليس على المرء أن ينظر إلى السياق.

عامل الجذب الرئيسي لامتلاك مثل هذا must سيكون أنه يمكن استخدامه مع اختبارات الوحدة ؛ خاصة إذا تم تعديل الحزمة testing بشكل مناسب للتعافي من الذعر الناجم عن must والإبلاغ عنها على أنها إخفاقات اختبار بطريقة لطيفة. ولكن لماذا نضيف آلية لغة جديدة أخرى عندما يمكننا فقط ضبط حزمة الاختبار لقبول أيضًا وظيفة الاختبار بالشكل TestXxx(t *testing.T) error ؟ إذا قاموا بإرجاع خطأ ، والذي يبدو طبيعيًا تمامًا بعد كل شيء (ربما كان علينا فعل ذلك من البداية) ، فعندئذٍ try سيعمل بشكل جيد. ستحتاج الاختبارات المحلية إلى مزيد من العمل ، ولكن من المحتمل أن يكون ذلك ممكنًا.

الاستخدام الآخر الشائع نسبيًا لـ must في تعبيرات التهيئة العامة ( must(regexp.Compile... ، إلخ). إذا كان من الجيد أن يكون لديك ، لكن هذا لا يرفعها بالضرورة إلى المستوى المطلوب لميزة لغة جديدة.

griesemer نظرًا لأن must مرتبط بشكل غامض بـ try ، وبالنظر إلى أن الزخم يتجه نحو تنفيذ try ، ألا تعتقد أنه من الجيد التفكير في must في نفس الوقت - حتى لو كان مجرد "ممتع".

الاحتمالات هي أنه إذا لم تتم مناقشتها في هذه الجولة ، فلن يتم تنفيذها / النظر فيها بجدية ، على الأقل لأكثر من 3 سنوات (أو ربما في أي وقت مضى). قد يكون التداخل في المناقشة جيدًا أيضًا بدلاً من البدء من نقطة الصفر ومناقشات إعادة التدوير.

ذكر الكثير من الناس أن must يكمل try بشكل لطيف للغاية.

pjebs بالتأكيد لا يبدو أن هناك أي "زخم نحو تنفيذ try " الآن ... - ولقد نشرنا هذا منذ يومين فقط. ولم يقرر أي شيء. دعونا نعطي هذا بعض الوقت.

لم يفلت منا من أن must يتوافق بشكل رائع مع try ، لكن هذا لا يشبه جعله جزءًا من اللغة. لقد بدأنا فقط في استكشاف هذه المساحة مع مجموعة أكبر من الناس. نحن في الحقيقة لا نعرف حتى الآن ما قد يأتي في دعمها أو ضدها. شكرا.

بعد قضاء ساعات في قراءة جميع التعليقات ووثيقة التصميم التفصيلية ، أردت إضافة وجهات نظري إلى هذا الاقتراح.

سأبذل قصارى جهدي لاحترام طلب ianlancetaylor ليس فقط إعادة ذكر النقاط السابقة ولكن بدلاً من ذلك إضافة تعليقات جديدة إلى المناقشة. ومع ذلك ، لا أعتقد أنه يمكنني تقديم التعليقات الجديدة دون الرجوع إلى حد ما إلى التعليقات السابقة.

اهتمامات

الحمولة الزائدة المؤسفة للتأجيل

إن تفضيل زيادة تحميل الطبيعة الواضحة والمباشرة لـ defer أمر مثير للقلق. إذا كتبت defer closeFile(f) فهذا واضح ومباشر لي ما يحدث ولماذا ؛ في نهاية الوظيفة التي سيتم استدعاؤها. وأثناء استخدام defer مقابل panic() و recover() أقل وضوحًا ، نادرًا ما أستخدمه ولن أراه مطلقًا عند قراءة كود الآخرين.

Spoo لزيادة تحميل defer للتعامل مع الأخطاء أيضًا ليس واضحًا ومربكًا. لماذا الكلمة الرئيسية defer ؟ هل لا يعني defer _ "أفعل لاحقًا" _ بدلاً من _ "ربما في وقت لاحق؟" _

هناك أيضًا القلق الذي ذكره فريق Go بشأن أداء defer . بالنظر إلى ذلك ، يبدو أنه من المؤسف بشكل مضاعف أن يتم النظر في defer لتدفق رمز _ "المسار السريع" _.

لا توجد إحصائيات تثبت حالة استخدام كبيرة

كما ذكر prologic ، هل هذا الاقتراح try() مبني على نسبة كبيرة من الكود الذي سيستخدم حالة الاستخدام هذه ، أم أنه يعتمد بدلاً من ذلك على محاولة تهدئة أولئك الذين اشتكوا من معالجة أخطاء Go؟

أتمنى لو كنت أعرف كيفية إعطائك إحصائيات من قاعدة التعليمات البرمجية الخاصة بي دون مراجعة شاملة لكل ملف وتدوين الملاحظات ؛ لا أعرف كيف استطاعprologic أن يفعل ذلك رغم أنه سعيد.

لكن حسب الروايات المتناقلة ، سأندهش إذا عالجت try() 5٪ من حالات الاستخدام الخاصة بي وأعتقد أنها ستعالج أقل من 1٪. هل تعلم على وجه اليقين أن نتائج الآخرين تختلف اختلافًا كبيرًا؟ هل أخذت مجموعة فرعية من المكتبة القياسية وحاولت معرفة كيفية تطبيقها؟

لأنه بدون إحصائيات معروفة تفيد بأن هذا مناسب لعدد كبير من التعليمات البرمجية في البرية ، يجب أن أسأل هل هذا التغيير الجديد معقدًا للغة والذي سيتطلب من الجميع تعلم المفاهيم الجديدة يعالج حقًا عددًا مقنعًا من حالات الاستخدام؟

يسهل على المطورين تجاهل الأخطاء

هذا تكرار لما لدى الآخرين من تعليقات ، ولكن ما يقدم try() بشكل أساسي هو مماثل من عدة نواحٍ لتبني ببساطة ما يلي كرمز اصطلاحي ، وهذا هو الكود الذي لن يجد طريقه أبدًا إلى أي رمز ذاتي - احترام سفن التطوير:

f, _ := os.Open(filename)

أعلم أنه يمكنني أن أكون أفضل في الكود الخاص بي ، لكنني أعرف أيضًا أن الكثير منا يعتمد على سخاء مطوري Go الآخرين الذين ينشرون بعض الحزم المفيدة للغاية ، ولكن مما رأيته في _ "رمز الأشخاص الآخرين (tm)" _ غالبًا ما يتم تجاهل أفضل الممارسات في معالجة الأخطاء.

على محمل الجد ، هل نريد حقًا تسهيل الأمر على المطورين لتجاهل الأخطاء والسماح لهم بالتعامل مع GitHub بحزم غير قوية؟

يمكن (في الغالب) بالفعل تنفيذ try() في userland

ما لم أسيء فهم الاقتراح - وهو ما أفعله على الأرجح - إليك try() في Go Playground المطبق في userland ، وإن كان ذلك مع قيمة إرجاع واحدة (1) وإرجاع واجهة بدلاً من النوع المتوقع:

package main

import (
    "errors"
    "fmt"
    "strings"
)
func main() {
    defer func() {
        r := recover()
        if r != nil && strings.HasPrefix(r.(string),"TRY:") {
            fmt.Printf("Ouch! %s",strings.TrimPrefix(r.(string),"TRY: "))
        }
    }()
    n := try(badjuju()).(int)
    fmt.Printf("Just chillin %dx!",n)   
}
func badjuju() (int,error) {
    return 10, errors.New("this is a really bad error")
}
func try(args ...interface{}) interface{} {
    err,ok := args[1].(error)
    if ok && err != nil {
        panic(fmt.Sprintf("TRY: %s",err.Error()))
    }
    return args[0]
}

لذلك يمكن للمستخدم إضافة try2() و try3() وهكذا اعتمادًا على عدد قيم الإرجاع التي يحتاجون إلى إرجاعها.

لكن Go سيحتاج فقط إلى ميزة لغة واحدة (1) بسيطة _ لكنها عالمية _ للسماح للمستخدمين الذين يريدون try() بتقديم دعمهم الخاص ، وإن كان لا يزال يتطلب تأكيدًا صريحًا للنوع. أضف _ (متوافقة تمامًا مع الإصدارات السابقة) _ إمكانية Go func لإرجاع عدد متغير من قيم الإرجاع ، على سبيل المثال:

func try(args ...interface{}) ...interface{} {
    err,ok := args[1].(error)
    if ok && err != nil {
        panic(fmt.Sprintf("TRY: %s",err.Error()))
    }
    return args[0:len(args)-2]
}

وإذا تناولت الأدوية الجنيسة أولاً ، فلن تكون تأكيدات النوع ضرورية حتى _ (على الرغم من أنني أعتقد أن حالات استخدام الأدوية الجنيسة يجب أن يتم تقليصها عن طريق إضافة عناصر مدمجة لمعالجة حالات الاستخدام العامة بدلاً من إضافة الدلالات المربكة وسلطة بناء الجملة للأدوية العامة من جافا وآخرون) _

عدم الوضوح

عند دراسة كود الاقتراح أجد أن السلوك غير واضح ومن الصعب نوعًا ما التفكير فيه.

عندما أرى try() يلف تعبيرًا ، ماذا سيحدث إذا تم إرجاع خطأ؟

هل سيتم تجاهل الخطأ فقط؟ أم ستنتقل إلى أول أو أحدث defer ، وإذا كان الأمر كذلك ، فسيقوم تلقائيًا بتعيين متغير باسم err داخل الإغلاق ، أو سيمرره كمعامل _ (I لا ترى معلمة؟) _. وإذا لم يكن اسم خطأ تلقائي ، فكيف يمكنني تسميته؟ وهل يعني ذلك أنه لا يمكنني التصريح عن متغير err الخاص بي في وظيفتي ، لتجنب التعارضات؟

وهل ستسمي كل defer s؟ بترتيب عكسي أم ترتيب عادي؟

أم أنها ستعود من كل من الإغلاق و func حيث تم إرجاع الخطأ؟ _ (شيء لم أكن لأفكر فيه أبدًا إذا لم أقرأ هنا الكلمات التي تشير إلى ذلك.) _

بعد قراءة الاقتراح وجميع التعليقات حتى الآن ما زلت بصراحة لا أعرف إجابات الأسئلة أعلاه. هل هذا هو نوع الميزة التي نريد أن نضيفها إلى لغة يدافع عنها المدافعون عن أنها _ "كابتن واضح؟" _

عدم السيطرة

باستخدام defer ، يبدو أن مطوري التحكم الوحيدين الذين سيتم منحهم هو التفريع إلى _ (الأحدث؟) _ defer . ولكن في تجربتي مع أي طرق تتجاوز func تافه ، عادة ما تكون أكثر تعقيدًا من ذلك.

غالبًا ما وجدت أنه من المفيد مشاركة جانب من معالجة الأخطاء ضمن func - أو حتى عبر package - ولكن بعد ذلك يكون لديك أيضًا معالجة أكثر تحديدًا مشتركة عبر حزمة أخرى أو أكثر.

على سبيل المثال ، يمكنني الاتصال بخمسة (5) func مكالمات تعيد error() من داخل func آخر ؛ دعنا نسميها A() ، B() ، C() ، D() ، و E() . قد أحتاج إلى C() للتعامل مع الأخطاء الخاصة به ، A() ، B() ، D() ، و E() لمشاركة بعض معالجة الأخطاء ، و B() و E() للتعامل بشكل خاص.

لكني لا أعتقد أنه سيكون من الممكن القيام بذلك مع هذا الاقتراح. على الأقل ليس بسهولة.

ومن المفارقات ، مع ذلك ، أن Go لديها بالفعل ميزات لغوية تسمح بمستوى عالٍ من المرونة التي لا تحتاج إلى أن تقتصر على مجموعة صغيرة من حالات الاستخدام ؛ func s والإغلاق. لذا فإن سؤالي الخطابي هو:

_ "لماذا لا نضيف فقط تحسينات طفيفة على اللغة الحالية لمعالجة حالات الاستخدام هذه ولا نحتاج إلى إضافة وظائف مضمنة جديدة أو قبول دلالات محيرة؟" _

إنه سؤال بلاغي لأنني أخطط لتقديم اقتراح كبديل ، وهو اقتراح كنت أتخيله أثناء دراسة هذا الاقتراح وأثناء النظر في جميع عيوبه.

لكني أستطرد ، سيأتي ذلك لاحقًا وهذا التعليق هو سبب الحاجة إلى إعادة النظر في الاقتراح الحالي.

عدم وجود دعم معلن لـ break

قد يبدو هذا وكأنه يخرج من الحقل الأيسر لأن معظم الأشخاص يستخدمون العوائد المبكرة لمعالجة الأخطاء ، لكنني وجدت أنه من الأفضل استخدام break لمعالجة الخطأ في التفاف معظم أو كل func قبل return .

لقد استخدمت هذا النهج لفترة من الوقت وفوائده في تسهيل إعادة البناء وحده تجعله أفضل من return المبكر ، لكن له العديد من المزايا الأخرى بما في ذلك نقطة الخروج الفردية والقدرة على إنهاء قسم من func مبكرًا ولكن لا يزال كذلك قادر على تشغيل التنظيف _ (وهذا على الأرجح هو السبب في أنني نادرًا ما أستخدم defer ، والذي أجد صعوبة في التفكير فيه فيما يتعلق بتدفق البرنامج.) _

لاستخدام break بدلاً من الإرجاع المبكر ، استخدم حلقة for range "1" {...} لإنشاء كتلة للخروج من _ (أقوم بالفعل بإنشاء حزمة تسمى only تحتوي فقط على ثابت يسمى Once بقيمة "1" ): _

func (me *Config) WriteFile() (err error) {
    for range only.Once {
        var j []byte
        j, err = json.MarshalIndent(me, "", "    ")
        if err != nil {
            err = fmt.Errorf("unable to marshal config; %s", 
                err.Error(),
            )
            break
        }
        err = me.MaybeMakeDir(me.GetDir(), os.ModePerm)
        if err != nil {
            err = fmt.Errorf("unable to make directory'%s'; %s", 
                me.GetDir(), 
                err.Error(),
            )
            break
        }
        err = ioutil.WriteFile(string(me.GetFilepath()), j, os.ModePerm)
        if err != nil {
            err = fmt.Errorf("unable to write to config file '%s'; %s", 
                me.GetFilepath(), 
                err.Error(),
            )
            break
        }
    }
    return err
}

أخطط للتدوين حول هذا النمط مطولًا في المستقبل القريب ، ومناقشة الأسباب العديدة التي جعلتني أجد أنه يعمل بشكل أفضل من العوائد المبكرة.

لكني استطرادا. السبب وراء طرحه هنا هو أنني سأضطر إلى تنفيذ معالجة الأخطاء التي تفترض مبكرًا return s ويتجاهل استخدام break لمعالجة الأخطاء

رأيي err == nil إشكالي

لمزيد من الاستطراد ، أريد أن أثير القلق الذي شعرت به بشأن معالجة الأخطاء الاصطلاحية في Go. على الرغم من أنني أؤمن بشدة بفلسفة Go للتعامل مع الأخطاء عند حدوثها مقابل استخدام معالجة الاستثناءات ، أشعر أن استخدام nil للإشارة إلى عدم وجود خطأ يمثل مشكلة لأنني غالبًا ما أجد أنني أرغب في إرجاع رسالة نجاح من روتين - للاستخدام في استجابات واجهة برمجة التطبيقات (API) - ولا يقوم فقط بإرجاع قيمة غير صفرية فقط عندما يكون هناك خطأ.

لذلك بالنسبة إلى Go 2 ، أود حقًا أن أرى Go فكر في إضافة نوع مضمّن جديد status وثلاث وظائف مضمنة iserror() ، iswarning() ، issuccess() . status يمكن أن ينفذ error - مما يسمح بالكثير من التوافق مع الإصدارات السابقة و nil تم تمريره إلى issuccess() سيعود true - لكن status سيكون لدى nil . سيتيح ذلك شيئًا مثل النهج التالي بدلاً من ذلك:

func (me *Config) WriteFile() (sts status) {
    for range only.Once {
        var j []byte
        j, sts = json.MarshalIndent(me, "", "    ")
        if iserror(sts) {
            sts.AddMessage("unable to marshal config")
            break
        }
        sts = me.MaybeMakeDir(me.GetDir(), os.ModePerm)
        if iserror(sts) {
            sts.AddMessage("unable to make directory'%s'", me.GetDir())
            break
        }
        sts = ioutil.WriteFile(string(me.GetFilepath()), j, os.ModePerm)
        if iserror(sts) {
            sts.AddMessage("unable to write to config file '%s'", 
                me.GetFilepath(), 
            )
            break
        }
        sts = fmt.Status("config file written")
    }
    return sts
}

أنا أستخدم بالفعل نهج userland في مستوى ما قبل الإصدار التجريبي حاليًا من حزمة الاستخدام الداخلي التي تشبه ما سبق لمعالجة الأخطاء. بصراحة ، أقضي وقتًا أقل بكثير في التفكير في كيفية هيكلة الكود عند استخدام هذا النهج مقارنةً بالوقت الذي كنت أحاول فيه اتباع معالجة أخطاء Go الاصطلاحية.

إذا كنت تعتقد أن هناك أي فرصة لتطوير كود Go الاصطلاحي لهذا النهج ، فيرجى أخذ ذلك في الاعتبار عند تنفيذ معالجة الأخطاء ، بما في ذلك عند النظر في هذا الاقتراح try() .

_ مبرر "ليس للجميع"

كانت إحدى الردود الرئيسية من فريق Go هي _ "مرة أخرى ، لا يحاول هذا الاقتراح حل جميع حالات معالجة الأخطاء." _
وربما يكون هذا هو الشاغل الأكثر إثارة للقلق ، من منظور الحكم.

هل هذا التغيير الجديد المعقد للغة الذي سيتطلب من الجميع تعلم المفاهيم الجديدة يعالج بالفعل عددًا مقنعًا من حالات الاستخدام؟

أليس هذا هو التبرير نفسه الذي رفضه أعضاء الفريق الأساسي للعديد من طلبات الميزات من المجتمع؟ فيما يلي اقتباس مباشر من تعليق أدلى به أحد أعضاء فريق Go في رد نموذجي على طلب ميزة تم إرساله منذ حوالي عامين _ (لا أقوم بتسمية الشخص أو طلب الميزة المحددة لأن هذه المناقشة يجب ألا تكون قادرة الناس ولكن بدلا من اللغة): _

_ "تحتاج ميزة اللغة الجديدة إلى حالات استخدام مقنعة. جميع ميزات اللغة مفيدة ، أو لن يقترحها أحد ؛ السؤال هو: هل هي مفيدة بما يكفي لتبرير تعقيد اللغة ومطالبة الجميع بتعلم المفاهيم الجديدة؟ ما هو الاستخدام المقنع الحالات هنا؟ كيف سيستخدمها الأشخاص؟ على سبيل المثال ، هل يتوقع الناس أن يكونوا قادرين على ... وإذا كان الأمر كذلك فكيف سيفعلون ذلك؟ هل هذا الاقتراح يفعل أكثر من السماح لك ...؟ "_
- عضو أساسي في فريق Go

بصراحة عندما رأيت تلك الردود شعرت بواحد من شعورين:

  1. استياء إذا كانت ميزة أتفق معها ، أو
  2. الابتهاج إذا كانت ميزة لا أتفق معها.

لكن في كلتا الحالتين كانت مشاعري غير ذات صلة ؛ أفهم وأوافق على أن جزءًا من سبب Go هي اللغة التي يختار الكثير منا تطويرها بسبب تلك الحراسة الغيورة لنقاء اللغة.

وهذا هو السبب في أن هذا الاقتراح يزعجني ، لأن فريق Go الأساسي يبدو أنه يبحث في هذا الاقتراح على نفس المستوى لشخص يريد بشكل دوغمائي ميزة مقصورة على فئة معينة لا توجد وسيلة في الجحيم سيتسامح معها مجتمع Go.

_ (وآمل حقًا ألا يقوم الفريق بإطلاق النار على الرسول وأن يأخذ هذا على أنه نقد بناء من شخص يريد أن يرى Go يواصل كونه الأفضل لنا جميعًا حيث يجب أن يتم اعتباره "شخصًا غير مرغوب فيه" من قِبل الفريق الأساسي.) _

إذا كان يتطلب مجموعة مقنعة من حالات الاستخدام في العالم الحقيقي هو الشريط لجميع مقترحات الميزات التي ينشئها المجتمع ، ألا يجب أن يكون هو نفس الشريط لـ _ جميع _ مقترحات الميزات؟

تعشيش المحاولة ()

تمت تغطية هذا أيضًا بواسطة عدد قليل ، لكنني أريد إجراء مقارنة بين try() والطلب المستمر للمشغلين الثلاثي. نقلاً عن تعليقات عضو آخر في فريق Go منذ حوالي 18 شهرًا:

_ "عند" البرمجة بشكل كبير "(قواعد التعليمات البرمجية الكبيرة بفرق كبيرة على مدى فترات طويلة من الوقت) ، تتم قراءة الكود بالطريقة أكثر مما تتم كتابته ، لذلك نقوم بالتحسين من أجل سهولة القراءة وليس قابلية الكتابة." _

أحد الأسباب _ الأولية_ المعلنة لعدم إضافة العوامل الثلاثية هو صعوبة قراءتها و / أو إساءة قراءتها عند دمجها. ومع ذلك ، يمكن أن يكون الأمر نفسه صحيحًا بالنسبة لكشوفات try() المتداخلة مثل try(try(try(to()).parse().this)).easily()) .

كانت الأسباب الإضافية للجدل ضد العوامل الثلاثية هي أنها _ "تعبيرات" _ مع هذه الوسيطة القائلة بأن التعبيرات المتداخلة يمكن أن تضيف تعقيدًا. لكن ألا ينشئ try() تعبيرًا متداخلًا أيضًا؟

الآن قال أحدهم هنا _ "أعتقد أن أمثلة مثل [المتداخلة try() s] غير واقعية" _ ولم يتم الطعن في هذا البيان.

ولكن إذا قبل الناس على سبيل الافتراض بأن المطورين لن يقوموا بإدراج try() ، فلماذا لا يتم إعطاء نفس الاحترام للمشغلين الثلاثي عندما يقول الناس _ "أعتقد أن العوامل الثلاثية المتداخلة بعمق غير واقعية؟" _

خلاصة القول لهذه النقطة ، أعتقد أنه إذا كانت الحجة ضد العوامل الثلاثية صحيحة حقًا ، فيجب اعتبارها أيضًا حججًا صالحة ضد هذا الاقتراح try() .

باختصار

في وقت كتابة هذه السطور ، كانت الأصوات السفلية لـ $ 58% لصالح 42% . أعتقد أن هذا وحده يجب أن يكون كافيًا للإشارة إلى أن هذا الاقتراح مثير للانقسام بدرجة كافية ، وقد حان الوقت للعودة إلى لوحة الرسم بشأن هذه المسألة.

fwiw

ملاحظة: لوضعها أكثر في الخد ، أعتقد أننا يجب أن نتبع حكمة يودا المعاد صياغتها:

_ "لا يوجد try() . فقط do() ." _

تضمين التغريدة

beoran ربما يمكنك توسيع العلاقة بين الأدوية الجنيسة ومعالجة الأخطاء.

لا أتحدث باسم beoran ولكن في تعليقي منذ بضع دقائق ، سترى أنه إذا كان لدينا أدوية _ (بالإضافة إلى معلمات الإرجاع المتغيرة) _ فيمكننا بناء try() الخاص بنا.

ومع ذلك - وسأكرر ما قلته أعلاه حول الأدوية الجنسية هنا حيث سيكون من الأسهل رؤيتها:

_ "أعتقد أنه يجب تقليص حالات استخدام الأدوية الجنيسة عن طريق إضافة عناصر مدمجة لمعالجة حالات الاستخدام العامة بدلاً من إضافة الدلالات المربكة وسلطة بناء الجملة للأدوية من Java وآخرون)" _

تضمين التغريدة

عند محاولة صياغة إجابة لسؤالك ، حاولت تنفيذ وظيفة try في Go كما هي ، ومن دواعي سروري أنه من الممكن بالفعل محاكاة شيء مشابه تمامًا:

func try(v interface{}, err error) interface{} {
   if err != nil { 
     panic(err)
   }
   return v
}

انظر هنا كيف يمكن استخدامه: https://play.golang.org/p/Kq9Q0hZHlXL

مساوئ هذا النهج هي:

  1. هناك حاجة إلى إنقاذ مؤجل ، ولكن مع try كما في هذا الاقتراح ، هناك حاجة أيضًا إلى معالج مؤجل إذا أردنا القيام بمعالجة الأخطاء بشكل صحيح. لذلك أشعر أن هذا ليس جانبًا سلبيًا خطيرًا. قد يكون من الأفضل أن يكون لدى Go نوع من super(arg1, ..., argn) مدمج يتسبب في عودة المتصل ، بمستوى أعلى في مكدس الاستدعاءات ، بالوسيطات المعطاة arg1 ، ... argn ، نوع من الإرجاع الفائق إن شئت.
  2. هذا try الذي قمت بتنفيذه يمكنه العمل فقط مع دالة تقوم بإرجاع نتيجة واحدة وخطأ.
  3. يجب عليك كتابة تأكيد نتائج واجهة emtpy التي تم إرجاعها.

يمكن للأدوية القوية بشكل كافٍ أن تحل المشكلة 2 و 3 ، مع ترك 1 فقط ، والذي يمكن حله بإضافة super() . مع هاتين الميزتين ، يمكننا الحصول على شيء مثل:

func (T ... interface{})try(T, err error) super {
   if err != nil { 
      super(err)
   }
  super(T...)
}

وبعد ذلك لن تكون هناك حاجة إلى الإنقاذ المؤجل بعد الآن. ستكون هذه الميزة متاحة حتى إذا لم تتم إضافة الأدوية الجنيسة إلى Go.

في الواقع ، هذه الفكرة الخاصة بالبناء الفائق () قوية جدًا ومثيرة للاهتمام لدرجة أنني قد أنشر اقتراحًا لها بشكل منفصل.

beoran من الجيد أن نرى أننا توصلنا إلى نفس القيود تمامًا بشكل مستقل فيما يتعلق بتنفيذ try() في userland ، باستثناء الجزء الممتاز الذي لم أدرجه لأنني أردت التحدث عن شيء مشابه في اقتراح بديل. :-)

يعجبني الاقتراح ولكن حقيقة أنه كان عليك تحديد صراحة أن defer try(...) و go try(...) غير مسموح بهما جعلني أعتقد أن شيئًا ما لم يكن صحيحًا تمامًا ... التعامد هو دليل تصميم جيد. لمزيد من القراءة ورؤية أشياء مثل
x = try(foo(...)) y = try(bar(...))
أتساءل عما إذا كان قد يكون try يحتاج إلى سياق ! انصح:
try ( x = foo(...) y = bar(...) )
هنا ترجع قيمتا foo() و bar() قيمتين ، والثانية هي error . جرب الدلالات المهمة فقط للمكالمات داخل الكتلة try حيث يتم حذف قيمة الخطأ المُعاد (بدون مُستقبل) بدلاً من تجاهلها (المتلقي هو _ ). يمكنك حتى معالجة بعض الأخطاء بين foo و bar للمكالمات.

ملخص:
أ) مشكلة عدم السماح بـ try لـ go و defer تختفي بحكم التركيب.
ب) يمكن معالجة الخطأ في معالجة الوظائف المتعددة.
ج) يتم التعبير عن طبيعتها السحرية بشكل أفضل على أنها بناء جملة خاص بدلاً من استدعاء دالة.

إذا كانت المحاولة عبارة عن سياق ، فقد قمنا للتو بإعداد كتل المحاولة / الالتقاط التي نحاول على وجه التحديد تجنبها (ولسبب وجيه)

لا يوجد صيد. سيتم إنشاء نفس الرمز بالضبط كما هو الحال عندما يكون الاقتراح الحالي
x = try(foo(...)) y = try(bar(...))
هذا مجرد بناء جملة مختلف ، وليس دلالات.
""

أعتقد أنني وضعت بعض الافتراضات حول هذا الموضوع والتي لم يكن يجب أن أفعلها ، على الرغم من أنه لا يزال هناك عيبان في ذلك.

ماذا لو لم تُرجع foo أو bar خطأً ، فهل يمكن وضعها في سياق try أيضًا؟ إذا لم يكن الأمر كذلك ، يبدو أنه سيكون من القبيح نوعًا ما التبديل بين وظائف الخطأ والوظائف غير المتعلقة بالخطأ ، وإذا كان بإمكانهم ذلك ، فإننا نعود إلى مشكلات تجربة الكتل في اللغات القديمة.

الشيء الثاني هو أن بناء الجملة keyword ( ... ) يعني أنك تسبق الكلمة الأساسية في كل سطر. لذلك بالنسبة للاستيراد ، var ، const ، إلخ: يبدأ كل سطر بالكلمة الرئيسية. إجراء استثناء لهذه القاعدة لا يبدو قرارًا جيدًا

بدلاً من استخدام دالة ، هل سيكون الأمر أكثر اصطلاحًا باستخدام معرف خاص؟

لدينا بالفعل المعرف الفارغ _ الذي يتجاهل القيم.
يمكن أن يكون لدينا شيء مثل # والذي لا يمكن استخدامه إلا في الوظائف التي تحتوي على آخر قيمة تم إرجاعها من نوع الخطأ.

func foo() (error) {
    f, # := os.Open()
    defer f.Close()
    _, # = f.WriteString("foo")
    return nil
}

عند تعيين خطأ إلى # ، تعود الدالة فورًا مع تلقي الخطأ. أما بالنسبة للمتغيرات الأخرى ، فستكون قيمها:

  • إذا لم يتم تسميتها بقيمة صفرية
  • القيمة المخصصة للمتغيرات المسماة بخلاف ذلك

deanveloper ، دلالات الكتلة try مهمة فقط للوظائف التي تُرجع قيمة خطأ وحيث لا يتم تعيين قيمة الخطأ. لذلك يمكن أيضًا كتابة المثال الأخير على الاقتراح الحالي بصيغة
try(x = foo(...)) try(y = bar(...))
وضع كلا العبارتين في نفس الكتلة يشبه ما نقوم به من أجل الكشوف المتكررة import ، const و var .

الآن إذا كان لديك ، على سبيل المثال
try( x = foo(...)) go zee(...) defer fum() y = bar(...) )
هذا يعادل الكتابة
try(x = foo(...)) go zee(...) defer fum() try(y = bar(...))
إن احتساب كل ذلك في كتلة محاولة واحدة يجعله أقل انشغالًا.

انصح
try(x = foo())
إذا لم يُرجع foo () قيمة خطأ ، فهذا يعادل
x = foo()

انصح
try(f, _ := os.open(filename))
نظرًا لتجاهل قيمة الخطأ المرتجعة ، فإن هذا يعادل فقط
f, _ := os.open(filename)

انصح
try(f, err := os.open(filename))
نظرًا لعدم تجاهل قيمة الخطأ الذي تم إرجاعه ، فهذا يعادل
f, err := os.open(filename) if err != nil { return ..., err }
كما هو محدد حاليا في الاقتراح.

كما أنه يزيل المحاولات المتداخلة بشكل جيد!

فيما يلي رابط إلى الاقتراح البديل الذي ذكرته أعلاه:

يدعو إلى إضافة ميزتين (2) صغيرتين لكن للأغراض العامة لمعالجة حالات الاستخدام نفسها مثل try()

  1. القدرة على استدعاء func / إغلاق في بيان التخصيص.
  2. القدرة على break ، continue أو return أكثر من مستوى واحد.

مع هاتين الميزتين لن يكونا _ "سحريتين" _ وأعتقد أن استخدامهما سينتج رمز Go الذي يسهل فهمه وأكثر انسجامًا مع كود Go الاصطلاحي الذي نعرفه جميعًا.

لقد قرأت الاقتراح وأعجبني حقًا إلى أين تتجه المحاولة.

بالنظر إلى مدى انتشار المحاولة ، أتساءل عما إذا كان جعلها سلوكًا افتراضيًا أكثر سيجعل التعامل معها أسهل.

ضع في اعتبارك الخرائط. هذا صحيح:

v := m[key]

هكذا:

v, ok := m[key]

ماذا لو تعاملنا مع الأخطاء تمامًا بالطريقة التي تقترحها المحاولة ، لكن أزلنا المضمنة. لذلك إذا بدأنا بـ:

v, err := fn()

بدلا من الكتابة:

v := try(fn())

يمكننا بدلاً من ذلك أن نكتب:

v := fn()

عندما لا يتم تسجيل قيمة الخطأ ، يتم التعامل معها تمامًا كما تفعل المحاولة. قد يستغرق التعود قليلاً ، لكنه يبدو مشابهًا جدًا لـ v, ok := m[key] و v, ok := x.(string) . بشكل أساسي ، يؤدي أي خطأ غير معالج إلى إرجاع الوظيفة وتعيين قيمة الخطأ.

للعودة إلى استنتاجات مستندات التصميم ومتطلبات التنفيذ:

• يتم الاحتفاظ بصيغة اللغة ولا يتم إدخال كلمات رئيسية جديدة
• لا يزال السكر النحوي مثل المحاولة ونأمل أن يكون من السهل شرحه.
• لا يتطلب بناء جملة جديدة
• يجب أن يكون متوافقًا تمامًا مع الإصدارات السابقة.

أتخيل أن هذا سيكون له نفس متطلبات التنفيذ تقريبًا مثل المحاولة حيث أن الاختلاف الأساسي هو بدلاً من أن يقوم العنصر المدمج بتشغيل السكر النحوي ، فهو الآن غياب حقل الخطأ.

لذا باستخدام المثال CopyFile من الاقتراح مع defer fmt.HandleErrorf(&err, "copy %s %s", src, dst) ، نحصل على:

func CopyFile(src, dst string) (err error) {
        defer fmt.HandleErrorf(&err, "copy %s %s", src, dst)

        r := os.Open(src)
        defer r.Close()

        w := os.Create(dst)
        defer func() {
                err := w.Close()
                if err != nil {
                        os.Remove(dst) // only if a “try” fails
                }
        }()

        io.Copy(w, r)
        w.Close()
        return nil
}

savaki ، لقد أحببت هذا وكنت أفكر في ما يتطلبه الأمر لجعل Go يقلب معالجة الخطأ من خلال معالجة الأخطاء دائمًا بشكل افتراضي والسماح للمبرمج بتحديد متى لا يفعل ذلك (من خلال التقاط الخطأ في متغير) ولكن الافتقار التام لأي شيء المعرف سيجعل من الصعب متابعة الكود لأن المرء لن يكون قادرًا على رؤية جميع نقاط الإرجاع. قد يكون اصطلاحًا لتسمية الوظائف التي يمكن أن تعرض خطأ بشكل مختلف يمكن أن تعمل (مثل كتابة المعرفات العامة بأحرف كبيرة). قد يكون ذلك في حالة عرض دالة لخطأ ، يجب أن تنتهي دائمًا بـ ، دعنا نقول ? . ثم يمكن لـ Go دائمًا معالجة الخطأ ضمنيًا وإعادته تلقائيًا إلى وظيفة الاستدعاء تمامًا مثل try. هذا يجعله مشابهًا جدًا لبعض المقترحات التي تقترح استخدام معرف ? بدلاً من المحاولة ولكن الاختلاف المهم هنا هو أن ? سيكون جزءًا من اسم الوظيفة وليس معرفًا إضافيًا. في الواقع ، لن يتم تجميع الوظيفة التي أعادت error كقيمة إرجاع أخيرة إذا لم يتم لصقها ? . بالطبع ? تعسفي ويمكن استبداله بأي شيء آخر يجعل النية أكثر وضوحًا. سيكون operation?() معادلاً للالتفاف try(someFunc()) لكن ? سيكون جزءًا من اسم الوظيفة وسيكون الغرض الوحيد منه هو الإشارة إلى أن الوظيفة يمكنها إرجاع خطأ تمامًا مثل الكتابة بالأحرف الكبيرة الحرف الأول من المتغير.

ينتهي هذا الأمر بشكل سطحي بأن يكون مشابهًا جدًا للمقترحات الأخرى التي تطلب استبدال try بـ ? لكن الاختلاف الجوهري هو أنه يجعل معالجة الخطأ ضمنيًا (تلقائي) وبدلاً من ذلك يجعل تجاهل (أو التفاف) أخطاء واضحة والتي نوع من أفضل الممارسات على أي حال. المشكلة الأكثر وضوحًا في هذا بالطبع هي أنه غير متوافق مع الإصدارات السابقة وأنا متأكد من وجود المزيد.

ومع ذلك ، سأكون مهتمًا جدًا برؤية كيف يمكن لـ Go أن يخطئ في معالجة الحالة الافتراضية / الضمنية عن طريق أتمتة والسماح للمبرمج بكتابة القليل من التعليمات البرمجية الإضافية لتجاهل / تجاوز المعالجة. التحدي الذي أعتقده هو كيفية جعل جميع نقاط العودة واضحة في هذه الحالة لأنه بدونها ستصبح الأخطاء أشبه بالاستثناءات بمعنى أنها يمكن أن تأتي من أي مكان لأن تدفق البرنامج لن يجعل الأمر واضحًا. يمكن للمرء أن يقول أن ارتكاب أخطاء ضمنية باستخدام المؤشر المرئي هو نفسه تنفيذ try وجعل errcheck فشلًا في المترجم.

هل يمكننا عمل شيء مثل استثناءات c ++ مع أدوات التزيين للوظائف القديمة؟

func some_old_test() (int, error){
    return 0, errors.New("err1")
}
func some_new_test() (int){
        if true {
             return 1
        }
    throw errors.New("err2")
}
func throw_res(int, e error) int {
    if e != nil {
        throw e
    }
    return int
}
func main() {
    fmt.Println("Hello, playground")
    try{
        i := throw_res(some_old_test())
        fmt.Println("i=", i + some_new_test())
    } catch(err io.Error) {
        return err
    } catch(err error) {
        fmt.Println("unknown err", err)
    }
}

owais كنت أفكر في أن الدلالات ستكون تمامًا مثل المحاولة ، لذا على الأقل ستحتاج إلى التصريح عن نوع الخطأ. لذلك إذا بدأنا بـ:

func foo() error {
  _, err := fn() 
  if err != nil {
    return err
  }

  return nil
} 

إذا فهمت اقتراح المحاولة ، فما عليك سوى القيام بذلك:

func foo() error {
  _  := fn() 
  return nil
} 

لن يتم تجميعها. من المزايا الرائعة أنه يمنح المترجم فرصة لإخبار المستخدم بما ينقصه. هناك شيء يشير إلى أن استخدام معالجة الخطأ الضمني يتطلب تسمية نوع إرجاع الخطأ ، يخطئ.

هذا بعد ذلك ، من شأنه أن يعمل:

func foo() (err error) {
  _  := fn() 
  return nil
} 

لماذا لا نتعامل فقط مع حالة الخطأ التي لم يتم تعيينها إلى متغير.

  • إزالة الحاجة إلى العوائد المسماة ، يمكن للمترجم أن يفعل كل ذلك بمفرده.
  • يسمح بإضافة سياق.
  • يتعامل مع حالة الاستخدام الشائع.
  • متوافق مع الجيل السابق
  • لا يتفاعل بشكل غريب مع التأجيل أو الحلقات أو المفاتيح.

عودة ضمنية لحالة if err! = nil ، يمكن للمجمع إنشاء اسم متغير محلي للإرجاع إذا لزم الأمر لا يمكن للمبرمج الوصول إليه.
أنا شخصيا لا أحب هذه الحالة بالذات من نقطة الوقوف لسهولة قراءة الكود

f := os.Open("foo.txt")

تفضل عائدًا صريحًا ، يتبع الكود قراءة أكثر من المانترا المكتوبة

f := os.Open("foo.txt") else return

من المثير للاهتمام أنه يمكننا قبول كلا النموذجين ، وجعل gofmt يضيف عائد else تلقائيًا.

إضافة سياق ، وكذلك تسمية محلية للمتغير. تصبح العودة صريحة لأننا نريد إضافة سياق.

f := os.Open("foo.txt") else err {
  return errors.Wrap(err, "some context")
}

إضافة سياق بقيم إرجاع متعددة

f := os.Open("foo.txt") else err {
  return i, j, errors.Wrap(err, "some context")
}

تتطلب الدوال المتداخلة أن تعالج الوظائف الخارجية جميع النتائج بنفس الترتيب
ناقص الخطأ النهائي.

bits := ioutil.ReadAll(os.Open("foo")) else err {
  // either error ends up here.
  return i, j, errors.Wrap(err, "some context")
}

المترجم يرفض التجميع بسبب فقدان قيمة إرجاع الخطأ في الوظيفة

func foo(s string) int {
   i := strconv.Atoi(s) // cannot implicitly return error due to missing error return value for foo.
   return i * 2
}

يجمع لحسن الحظ لأنه يتم تجاهل الخطأ صراحة.

func foo(s string) int {
   i, _ := strconv.Atoi(s)
   return i * 2
} 

المترجم سعيد. يتجاهل الخطأ كما هو الحال حاليًا بسبب عدم حدوث أي تعيين أو لاحقة أخرى.

func foo() error {
  return errors.New("whoops")
}

func bar() {
  foo()
}

داخل حلقة يمكنك استخدام متابعة.

for _, s := range []string{"1","2","3","4","5","6"} {
  i := strconv.Atoi(s) else continue
}

تحرير: استبدلت ; بـ else

savaki أعتقد أنني فهمت تعليقك الأصلي وأحب فكرة Go التعامل مع الأخطاء بشكل افتراضي ولكن لا أعتقد أنها قابلة للتطبيق دون إضافة بعض التغييرات الإضافية في بناء الجملة وبمجرد قيامنا بذلك ، يصبح الأمر مشابهًا بشكل لافت للنظر للاقتراح الحالي.

أكبر جانب سلبي لما تقترحه هو أنه لا يعرض جميع النقاط من حيث يمكن أن تعود الوظيفة على عكس if err != nil {return err} الحالي أو وظيفة try التي تم تقديمها في هذا الاقتراح. على الرغم من أنه سيعمل بنفس الطريقة تمامًا تحت الغطاء ، إلا أن الكود سيبدو بصريًا مختلفًا تمامًا. عند قراءة الكود ، لن تكون هناك طريقة لمعرفة استدعاءات الوظائف التي قد تؤدي إلى حدوث خطأ. سينتهي الأمر بأن تكون تجربة أسوأ من استثناءات المنظمة البحرية الدولية.

قد يكون معالجة الأخطاء ضمنيًا إذا فرض المترجم بعض الاصطلاحات الدلالية على الوظائف التي يمكن أن ترجع أخطاء. مثل أنها يجب أن تبدأ أو تنتهي بعبارة أو شخصية معينة. سيؤدي ذلك إلى جعل جميع نقاط الإرجاع واضحة جدًا وأعتقد أنه سيكون أفضل من معالجة الأخطاء اليدوية ولكن لست متأكدًا من مدى أفضل بكثير بالنظر إلى وجود فحوصات الوبر التي تستدعي الحمل عند اكتشاف خطأ يتم تجاهله. سيكون من المثير للاهتمام معرفة ما إذا كان المترجم يمكنه فرض تسمية الوظائف بطريقة معينة اعتمادًا على ما إذا كان بإمكانهم إرجاع الأخطاء المحتملة.

العيب الرئيسي لهذا النهج هو أن معلمة نتيجة الخطأ تحتاج إلى تسمية ، مما قد يؤدي إلى واجهات برمجة تطبيقات أقل جمالًا (لكن راجع الأسئلة الشائعة حول هذا الموضوع). نعتقد أننا سنعتاد عليه بمجرد أن يثبت هذا الأسلوب نفسه.

لست متأكدًا مما إذا كان قد تم اقتراح شيء كهذا من قبل ، أو لا يمكنك العثور عليه هنا أو في الاقتراح. هل فكرت في دالة مضمنة أخرى تُرجع مؤشرًا لقيمة إرجاع الخطأ للوظيفة الحالية؟
على سبيل المثال:

func example() error {
        var err *error = funcerror() // always return a non-nil pointer
        fmt.Print(*err) // always nil if the return parameters are not named and not in a defer

        defer func() {
                err := funcerror()
                fmt.Print(*err) // "x"
        }

        return errors.New("x")
}
func exampleNamed() (err error) {
        funcErr := funcerror()
        fmt.Print(*funcErr) // "nil"

        err = errors.New("x")
        fmt.Print(*funcErr) // "x", named return parameter is reflected even before return is called

        *funcErr = errors.New("y")
        fmt.Print(err) // "y", unfortunate side effect?

        defer func() {
                funcErr := funcerror()
                fmt.Print(*funcErr) // "z"
                fmt.Print(err) // "z"
        }

        return errors.New("z")
}

الاستخدام مع المحاولة:

func CopyFile(src, dst string) (error) {
        defer func() {
                err := funcerror()
                if *err != nil {
                        *err = fmt.Errorf("copy %s %s: %v", src, dst, err)
                }
        }()
        // one liner alternative
        // defer fmt.HandleErrorf(funcerror(), "copy %s %s", src, dst)

        r := try(os.Open(src))
        defer r.Close()

        w := try(os.Create(dst))
        defer func() {
                w.Close()
                err := funcerror()
                if *err != nil {
                        os.Remove(dst) // only if a “try” fails
                }
        }()

        try(io.Copy(w, r))
        try(w.Close())
        return nil
}

وبدلاً من ذلك ، فإن funcerror (الاسم هو عمل قيد التنفيذ: D) يمكن أن يعود بلا شيء إذا لم يتم استدعاؤه بالداخل تأجيل.

البديل الآخر هو أن funcerror إرجاع واجهة "Errorer" لجعلها للقراءة فقط:

type interface Errorer() {
        Error() error
}

savaki أنا في الواقع أحب اقتراحك بحذف try() والسماح له بأن يكون أشبه باختبار خريطة أو تأكيد نوع. هذا يشعر أكثر بكثير _ "Go-like." _

ومع ذلك ، لا تزال هناك مشكلة صارخة واحدة أراها ، وهذا هو اقتراحك الذي يفترض أن جميع الأخطاء باستخدام هذا الأسلوب ستؤدي إلى return وتترك الوظيفة. ما لا يفكر فيه هو إصدار break من for الحالي أو continue الحالي for .

في وقت مبكر return s عبارة عن مطرقة ثقيلة عندما يكون المشرط في كثير من الأحيان هو الخيار الأفضل.

لذلك أؤكد أن break و continue يجب أن يكونا إستراتيجيتين صالحتين لمعالجة الأخطاء وحاليًا لا يفترض اقتراحك سوى return بينما try() يفترض ذلك أو استدعاء خطأ المعالج نفسه يمكنه فقط return ، وليس break أو continue .

يبدو أن سافاكي وكان لدي أفكار مماثلة ، لقد أضفت للتو دلالات الكتلة للتعامل مع الخطأ إذا رغبت في ذلك. على سبيل المثال إضافة comtext ، للحلقات حيث تريد ماس كهربائى وما إلى ذلك

mikeschinkel انظر إلى امتدادي ، كان لدي هو وأنا أفكار متشابهة ، لقد قمت للتو بتوسيعها ببيان كتلة اختياري

@ جيمس لورانس

mikesckinkel انظر إلى الامتداد الخاص بي ، لقد كان لدي وأنا أفكار متشابهة ، لقد قمت للتو بتوسيعها ببيان كتلة اختياري

أخذ المثال الخاص بك:

f := os.Open("foo.txt"); err {
  return errors.Wrap(err, "some context")
}

والذي يقارن بما نقوم به اليوم:

f,err := os.Open("foo.txt"); 
if err != nil {
  return errors.Wrap(err, "some context")
}

هو بالتأكيد الأفضل بالنسبة لي. باستثناء أنه يحتوي على بعض المشكلات:

  1. يبدو أن err _ تم إعلانه _ "بطريقة سحرية". يجب التقليل من السحر ، أليس كذلك؟ لذلك دعونا نعلن ذلك:
f, err := os.Open("foo.txt"); err {
  return errors.Wrap(err, "some context")
}
  1. لكن هذا لا يزال لا يعمل لأن Go لا يفسر قيم nil على أنها false ولا قيم المؤشر كـ true ، لذلك يجب أن يكون:
f, err := os.Open("foo.txt"); err != nil {
  return errors.Wrap(err, "some context")
}

وما ينجح ، يبدأ في الشعور بنفس القدر من العمل والكثير من بناء الجملة في سطر واحد ، لذلك قد أستمر في القيام بالطريقة القديمة من أجل الوضوح.

ولكن ماذا لو أضاف Go اثنين (2) من المباني ؛ iserror() و error() ؟ ثم يمكننا القيام بذلك ، وهذا لا أشعر بالسوء بالنسبة لي:

f := os.Open("foo.txt"); iserror() {
  return errors.Wrap(error(), "some context")
}

أو أفضل _ (شيء من هذا القبيل): _

f := os.Open("foo.txt"); iserror() {
  return error().Extend("some context")
}

ماذا تعتقد والآخرين؟

جانبا ، تحقق من هجاء اسم المستخدم الخاص بي. لم يتم إخطاري بذكرك إذا لم أكن منتبهًا على أي حال ...

mikeschinkel آسف على الاسم الذي كنت على هاتفي ولم يكن جيثب يقترح تلقائيًا.

يبدو أنه تم التصريح عن الخطأ "بطريقة سحرية". يجب التقليل من السحر ، أليس كذلك؟ لذلك دعونا نعلن ذلك:

meh ، فإن الفكرة الكاملة لإدراج المرتجعات تلقائيًا هي فكرة سحرية. ليس هذا هو الشيء الأكثر سحراً في هذا الاقتراح بأكمله. بالإضافة إلى أنني سأجادل في إعلان الخطأ ؛ فقط في النهاية داخل سياق الكتلة المحددة النطاق مما يمنعها من تلويث النطاق الأصلي مع الحفاظ على جميع الأشياء الجيدة التي نحصل عليها بشكل طبيعي باستخدام عبارات if.

أنا سعيد بشكل عام بمعالجة أخطاء go's مع الإضافات القادمة لحزمة الأخطاء. لا أرى أي شيء في هذا الاقتراح مفيد للغاية. أنا فقط أحاول تقديم أكثر ملاءمة طبيعية لـ golang إذا كنا قد انتهينا من القيام بذلك.

_ "إن فكرة إدراج المرتجعات تلقائيًا هي فكرة سحرية." _

لن تحصل على أي جدال مني هناك.

_ "ليس هذا هو الشيء الأكثر سحراً في هذا الاقتراح بأكمله." _

أعتقد أنني كنت أحاول أن أجادل في أن _ "كل السحر يمثل مشكلة." _

_ "بالإضافة إلى أنني سأجادل بأن الخطأ قد تم الإعلان عنه ؛ فقط في النهاية داخل سياق الكتلة المحددة النطاق ..." _

لذا إذا أردت أن أسميها err2 فهل هذا يعمل أيضًا؟

f := os.Open("foo.txt"); err2 {
  return errors.Wrap(err, "some context")
}

لذلك أفترض أنك تقترح أيضًا معالجة حالة خاصة لـ err / err2 بعد الفاصلة المنقوطة ، أي أنه من المفترض أن تكون إما nil أو لا nil بدلاً من bool مثل عند فحص الخريطة؟

if _,ok := m[a]; !ok {
   print("there is no 'a' in 'm'")
}

أنا سعيد بشكل عام بمعالجة أخطاء go's مع الإضافات القادمة لحزمة الأخطاء.

أنا سعيد أيضًا بمعالجة الأخطاء ، عند دمجها مع break و continue _ (لكن ليس return .) _

كما هو الحال ، أرى أن هذا الاقتراح try() أكثر ضررًا من كونه مفيدًا ، وأفضّل ألا أرى شيئًا سوى هذا التنفيذ كما هو مقترح. # ج.

beoranmikeschinkel لقد اقترحت سابقًا أنه لا يمكننا تنفيذ هذا الإصدار من try باستخدام الأدوية الجنيسة ، لأنه يغير تدفق التحكم. إذا كنت أقرأ بشكل صحيح ، فأنتما تقترحان أنه يمكننا استخدام الأدوية العامة لتنفيذ try من خلال جعلها تستدعي panic . لكن هذا الإصدار من try لا يعني صراحة panic . لذلك لا يمكننا استخدام الأدوية العامة لتنفيذ هذا الإصدار من try .

نعم ، يمكننا استخدام الأدوية الجنيسة (نسخة من الأدوية العامة أقوى بكثير من تلك الموجودة في مسودة التصميم على https://go.googlesource.com/proposal/+/master/design/go2draft-contracts.md) لكتابة وظيفة هذا الذعر على الخطأ. لكن الذعر من الخطأ ليس من النوع الذي يكتبه مبرمجو Go اليوم ، ولا يبدو أنه فكرة جيدة بالنسبة لي.

المعالجة الخاصة لـmikeschinkel هي أن الكتلة يتم تنفيذها فقط عندما يكون هناك خطأ.
""
f: = os.Open ('foo') ؛ الخطأ {إرجاع الخطأ} // سيكون دائمًا غير معدوم هنا.

تضمين التغريدة

_ "نعم ، يمكننا استخدام الأدوية الجنيسة ... لكن الذعر من الخطأ ليس نوع معالجة الأخطاء الذي يكتبه مبرمجو Go اليوم ، ولا يبدو أنه فكرة جيدة بالنسبة لي." _

أنا أتفق معك بشدة في هذا الأمر ، وبالتالي يبدو أنك قد أساءت تفسير القصد من تعليقي. لم أكن أقترح على الإطلاق أن يقوم فريق Go بتنفيذ معالجة الأخطاء التي تستخدم panic() - بالطبع لا.

بدلاً من ذلك ، كنت أحاول فعلاً متابعة تقدمك من العديد من تعليقاتك السابقة حول قضايا أخرى واقترحت أن نتجنب إجراء أي تغييرات على Go ليست ضرورية تمامًا لأنها ممكنة بدلاً من ذلك في userland . إذاً _if_ تمت معالجة الأدوية الجنيسة _ ثم يمكن للأشخاص الذين يريدون try() تنفيذها بأنفسهم ، وإن كان ذلك من خلال الاستفادة من panic() . وستكون هذه ميزة أقل سيحتاج الفريق إلى إضافتها وتوثيقها لـ Go.

ما لم أفعله - وربما لم يكن ذلك واضحًا - كان يدافع عن أن يستخدم الأشخاص بالفعل panic() لتنفيذ try() ، فقط يمكنهم ذلك إذا أرادوا حقًا ، وكان لديهم ميزات الأدوية.

هل هذا يوضح؟

بالنسبة لي ، فإن الاتصال بـ panic ، ومع ذلك ، يختلف تمامًا عن هذا الاقتراح لـ try . لذا ، بينما أعتقد أنني أفهم ما تقوله ، فأنا لا أوافق على أنهما متكافئان. حتى لو كان لدينا أدوية قوية بما يكفي لتنفيذ إصدار try هذا الذعر ، أعتقد أنه لا تزال هناك رغبة معقولة في إصدار try المقدم في هذا الاقتراح.

ianlancetaylor تم الإقرار به. مرة أخرى ، كنت أبحث عن سبب عدم الحاجة إلى إضافة try() بدلاً من إيجاد طريقة لإضافته. كما قلت أعلاه ، أفضل ألا يكون لدي أي شيء جديد لمعالجة الأخطاء من أن يكون لدي try() كما هو مقترح هنا.

أنا شخصياً أحببت الاقتراح السابق check أكثر من هذا ، بناءً على الجوانب المرئية البحتة ؛ كان لدى check نفس القوة مثل try() لكن bar(check foo()) أكثر قابلية للقراءة بالنسبة لي من bar(try(foo())) (كنت فقط بحاجة إلى ثانية لحساب الأقواس!).

والأهم من ذلك ، أن ما استحوذت عليه بشأن handle / check هو أنه لا يسمح بتغليف الشيكات الفردية بطرق مختلفة - والآن هذا الاقتراح try() له نفس الخلل ، أثناء استدعاء ميزات مربكة للمبتدئين ونادراً ما تستخدم للمبتدئين والعائدات المسماة. ومع handle على الأقل ، كان لدينا خيار استخدام النطاقات لتحديد كتل المقابض ، مع defer حتى أن هذا غير ممكن.

بقدر ما أشعر بالقلق ، فإن هذا الاقتراح يخسر أمام الاقتراح السابق handle / check في جميع الجوانب.

إليك مصدر قلق آخر بشأن استخدام المؤجِّلات لمعالجة الأخطاء.

try هو خروج مقصود / متحكم فيه من دالة. يعمل defers دائمًا ، بما في ذلك عمليات الخروج غير المنضبطة / غير المقصودة من الوظائف. قد يؤدي عدم التطابق إلى حدوث ارتباك. إليك سيناريو وهمي:

func someHTTPHandlerGuts() (err error) {
  defer func() {
    recordMetric("db call failed")
    return fmt.HandleErrorf("db unavailable: %v", err)
  }()
  data := try(makeDBCall)
  // some code that panics due to a bug
  return nil
}

تذكر أن net / http يتعافى من الذعر ، وتخيل تصحيح مشكلة إنتاج حول الذعر. ستنظر إلى الأجهزة الخاصة بك وترى ارتفاعًا مفاجئًا في حالات فشل مكالمات db ، من المكالمات recordMetric . قد يخفي هذا المشكلة الحقيقية ، وهي الذعر في السطر التالي.

لست متأكدًا من مدى خطورة هذا القلق في الممارسة العملية ، ولكن (للأسف) ربما يكون سببًا آخر للاعتقاد بأن التأجيل ليس آلية مثالية للتعامل مع الأخطاء.

إليك تعديل قد يساعد في حل بعض المخاوف المثارة: تعامل مع try مثل goto بدلاً من مثل return . أستمع لي. :)

بدلاً من ذلك ، سيكون try سكرًا نحويًا لـ:

t1, … tn, te := f()  // t1, … tn, te are local (invisible) temporaries
if te != nil {
        err = te   // assign te to the error result parameter
        goto error // goto "error" label
}
x1, … xn = t1, … tn  // assignment only if there was no error

فوائد:

  • defer غير مطلوب لتزيين الأخطاء. (لا تزال عمليات الإرجاع المسماة مطلوبة ، على الرغم من ذلك).
  • وجود التسمية error: هو دليل مرئي على وجود try في مكان ما في الوظيفة.

يوفر هذا أيضًا آلية لإضافة المعالجات التي تتجنب مشاكل المعالج كوظيفة: استخدم التسميات كمعالجات. try(fn(), wrap) سيكون goto wrap بدلاً من goto error . يستطيع المترجم تأكيد وجود wrap: في الوظيفة. لاحظ أن وجود معالجات يساعد أيضًا في تصحيح الأخطاء: يمكنك إضافة / تعديل المعالج لتوفير مسار تصحيح الأخطاء.

عينة من الرموز:

func CopyFile(src, dst string) (err error) {
    r := try(os.Open(src))
    defer r.Close()

    w := try(os.Create(dst))
    defer func() {
        w.Close()
        if err != nil {
            os.Remove(dst) // only if a “try” fails
        }
    }()

    try(io.Copy(w, r), copyfail)
    try(w.Close())
    return nil

error:
    return fmt.Errorf("copy %s %s: %v", src, dst, err)

copyfail:
    recordMetric("copy failure") // count incidents of this failure
    return fmt.Errorf("copy %s %s: %v", src, dst, err)
}

تعليقات أخرى:

  • قد نطلب أن يسبق أي تصنيف يستخدم كهدف لـ try بيان إنهاء. في الممارسة العملية ، هذا سيجبرهم على نهاية الوظيفة ويمكن أن يمنع بعض كود السباغيتي. من ناحية أخرى ، قد يمنع بعض الاستخدامات المعقولة والمفيدة.
  • يمكن استخدام try لإنشاء حلقة. أعتقد أن هذا يندرج تحت شعار "إذا كان هذا مؤلمًا ، فلا تفعله" ، لكنني لست متأكدًا.
  • قد يتطلب ذلك إصلاح https://github.com/golang/go/issues/26058.

الائتمان: أعتقد أن البديل من هذه الفكرة قد اقترحه griesemer شخصيًا في GopherCon العام الماضي.

josharian التفكير في التفاعل مع panic مهم هنا ، ويسعدني أنك طرحته ، لكن مثالك يبدو غريبًا بالنسبة لي. في الكود التالي ، ليس من المنطقي بالنسبة لي أن المؤجل يسجل دائمًا مقياسًا "db call failed" . سيكون مقياسًا خاطئًا إذا نجح someHTTPHandlerGuts وأرجع nil . يتم تشغيل defer في جميع حالات الخروج ، وليس فقط حالات الخطأ أو الذعر ، لذا يبدو الرمز خاطئًا حتى لو لم يكن هناك ذعر.

func someHTTPHandlerGuts() (err error) {
  defer func() {
    recordMetric("db call failed")
    return fmt.HandleErrorf("db unavailable: %v", err)
  }()
  data := try(makeDBCall)
  // some code that panics due to a bug
  return nil
}

josharian نعم ، هذا هو الإصدار الذي ناقشناه العام الماضي بشكل أو بآخر (باستثناء أننا استخدمنا check بدلاً من try ). أعتقد أنه سيكون من الأهمية بمكان ألا يتمكن المرء من القفز "للخلف" إلى بقية الجسم الوظيفي ، بمجرد أن نكون عند التصنيف error . سيضمن ذلك أن goto "منظم" إلى حد ما (لا يوجد رمز معكرونة ممكن). كان أحد المخاوف التي تم طرحها هو أن معالج الخطأ (التسمية error: ) سينتهي به المطاف دائمًا في نهاية الوظيفة (وإلا فسيتعين على المرء القفز حوله بطريقة ما). أنا شخصياً أحب التعامل مع رمز الخطأ بعيدًا (في النهاية) ، لكن آخرين شعروا أنه يجب أن يكون مرئيًا في البداية.

mikeshenkel أرى العودة من الحلقة على أنها علامة زائد وليست سلبية. أعتقد أن ذلك سيشجع المطورين على استخدام وظيفة منفصلة للتعامل مع محتويات حلقة أو استخدام خطأ بشكل صريح كما نفعل حاليًا. كلاهما يبدو نتائج جيدة بالنسبة لي.

من وجهة نظري ، لا أشعر أن بناء الجملة هذا يجب أن يتعامل مع كل حالة استخدام تمامًا مثلما لا أشعر أنني بحاجة إلى استخدام

الخامس ، حسنًا: = م [مفتاح]

شكل من القراءة من الخريطة

يمكنك تجنب فرض تسميات الانتقال إلى نهاية الوظيفة عن طريق إحياء اقتراح handle / check في شكل مبسط. ماذا لو استخدمنا بناء الجملة handle err { ... } لكننا لم نسمح فقط لسلسلة المعالجات ، بدلاً من ذلك يتم استخدام آخر واحد فقط. إنه يبسط هذا الاقتراح كثيرًا ، وهو مشابه جدًا لفكرة الانتقال ، إلا أنه يجعل المناولة أقرب إلى نقطة الاستخدام.

func CopyFile(src, dst string) (err error) {
    handle err {
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }
    r := check os.Open(src)
    defer r.Close()

    w := check os.Create(dst)
    defer func() {
        w.Close()
        if err != nil {
            os.Remove(dst) // only if a “check” fails
        }
    }()

    {
        // handlers are scoped, after this scope the original handle is used again.
        // as an alternative, we could have repeated the first handle after the io.Copy,
        // or come up with a syntax to name the handlers, though that's often not useful.
        handle err {
            recordMetric("copy failure") // count incidents of this failure
            return fmt.Errorf("copy %s %s: %v", src, dst, err)
        }
        check io.Copy(w, r)
    }
    check w.Close()
    return nil
}

على سبيل المكافأة ، هذا له مسار مستقبلي للسماح بسلسلة المعالجات ، حيث سيكون لجميع الاستخدامات الحالية عائد.

joshariangriesemer إذا أدخلت معالجات مسماة (والتي طلبت العديد من الاستجابات للتحقق / التعامل معها ، راجع السمات المتكررة ) ، فهناك خيارات بناء الجملة مفضلة على try(f(), err) :

try.err f()
try?err f()
try#err f()

?err    f() // because 'try' is redundant
?return f() // no handler
?panic  f() // no handler

(?err f()).method()

f?err() // lead with function name, instead of error handling
f?err().method()

file, ?err := os.Open(...) // many check/handle responses also requested this style

من أكثر الأشياء التي أحبها في Go هو أن تركيبها خالي نسبيًا من علامات الترقيم ، ويمكن قراءتها بصوت عالٍ دون مشاكل كبيرة. أنا أكره حقًا أن ينتهي الأمر بـ Go كـ $#@!perl .

بالنسبة لي ، فإن إنشاء وظيفة مضمنة "جرب" وتمكين السلاسل له مشكلتان:

  • إنه غير متسق مع بقية تدفق التحكم أثناء التشغيل (على سبيل المثال ، للكلمات الرئيسية / if / return / etc).
  • يجعل الكود أقل قابلية للقراءة.

أفضل أن أجعلها بيانًا بدون أقواس. قد تتطلب الأمثلة في الاقتراح سطورًا متعددة ولكنها ستصبح أكثر قابلية للقراءة (على سبيل المثال ، سيكون من الصعب تفويت حالات "المحاولة" الفردية). نعم ، سيؤدي ذلك إلى كسر المحلل اللغوي الخارجي ولكني أفضل الحفاظ على التناسق.

العامل الثلاثي هو مكان آخر لا يحتوي على شيء ويتطلب المزيد من ضغطات المفاتيح ولكنه في نفس الوقت يحسن إمكانية القراءة / الصيانة. ستؤدي إضافة "try" في هذا النموذج الأكثر تقييدًا إلى موازنة التعبيرية مقابل المقروئية بشكل أفضل ، IMO.

يؤثر FWIW ، panic على تدفق التحكم وله أقواس ، لكن go و defer يؤثران أيضًا على التدفق ولا يؤثران. أميل إلى الاعتقاد بأن try أكثر شبهاً بـ defer من حيث إنها عملية تدفق غير عادية وتجعل من الصعب القيام بـ try (try os.Open(file)).Read(buf) أمر جيد لأننا نريد تثبيط الخطوط المفردة على أي حال ، ولكن أيا كان. كلاهما بخير.

اقتراح سيكره الجميع بسبب الاسم الضمني لمتغير إرجاع الخطأ النهائي: $err . إنه أفضل من try() IMO. :-)

تضمين التغريدة

_ "شخصيًا ، أحب معالجة رمز الخطأ بعيدًا عن الطريق (في النهاية)" _

+1 لذلك!

أجد أن معالجة الأخطاء التي تم تنفيذها _ قبل_ حدوث الخطأ أصعب بكثير من التفكير فيها من معالجة الأخطاء المطبقة _ بعد_ حدوث الخطأ. إن الاضطرار إلى القفز عقليًا والقوة لمتابعة التدفق المنطقي يبدو وكأنني عدت في عام 1980 إلى كتابة Basic with GOTOs.

دعني أقترح طريقة أخرى محتملة لمعالجة الأخطاء باستخدام CopyFile() كمثال مرة أخرى:

func CopyFile(src, dst string) (err error) {

    r := os.Open(src)
    defer r.Close()

    w := os.Create(dst)
    defer w.Close()

    io.Copy(w, r)
    w.Close()

    for err := error {
        switch err.Source() {
        case w.Close:
            os.Remove(dst) // only if a “try” fails
            fallthrough
        case os.Open, os.Create, io.Copy:
            err = fmt.Errorf("copy %s %s: %v", src, dst, err)
        default:
            err = fmt.Errorf("an unexpected error occurred")
        }
    }

    return err
}

التغييرات اللغوية المطلوبة ستكون:

  1. السماح بتركيب for error{} مشابه لـ for range{} لكن تم إدخاله فقط عند حدوث خطأ ولم يتم تنفيذه إلا مرة واحدة.

  2. اسمح بحذف التقاط قيم الإرجاع التي تنفذ <object>.Error() string ولكن فقط عندما يوجد بناء for error{} داخل نفس func .

  3. تسبب في تدفق التحكم في البرنامج للانتقال إلى السطر الأول من المكون for error{} عندما يُرجع func _ "خطأ" _ في قيمة الإرجاع الأخيرة.

  4. عند إرجاع خطأ _ "خطأ" _ يضيف Go تعيين مرجع إلى func الذي أعاد الخطأ الذي يجب استرجاعه بواسطة <error>.Source()

ما هو _ "خطأ" _؟

حاليًا _ "خطأ" _ يتم تعريفه على أنه أي كائن يقوم بتنفيذ Error() string وبالطبع ليس nil .

ومع ذلك ، غالبًا ما تكون هناك حاجة لتوسيع الخطأ _ حتى عند النجاح_ للسماح بإرجاع القيم المطلوبة لنتائج نجاح واجهة برمجة تطبيقات RESTful. لذا أود أن أطلب من فريق Go ألا يفترض تلقائيًا أن err!=nil يعني _ "خطأ" _ ولكن بدلاً من ذلك تحقق مما إذا كان كائن خطأ ينفذ IsError() وإذا كان IsError() يعيد true قبل افتراض أن أي قيمة بخلاف nil هي _ "خطأ" _

_ (أنا لا أتحدث بالضرورة عن الكود في المكتبة القياسية ولكن بشكل أساسي إذا اخترت تدفق التحكم الخاص بك إلى الفرع على _ "خطأ" _. إذا نظرت فقط إلى err!=nil ، فسنكون محدودًا جدًا فيما نحن يمكن القيام به من حيث قيم الإرجاع في وظائفنا.) _

راجع للشغل ، السماح للجميع باختبار _ "خطأ" _ بالطريقة نفسها على الأرجح يمكن تنفيذها بسهولة عن طريق إضافة وظيفة جديدة مضمنة iserror() :

type ErrorIser interface {
    IsError() bool
}
func iserror(err error) bool {
    if err == nil { 
        return false
    }
    if _,ok := err.(ErrorIser); !ok {
        return true
    }
    return err.IsError()
}

فوائد جانبية للسماح بعدم التقاط _ "الأخطاء" _

لاحظ أن السماح بعدم التقاط آخر _ "خطأ" _ من مكالمات func سيسمح بإعادة البناء في وقت لاحق لإرجاع أخطاء من func s التي لم تكن بحاجة في البداية إلى إرجاع الأخطاء. وسيسمح بإعادة البناء هذه دون كسر أي كود موجود يستخدم هذا النوع من استعادة الأخطاء والمكالمات تقول func s.

بالنسبة لي ، فإن قرار _ "هل يجب أن أعيد خطأ أم أتخلى عن معالجة الخطأ للاتصال بالبساطة؟" _ هو أحد أكبر المآزق التي أواجهها عند كتابة كود Go. إن السماح بعدم التقاط _ "الأخطاء" _ أعلاه من شأنه أن يقضي على هذا المأزق.

لقد حاولت بالفعل تنفيذ هذه الفكرة بصفتي مترجم Go منذ حوالي نصف عام. ليس لدي رأي قوي حول ما إذا كان يجب إضافة هذه الميزة كـ Go المدمج ، ولكن دعني أشارك التجربة (على الرغم من أنني لست متأكدًا من أنها مفيدة).

https://github.com/rhysd/trygo

اتصلت بلغة TryGo الموسعة وطبقت مترجم TryGo to Go.

مع المترجم الشفرة

func CreateFileInSubdir(subdir, filename string, content []byte) error {
    cwd := try(os.Getwd())

    try(os.Mkdir(filepath.Join(cwd, subdir)))

    p := filepath.Join(cwd, subdir, filename)
    f := try(os.Create(p))
    defer f.Close()

    try(f.Write(content))

    fmt.Println("Created:", p)
    return nil
}

يمكن ترجمتها إلى

func CreateFileInSubdir(subdir, filename string, content []byte) error {
    cwd, _err0 := os.Getwd()
    if _err0 != nil {
        return _err0
    }

    if _err1 := os.Mkdir(filepath.Join(cwd, subdir)); _err1 != nil {
        return _err1
    }

    p := filepath.Join(cwd, subdir, filename)
    f, _err2 := os.Create(p)
    if _err2 != nil {
        return _err2
    }
    defer f.Close()

    if _, _err3 := f.Write(content); _err3 != nil {
        return _err3
    }

    fmt.Println("Created:", p)
    return nil
}

لتقييد اللغة ، لم أتمكن من تنفيذ try() call العام. يقتصر على

  • RHS لبيان التعريف
  • RHS بيان التخصيص
  • بيان المكالمة

لكن يمكنني تجربة ذلك من خلال مشروعي الصغير. كانت تجربتي

  • إنه يعمل بشكل جيد ويحفظ عدة أسطر
  • قيمة الإرجاع المسماة غير قابلة للاستخدام بالفعل لـ err حيث يتم تحديد قيمة إرجاع وظيفتها بواسطة كل من التعيين والدالة الخاصة try() . محير جدا
  • تفتقر هذه الوظيفة try() إلى ميزة "خطأ الالتفاف" كما تمت مناقشته أعلاه.

_ "كلاهما يبدو وكأنهما نتائج جيدة بالنسبة لي." _

سيتعين علينا أن نتفق على الاختلاف هنا.

_ "بناء الجملة هذا (ليس من الضروري) التعامل مع كل حالة استخدام" _

ربما يكون هذا الميم هو الأكثر إثارة للقلق. على الأقل بالنظر إلى مدى مقاومة فريق / مجتمع Go لأي تغييرات في الماضي لم تكن قابلة للتطبيق على نطاق واسع.

إذا سمحنا بهذا التبرير هنا ، فلماذا لا يمكننا إعادة النظر في المقترحات السابقة التي تم رفضها لأنها لم تكن قابلة للتطبيق على نطاق واسع؟

وهل نحن الآن منفتحون للدفاع عن التغييرات في Go التي تفيد فقط في حالات الحافة المحددة؟

في تخميني ، لن يؤدي وضع هذه السابقة إلى نتائج جيدة على المدى الطويل ...

_ "@ mikeshenkel" _

ملاحظة: لم أر رسالتك في البداية بسبب خطأ إملائي. _ (هذا لا يزعجني ، لا يتم إخطاري عند وجود أخطاء إملائية في اسم المستخدم الخاص بي ...) _

أقدر الالتزام بالتوافق مع الإصدارات السابقة الذي يحفزك على جعل try مدمجًا ، بدلاً من كلمة رئيسية ، ولكن بعد المصارعة مع الغرابة المطلقة لامتلاك وظيفة مستخدمة بشكل متكرر يمكنها تغيير تدفق التحكم ( panic و recover نادران للغاية) ، تساءلت: هل أجرى أي شخص أي تحليل واسع النطاق لتكرار try كمعرف في قواعد البرمجة مفتوحة المصدر؟ كنت فضوليًا ومتشككًا ، لذلك أجريت بحثًا أوليًا عبر ما يلي:

عبر 11،108،770 سطرًا مهمًا من Go live في هذه المستودعات ، كان هناك 63 مثيلًا فقط من try يتم استخدامه كمعرف. بالطبع ، أدرك أن قواعد الرموز هذه (على الرغم من كونها كبيرة ، ومستخدمة على نطاق واسع ، ومهمة في حد ذاتها) لا تمثل سوى جزء بسيط من كود Go الموجود هناك ، بالإضافة إلى أنه ليس لدينا طريقة لتحليل قواعد الرموز الخاصة بشكل مباشر ، لكنها بالتأكيد نتيجة مثيرة للاهتمام.

علاوة على ذلك ، نظرًا لأن try ، مثل أي كلمة رئيسية ، هو أحرف صغيرة ، فلن تجده أبدًا في واجهة برمجة التطبيقات العامة للحزمة. ستؤثر إضافات الكلمات الرئيسية على العناصر الداخلية للحزمة فقط.

هذا كله مقدمة لبعض الأفكار التي أردت طرحها في المزيج الذي سيستفيد من try ككلمة رئيسية.

سأقترح الإنشاءات التالية.

1) لا معالج

// The existing proposal, but as a keyword rather than builtin.  When an error is 
// "caught", the function returns all zero values plus the error.  Nothing 
// particularly new here.
func doSomething() (int, error) {
    try SomeFunc()
    a, b := try AnotherFunc()

    // ...

    return 123, nil
}

2) معالج

لاحظ أن معالجات الأخطاء عبارة عن كتل تعليمات برمجية بسيطة ، يُقصد بها أن تكون مضمنة ، وليست وظائف. المزيد عن هذا أدناه.

func doSomething() (int, error) {
    // Inline error handler
    a, b := try SomeFunc() else err {
        return 0, errors.Wrap(err, "error in doSomething:")
    }

    // Named error handlers
    handler logAndContinue err {
        log.Errorf("non-critical error: %v", err)
    }
    handler annotateAndReturn err {
        return 0, errors.Wrap(err, "error in doSomething:")
    }

    c, d := try SomeFunc() else logAndContinue
    e, f := try OtherFunc() else annotateAndReturn

    // ...

    return 123, nil
}

القيود المقترحة:

  • يمكنك فقط try استدعاء دالة. لا try err .
  • إذا لم تحدد معالجًا ، فيمكنك فقط try من داخل دالة تقوم بإرجاع خطأ كقيمة إرجاع في أقصى اليمين. لا يوجد تغيير في كيفية تصرف try بناءً على سياقه. إنه لا يصيب بالذعر أبدًا (كما نوقش سابقًا في الموضوع).
  • لا توجد "سلسلة معالج" من أي نوع. المعالجات هي مجرد كتل تعليمات برمجية غير قابلة للإدخال.

فوائد:

  • يمكن فصل بناء الجملة try / else بشكل بسيط في "المركب" الحالي إذا:
    go a, b := try SomeFunc() else err { return 0, errors.Wrap(err, "error in doSomething:") }
    يصبح
    go if a, b, err := SomeFunc(); err != nil { return 0, errors.Wrap(err, "error in doSomething:") }
    بالنسبة لعيني ، بدت ifs المركبة دائمًا مربكة أكثر من كونها مفيدة لسبب بسيط للغاية: تحدث الحالات الشرطية عمومًا بعد العملية الجراحية ، ولها علاقة بمعالجة نتائجها. إذا كانت العملية محصورة داخل العبارة الشرطية ، فمن غير الواضح أنها تحدث. العين مشتتة. علاوة على ذلك ، فإن نطاق المتغيرات المحددة ليس واضحًا على الفور كما هو الحال عندما يتم تركها على الخط.
  • لا يتم تعريف معالجات الأخطاء عن قصد على أنها وظائف (ولا مع أي شيء يشبه الدلالات الشبيهة بالوظيفة). هذا يفعل العديد من الأشياء بالنسبة لنا:

    • يمكن للمترجم ببساطة تضمين معالج مسمى أينما تمت الإشارة إليه. إنه يشبه إلى حد كبير قالب ماكرو / ترميز بسيط من استدعاء دالة. لا يحتاج وقت التشغيل حتى إلى معرفة وجود المعالجات.

    • نحن لسنا مقيدين فيما يتعلق بما يمكننا القيام به داخل المعالج. نتفادى النقد الموجه إلى check / handle بأن "إطار معالجة الخطأ هذا مفيد فقط لعمليات الإنقاذ". نتغلب أيضًا على انتقادات "سلسلة المعالج". يمكن وضع أي رمز تعسفي داخل أحد هذه المعالجات ، ولا يتم تضمين أي تدفق تحكم آخر.

    • لا يتعين علينا اختطاف return داخل المعالج لتعني super return . اختطاف كلمة رئيسية أمر محير للغاية. return يعني فقط return ، وليس هناك حاجة حقيقية لـ super return .

    • لا يحتاج defer إلى ضوء القمر كآلية لمعالجة الأخطاء. يمكننا الاستمرار في التفكير في الأمر بشكل أساسي باعتباره وسيلة لتنظيف الموارد ، وما إلى ذلك.

  • فيما يتعلق بإضافة السياق إلى الأخطاء:

    • تعد إضافة السياق باستخدام المعالجات أمرًا بسيطًا للغاية ويبدو مشابهًا جدًا لمجموعات if err != nil الموجودة

    • على الرغم من أن بنية "المحاولة بدون المعالج" لا تشجع بشكل مباشر على إضافة سياق ، إلا أنه من السهل جدًا إعادة البناء في نموذج المعالج. سيكون الاستخدام المقصود في المقام الأول أثناء التطوير ، وسيكون من السهل جدًا كتابة شيك go vet لتسليط الضوء على الأخطاء التي لم تتم معالجتها.

أعتذر إذا كانت هذه الأفكار متشابهة جدًا مع المقترحات الأخرى - لقد حاولت مواكبة كل منهم ، لكن ربما فاتني صفقة جيدة.

brynbellomy شكرًا على تحليل الكلمات الرئيسية - هذه معلومات مفيدة جدًا. يبدو أن try ككلمة رئيسية قد تكون جيدة. (أنت تقول إن واجهات برمجة التطبيقات (API) لم تتأثر - هذا صحيح ، ولكن قد يستمر ظهور try كاسم معلمة أو ما شابه - لذلك قد يتعين تغيير الوثائق. لكنني أوافق على أن ذلك لن يؤثر على عملاء هذه الحزم.)

فيما يتعلق باقتراحك: سيكون الأمر جيدًا حتى بدون معالجات محددة ، أليس كذلك؟ (هذا من شأنه أن يبسط الاقتراح دون فقدان القوة. يمكن للمرء ببساطة استدعاء دالة محلية من المعالج المضمن.)

فيما يتعلق باقتراحك: سيكون الأمر جيدًا حتى بدون معالجات محددة ، أليس كذلك؟ (هذا من شأنه أن يبسط الاقتراح دون فقدان القوة. يمكن للمرء ببساطة استدعاء دالة محلية من المعالج المضمن.)

griesemer في الواقع - كنت أشعر بالفتور الشديد بشأن تضمين هؤلاء. بالتأكيد المزيد من Go-ish بدون.

من ناحية أخرى ، يبدو أن الناس يريدون القدرة على معالجة الأخطاء أحادية الخطوط ، بما في ذلك الخطوط المفردة التي return . ستكون الحالة النموذجية هي السجل ، ثم return . إذا استخدمنا دالة محلية في عبارة else ، فربما نفقد ما يلي:

a, b := try SomeFunc() else err {
    someLocalFunc(err)
    return 0, err
}

(ما زلت أفضل هذا على ifs المركب ، رغم ذلك)

ومع ذلك ، لا يزال بإمكانك الحصول على مرتجعات ذات سطر واحد تضيف سياق خطأ من خلال تنفيذ قرص بسيط gofmt تمت مناقشته مسبقًا في السلسلة:

a, b := try SomeFunc() else err { return 0, errors.Wrap(err, "bad stuff!") }

هل الكلمة الرئيسية الجديدة ضرورية في الاقتراح أعلاه؟ لما لا:

SomeFunc() else return
a, b := SomeOtherFunc() else err { return 0, errors.Wrap(err, "bad stuff!") }

griesemer إذا عادت المعالجات إلى الطاولة ، أقترح عليك إصدار عدد جديد لمناقشة try / handle أو try / _label_. حذف هذا الاقتراح المعالجات على وجه التحديد ، وهناك طرق لا حصر لها لتعريفهم واستدعاءهم.

يجب على أي شخص يقترح معالجات قراءة ويكي التغذية المرتدة الخاصة بالتحقق / التعامل أولاً. من الجيد أن كل ما تحلم به موصوف بالفعل هناك :-)
https://github.com/golang/go/wiki/Go2ErrorHandlingFeedback

smonkewitz لا ، كلمة رئيسية جديدة ليست ضرورية في هذا الإصدار لأنها مرتبطة ببيانات التخصيص ، والتي تم ذكرها عدة مرات حتى الآن في العديد من بناء الجملة sugar.

https://github.com/golang/go/issues/32437#issuecomment -499808741
https://github.com/golang/go/issues/32437#issuecomment -499852124
https://github.com/golang/go/issues/32437#issuecomment -500095505

ianlancetaylor هل هذه النكهة الخاصة للتعامل مع الأخطاء قد نظر فيها فريق go حتى الآن؟ ليس من السهل تنفيذ المحاولة المقترحة ولكنها تبدو أكثر اصطلاحية. ~ بيان لا داعي له ، آسف. ~

أود أن أكرر شيئًا ما قالهdeanveloper وقليل من الآخرين ، ولكن بتركيزي الخاص. في https://github.com/golang/go/issues/32437#issuecomment -498939499deanveloper قال:

try هو إرجاع مشروط. يتم تعليق كل من تدفق التحكم والعودة على قواعد في Go. يتم وضع مسافة بادئة لكل تدفق التحكم داخل دالة ، وتبدأ جميع المرتجعات بـ return . إن المزج بين هذين المفهومين معًا في استدعاء دالة سهل الفقدان يبدو قليلاً.

علاوة على ذلك ، في هذا الاقتراح ، try دالة تقوم بإرجاع قيم ، لذلك يمكن استخدامها كجزء من تعبير أكبر.

جادل البعض بأن panic قد حدد بالفعل سابقة لوظيفة مضمنة تغير تدفق التحكم ، لكنني أعتقد أن panic يختلف اختلافًا جوهريًا لسببين:

  1. الذعر ليس مشروطا. دائما يجهض وظيفة الاستدعاء.
  2. لا يُرجع الذعر أي قيم وبالتالي يمكن أن يظهر فقط كإفادة قائمة بذاتها ، مما يزيد من وضوحها.

جرب من ناحية أخرى:

  1. مشروط قد يعود أو لا يعود من وظيفة الاستدعاء.
  2. تُرجع القيم ويمكن أن تظهر في تعبير مركب ، ربما عدة مرات ، على سطر واحد ، وربما يتجاوز الهامش الأيمن لنافذة المحرر.

لهذه الأسباب أعتقد أن try يبدو أكثر من مجرد "قليل من الراحة" ، أعتقد أنه يضر بشكل أساسي بقراءة الكود.

اليوم ، عندما نواجه بعض أكواد Go للمرة الأولى ، يمكننا تخطيها سريعًا للعثور على نقاط الخروج المحتملة ونقاط التدفق للتحكم. أعتقد أن هذه خاصية ذات قيمة عالية لـ Go Code. باستخدام try يصبح من السهل جدًا كتابة كود لا يحتوي على هذه الخاصية.

أعترف أنه من المحتمل أن تتقارب قابلية قراءة كود القيمة من مطوري Go مع مصطلحات الاستخدام مقابل try لتجنب مخاطر قابلية القراءة هذه. آمل أن يحدث هذا لأن قابلية قراءة الكود تبدو قيمة أساسية للعديد من مطوري Go. لكن ليس من الواضح بالنسبة لي أن try يضيف قيمة كافية على مصطلحات الكود الحالية لتحمل ثقل إضافة مفهوم جديد إلى اللغة ليتعلمه الجميع ويمكن أن يضر ذلك بسهولة بالقراءة.

""
إذا! = "معطلة" {
dontFix (ذلك)
}

ChrisHines بالنسبة إلى وجهة نظرك (والتي ترد صدى في مكان آخر في هذا الموضوع) ، دعنا نضيف قيدًا آخر:

  • أي عبارة try (حتى تلك التي لا تحتوي على معالج) يجب أن تحدث في سطر خاص بها.

ستظل تستفيد من انخفاض كبير في الضوضاء المرئية. بعد ذلك ، تكون قد ضمنت المرتجعات التي تم التعليق عليها بواسطة return والعوائد المشروطة التي تم التعليق عليها بواسطة try ، وتقف هذه الكلمات الرئيسية دائمًا في بداية السطر (أو في أسوأ الأحوال ، مباشرة بعد تعيين متغير).

لذلك ، لا شيء من هذا النوع من الهراء:

try EmitEvent(try (try DecodeMsg(m)).SaveToDB())

بل بالأحرى هذا:

dm := try DecodeMsg(m)
um := try dm.SaveToDB()
try EmitEvent(um)

الذي لا يزال يبدو أوضح من هذا:

dm, err := DecodeMsg(m)
if err != nil {
    return nil, err
}

um, err := dm.SaveToDB()
if err != nil {
    return nil, err
}

err = EmitEvent(um)
if err != nil {
    return nil, err
}

أحد الأشياء التي أحبه في هذا التصميم هو أنه من المستحيل تجاهل الأخطاء بصمت دون الاستمرار في التعليق على احتمال حدوثها . بينما ترى الآن أحيانًا x, _ := SomeFunc() (ما هي قيمة الإرجاع التي تم تجاهلها؟ خطأ؟ شيء آخر؟) ، الآن عليك أن تشرح بوضوح:

x := try SomeFunc() else err {}

منذ رسالتي السابقة لدعم الاقتراح ، رأيت فكرتين تم نشرهما بواسطة jagv (بدون معلمات try تُرجع *error ) وبواسطة josharian (معالجات الأخطاء المسمى) والتي أؤمن بها شكل معدّل بشكل طفيف من شأنه أن يعزز الاقتراح إلى حد كبير.

من خلال وضع هذه الأفكار جنبًا إلى جنب مع فكرة أخرى لدي ، سيكون لدينا أربعة إصدارات من try :

  1. يحاول()
  2. محاولة (بارامز)
  3. محاولة (بارامس ، تسمية)
  4. محاولة (بارامز ، ذعر)

1 سيعيد ببساطة مؤشرًا إلى معامل إرجاع الخطأ (ERP) أو لا شيء إذا لم يكن هناك واحد (# 4 فقط). هذا من شأنه أن يوفر بديلاً لتخطيط موارد المؤسسات اسمه دون الحاجة إلى إضافة المزيد من الدعم.

2 سيعمل تمامًا كما هو متصور حاليًا. سيتم إرجاع الخطأ غير الصفري على الفور ولكن يمكن تزيينه بعبارة defer .

3 سيعمل على النحو الذي اقترحه josharian ، على سبيل المثال ، في حالة الخطأ غير الصفري ، سوف يتفرع الكود إلى الملصق. ومع ذلك ، لن يكون هناك تسمية افتراضية لمعالج الأخطاء لأن هذه الحالة ستتدهور الآن إلى رقم 2.

يبدو لي أن هذا سيكون عادةً طريقة أفضل لتزيين الأخطاء (أو التعامل معها محليًا ثم إعادة لا شيء) من defer لأنه أبسط وأسرع. أي شخص لم يعجبه لا يزال بإمكانه استخدام # 2.

سيكون من الأفضل وضع تسمية / رمز معالجة الأخطاء بالقرب من نهاية الوظيفة وعدم القفز مرة أخرى إلى باقي جسم الوظيفة. ومع ذلك ، لا أعتقد أن المترجم يجب أن يفرض أيًا منهما لأنه قد تكون هناك مناسبات غريبة حيث تكون مفيدة وقد يكون التنفيذ صعبًا في أي حال.

لذا فإن التسمية العادية والسلوك goto سيطبقان الموضوع (كما قال josharian ) على # 26058 الذي يتم إصلاحه أولاً ولكن أعتقد أنه يجب إصلاحه على أي حال.

لا يمكن أن يكون اسم التصنيف panic لأن هذا قد يتعارض مع رقم 4.

4 سيكون panic على الفور بدلاً من العودة أو التفريع. وبالتالي ، إذا كان هذا هو الإصدار الوحيد من try المستخدم في وظيفة معينة ، فلن تكون هناك حاجة لتخطيط موارد المؤسسات.

لقد أضفت هذا حتى يمكن أن تعمل حزمة الاختبار كما هي الآن دون الحاجة إلى مزيد من التغييرات المضمنة أو غيرها من التغييرات. ومع ذلك ، قد يكون مفيدًا في السيناريوهات "المميتة" الأخرى أيضًا.

يجب أن يكون هذا إصدارًا منفصلاً من try كبديل للتفرع إلى معالج الأخطاء ، ثم الفزع من ذلك لا يزال يتطلب تخطيط موارد المؤسسات (ERP).

كان أحد أقوى أنواع ردود الفعل على الاقتراح الأولي هو القلق بشأن فقدان الرؤية السهلة للتدفق الطبيعي لمكان عودة الوظيفة.

على سبيل المثال ، أعرب deanveloper عن هذا القلق جيدًا في https://github.com/golang/go/issues/32437#issuecomment -498932961 ، والذي أعتقد أنه أعلى تعليق تم التصويت عليه هنا.

dominikh كتب في https://github.com/golang/go/issues/32437#issuecomment -499067357:

في التعليمات البرمجية gofmt'ed ، يطابق العائد دائمًا / ^ \ t * return / - إنه نمط تافه جدًا يمكن ملاحظته بالعين ، دون أي مساعدة. المحاولة ، من ناحية أخرى ، يمكن أن تحدث في أي مكان في الكود ، متداخلة بشكل تعسفي بعمق في استدعاءات الوظائف. لن يجعلنا أي قدر من التدريب قادرين على تحديد كل تدفق التحكم على الفور في وظيفة بدون مساعدة الأداة.

للمساعدة في ذلك ، اقترح brynbellomy بالأمس:

يجب أن تحدث أي عبارة try (حتى تلك التي لا تحتوي على معالج) في سطر خاص بها.

مع أخذ ذلك أبعد من ذلك ، قد يكون مطلوبًا أن يكون try بداية السطر ، حتى بالنسبة للمهمة.

لذلك يمكن أن يكون:

try dm := DecodeMsg(m)
try um := dm.SaveToDB()
try EmitEvent(um)

بدلاً من التالي (من مثالbrynbellomy ):

dm, err := DecodeMsg(m)
if err != nil {
    return nil, err
}

um, err := dm.SaveToDB()
if err != nil {
    return nil, err
}

err = EmitEvent(um)
if err != nil {
    return nil, err
}

يبدو أن هذا سيحافظ على قدر معقول من الرؤية ، حتى بدون أي محرر أو مساعدة IDE ، مع تقليل النموذج المعياري.

يمكن أن يعمل ذلك مع النهج القائم على التأجيل المقترح حاليًا والذي يعتمد على معلمات النتائج المسماة ، أو يمكن أن يعمل مع تحديد وظائف المعالج العادية. (يبدو أن تحديد وظائف المعالج دون طلب قيم إرجاع مسماة أفضل من طلب قيم إرجاع مسماة ، لكن هذه نقطة منفصلة).

يتضمن الاقتراح هذا المثال:

info := try(try(os.Open(file)).Stat())    // proposed try built-in

يمكن أن يكون ذلك بدلاً من ذلك:

try f := os.Open(file)
try info := f.Stat()

لا يزال هذا يمثل انخفاضًا في الصيغة المعيارية مقارنة بما قد يكتبه شخص ما اليوم ، حتى لو لم يكن قصيرًا تمامًا مثل الصيغة المقترحة. ربما سيكون ذلك قصيرًا بدرجة كافية؟

قدم @ elagergren-spideroak هذا المثال:

try(try(try(to()).parse().this)).easily())

أعتقد أن هذا يحتوي على أقواس غير متطابقة ، والتي ربما تكون نقطة مقصودة أو نوعًا من الفكاهة ، لذلك لست متأكدًا مما إذا كان هذا المثال ينوي الحصول على 2 try أو 3 try . على أي حال ، ربما يكون من الأفضل طلب نشر ذلك عبر 2-3 أسطر تبدأ بـ try .

thepudds ، هذا ما كنت أفهمه في تعليقي السابق. ماعدا ذلك المعطى

try f := os.Open(file)
try info := f.Stat()

الشيء الواضح الذي يجب فعله هو التفكير في try على أنه كتلة تجريبية حيث يمكن وضع أكثر من جملة واحدة بين قوسين. لذلك يمكن أن يصبح ما سبق

try (
    f := os.Open(file)
    into := f.Stat()
)

إذا كان المترجم يعرف كيفية التعامل مع هذا ، فإن نفس الشيء يعمل مع التداخل أيضًا. حتى الآن يمكن أن يصبح ما سبق

try info := os.Open(file).Stat()

من تواقيع الوظيفة ، يعرف المترجم أن Open يمكنه إرجاع قيمة خطأ وكما هو الحال في كتلة try ، فإنه يحتاج إلى إنشاء معالجة للأخطاء ثم استدعاء Stat () على القيمة الأولية المرتجعة وما إلى ذلك.

الشيء التالي هو السماح للعبارات التي لا يتم فيها إنشاء قيمة خطأ أو التعامل معها محليًا. لذلك يمكنك القول الآن

try (
    f := os.Open(file)
    debug("f: %v\n", f) // debug returns nothing
    into := f.Stat()
)

هذا يسمح بتطوير التعليمات البرمجية دون الحاجة إلى إعادة ترتيب كتل المحاولة. ولكن لسبب غريب يبدو أن الناس يعتقدون أن معالجة الأخطاء يجب أن يتم توضيحها صراحة! هم يريدون

try(try(try(to()).parse()).this)).easily())

بينما أنا بخير تمامًا مع

try to().parse().this().easily()

على الرغم من أنه في كلتا الحالتين يمكن إنشاء نفس رمز التحقق من الأخطاء بالضبط. وجهة نظري هي أنه يمكنك دائمًا كتابة رمز خاص لمعالجة الأخطاء إذا كنت بحاجة إلى ذلك. يقوم try (أو أيًا كان ما تفضل تسميته) ببساطة بإلغاء معالجة الأخطاء الافتراضية (وهو دفعها للمتصل).

فائدة أخرى هي أنه إذا قام المترجم بإنشاء معالجة الأخطاء الافتراضية ، فيمكنه إضافة المزيد من معلومات التعريف حتى تعرف أي من الوظائف الأربعة المذكورة أعلاه فشلت.

كنت قلقًا إلى حد ما بشأن قابلية قراءة البرامج حيث يظهر try داخل تعبيرات أخرى. لذلك قمت بتشغيل grep "return .*err$" على المكتبة القياسية وبدأت في قراءة الكتل بشكل عشوائي. هناك 7214 نتيجة ، قرأت بضع مئات فقط.

أول شيء يجب ملاحظته هو أنه عند تطبيق try ، فإنه يجعل كل هذه الكتل تقريبًا أكثر قابلية للقراءة.

الأمر الثاني هو أن عددًا قليلاً جدًا من هؤلاء ، أقل من 1 من كل 10 ، سيضع try داخل تعبير آخر. الحالة النموذجية هي كشوف الحساب بالصيغة x := try(...) أو ^try(...)$ .

فيما يلي بعض الأمثلة حيث يظهر try داخل تعبير آخر:

نص / نموذج

func gt(arg1, arg2 reflect.Value) (bool, error) {
        // > is the inverse of <=.
        lessOrEqual, err := le(arg1, arg2)
        if err != nil {
                return false, err
        }
        return !lessOrEqual, nil
}

يصبح:

func gt(arg1, arg2 reflect.Value) (bool, error) {
        // > is the inverse of <=.
        return !try(le(arg1, arg2)), nil
}

نص / نموذج

func index(item reflect.Value, indices ...reflect.Value) (reflect.Value, error) {
...
        switch v.Kind() {
        case reflect.Map:
                index, err := prepareArg(index, v.Type().Key())
                if err != nil {
                        return reflect.Value{}, err
                }
                if x := v.MapIndex(index); x.IsValid() {
                        v = x
                } else {
                        v = reflect.Zero(v.Type().Elem())
                }
        ...
        }
}

يصبح

func index(item reflect.Value, indices ...reflect.Value) (reflect.Value, error) {
        ...
        switch v.Kind() {
        case reflect.Map:
                if x := v.MapIndex(try(prepareArg(index, v.Type().Key()))); x.IsValid() {
                        v = x
                } else {
                        v = reflect.Zero(v.Type().Elem())
                }
        ...
        }
}

(هذا هو المثال الأكثر إثارة للتساؤل الذي رأيته)

regexp / بناء الجملة:

regexp/syntax/parse.go

func Parse(s string, flags Flags) (*Regexp, error) {
        ...
        if c, t, err = nextRune(t); err != nil {
                return nil, err
        }
        p.literal(c)
        ...
}

يصبح

func Parse(s string, flags Flags) (*Regexp, error) {
        ...
        c, t = try(nextRune(t))
        p.literal(c)
        ...
}

هذا ليس مثالاً على المحاولة داخل تعبير آخر ولكني أريد تسميته لأنه يحسن إمكانية القراءة. من الأسهل كثيرًا أن ترى هنا أن قيم c و t تعيش خارج نطاق عبارة if.

صافي / http

net / http / request.go: readRequest

        mimeHeader, err := tp.ReadMIMEHeader()
        if err != nil {
                return nil, err
        }
        req.Header = Header(mimeHeader)

يصبح:

        req.Header = Header(try(tp.ReadMIMEHeader())

قاعدة البيانات / SQL

        if driverCtx, ok := driveri.(driver.DriverContext); ok {
                connector, err := driverCtx.OpenConnector(dataSourceName)
                if err != nil {
                        return nil, err
                }
                return OpenDB(connector), nil
        }

يصبح

        if driverCtx, ok := driveri.(driver.DriverContext); ok {
                return OpenDB(try(driverCtx.OpenConnector(dataSourceName))), nil
        }

قاعدة البيانات / SQL

        si, err := ctxDriverPrepare(ctx, dc.ci, query)
        if err != nil {
                return nil, err
        }
        ds := &driverStmt{Locker: dc, si: si}

يصبح

        ds := &driverStmt{
                Locker: dc,
                si: try(ctxDriverPrepare(ctx, dc.ci, query)),
        }

صافي / http

        if http2isconnectioncloserequest(req) && dialonmiss {
                // it gets its own connection.
                http2tracegetconn(req, addr)
                const singleuse = true
                cc, err := p.t.dialclientconn(addr, singleuse)
                if err != nil {
                        return nil, err
                }
                return cc, nil
        }

يصبح

        if http2isconnectioncloserequest(req) && dialonmiss {
                // it gets its own connection.
                http2tracegetconn(req, addr)
                const singleuse = true
                return try(p.t.dialclientconn(addr, singleuse))
        }

صافي / http

func (f *http2Framer) endWrite() error {
        ...
        n, err := f.w.Write(f.wbuf)
        if err == nil && n != len(f.wbuf) {
                err = io.ErrShortWrite
        }
        return err
}

يصبح

func (f *http2Framer) endWrite() error {
        ...
        if try(f.w.Write(f.wbuf) != len(f.wbuf) {
                return io.ErrShortWrite
        }
        return nil
}

(هذا الذي أحبه حقًا.)

صافي / http

        if f, err := fr.ReadFrame(); err != nil {
                return nil, err
        } else {
                hc = f.(*http2ContinuationFrame) // guaranteed by checkFrameOrder
        }

يصبح

        hc = try(fr.ReadFrame()).(*http2ContinuationFrame)// guaranteed by checkFrameOrder

}

(أيضا لطيفة.)

صافي :

        if ctrlFn != nil {
                c, err := newRawConn(fd)
                if err != nil {
                        return err
                }
                if err := ctrlFn(fd.ctrlNetwork(), laddr.String(), c); err != nil {
                        return err
                }
        }

يصبح

        if ctrlFn != nil {
                try(ctrlFn(fd.ctrlNetwork(), laddr.String(), try(newRawConn(fd))))
        }

ربما يكون هذا كثيرًا ، وبدلاً من ذلك يجب أن يكون:

        if ctrlFn != nil {
                c := try(newRawConn(fd))
                try(ctrlFn(fd.ctrlNetwork(), laddr.String(), c))
        }

بشكل عام ، أستمتع تمامًا بتأثير try على كود المكتبة القياسي الذي قرأته.

نقطة أخيرة: رؤية تطبيق try لقراءة التعليمات البرمجية بخلاف الأمثلة القليلة في الاقتراح كان مفيدًا. أعتقد أنه من المفيد التفكير في كتابة أداة لتحويل الكود تلقائيًا لاستخدام try (حيث لا يغير ذلك من دلالات البرنامج). سيكون من المثير للاهتمام قراءة عينة من الاختلافات التي يتم إنتاجها مقابل الحزم الشائعة على جيثب لمعرفة ما إذا كان ما وجدته في المكتبة القياسية ثابتًا. يمكن لمخرجات مثل هذا البرنامج أن توفر نظرة ثاقبة إضافية حول تأثير الاقتراح.

شكرًا crawshaw على القيام بذلك ، كان من الرائع رؤيته أثناء العمل. لكن رؤيتي له أثناء العمل جعلني آخذ الحجج ضد معالجة الخطأ المضمّن بجدية أكبر والتي كنت أرفضها حتى الآن.

نظرًا لأن هذا كان قريبًا جدًا من اقتراح thepudds المثير للاهتمام بجعل العبارة try ، أعدت كتابة جميع الأمثلة باستخدام بناء الجملة هذا ووجدته أكثر وضوحًا من التعبير- try أو الوضع الراهن ، دون الحاجة إلى عدد كبير جدًا من الأسطر الإضافية:

func gt(arg1, arg2 reflect.Value) (bool, error) {
        // > is the inverse of <=.
        try lessOrEqual := le(arg1, arg2)
        return !lessOrEqual, nil
}
func index(item reflect.Value, indices ...reflect.Value) (reflect.Value, error) {
        ...
        switch v.Kind() {
        case reflect.Map:
                try index := prepareArg(index, v.Type().Key())
                if x := v.MapIndex(index); x.IsValid() {
                        v = x
                } else {
                        v = reflect.Zero(v.Type().Elem())
                }
        ...
        }
}
func Parse(s string, flags Flags) (*Regexp, error) {
        ...
        try c, t = nextRune(t)
        p.literal(c)
        ...
}
        try mimeHeader := tp.ReadMIMEHeader()
        req.Header = Header(mimeHeader)
        if driverCtx, ok := driveri.(driver.DriverContext); ok {
                try connector := driverCtx.OpenConnector(dataSourceName)
                return OpenDB(connector), nil
        }

يمكن القول إن هذا أفضل مع تعبير- try إذا كان هناك عدة حقول يجب أن تكون try -ed ، لكنني ما زلت أفضل رصيد هذه المقايضة

        try si := ctxDriverPrepare(ctx, dc.ci, query)
        ds := &driverStmt{Locker: dc, si: si}

هذا في الأساس هو أسوأ حالة لهذا ويبدو أنه جيد:

        if http2isconnectioncloserequest(req) && dialonmiss {
                // it gets its own connection.
                http2tracegetconn(req, addr)
                const singleuse = true
                try cc := p.t.dialclientconn(addr, singleuse)
                return cc, nil
        }

لقد ناقشت مع نفسي ما إذا كان if try سيكون قانونيًا أو يجب أن يكون قانونيًا ، لكن لم أتمكن من التوصل إلى تفسير معقول لماذا لا يجب أن يكون ، وهو يعمل جيدًا هنا:

func (f *http2Framer) endWrite() error {
        ...
        if try n := f.w.Write(f.wbuf); n != len(f.wbuf) {
                return io.ErrShortWrite
        }
        return nil
}
        try f := fr.ReadFrame()
        hc = f.(*http2ContinuationFrame) // guaranteed by checkFrameOrder
        if ctrlFn != nil {
                try c := newRawConn(fd)
                try ctrlFn(fd.ctrlNetwork(), laddr.String(), c)
        }

إن المسح من خلال أمثلة crawshaw يجعلني أشعر بمزيد من اليقين من أن تدفق التحكم غالبًا ما يكون خفيًا بما يكفي لأكون أكثر حرصًا بشأن التصميم. إن ربط قدر ضئيل من التعقيد يصبح من الصعب قراءته وسهل الفوضى. يسعدني أن أرى الخيارات قيد النظر ، لكن تعقيد تدفق التحكم في مثل هذه اللغة الخاضعة للحراسة يبدو بشكل استثنائي خارج الشخصية.

func (f *http2Framer) endWrite() error {
        ...
        if try(f.w.Write(f.wbuf) != len(f.wbuf) {
                return io.ErrShortWrite
        }
        return nil
}

أيضًا ، try لا "يجرب" أي شيء. إنه "مرحل وقائي". إذا تم إيقاف تشغيل الدلالات الأساسية للاقتراح ، فأنا لست مندهشًا من أن الكود الناتج يمثل مشكلة أيضًا.

func (f *http2Framer) endWrite() error {
        ...
        relay n := f.w.Write(f.wbuf)
        return checkShortWrite(n, len(f.wbuf))
}

إذا قمت بتجربة عبارة ، يمكنك استخدام علامة للإشارة إلى القيمة المرجعة ، والإجراء:

try c, @      := newRawConn(fd) // return
try c, <strong i="6">@panic</strong> := newRawConn(fd) // panic
try c, <strong i="7">@hname</strong> := newRawConn(fd) // invoke named handler
try c, <strong i="8">@_</strong>     := newRawConn(fd) // ignore, or invoke "ignored" handler if defined

ما زلت بحاجة إلى بناء جملة تعبير فرعي (ذكر روس أنه مطلب) ، على الأقل من أجل الذعر وتجاهل الإجراءات.

أولاً ، أحيي crawshaw لأخذ الوقت الكافي للنظر في ما يقرب من 200 مثال حقيقي واستغراق الوقت في كتابته المدروسة أعلاه.

ثانيًا ، jimmyfrasche ، بخصوص إجابتك هنا حول مثال http2Framer :


لقد ناقشت مع نفسي ما إذا كان if try سيكون قانونيًا أو يجب أن يكون قانونيًا ، لكن لم أتمكن من التوصل إلى تفسير معقول لماذا لا يجب أن يكون ، وهو يعمل جيدًا هنا:

""
func (f * http2Framer) خطأ endWrite () {
...
إذا جرب n: = fwWrite (f.wbuf) ؛ n! = len (f.wbuf) {
إرجاع io.ErrShortWrite
}
العودة لا شيء
}

At least under what I was suggesting above in https://github.com/golang/go/issues/32437#issuecomment-500213884, under that proposal variation I would suggest `if try` is not allowed.

That `http2Framer` example could instead be:

func (f * http2Framer) خطأ endWrite () {
...
جرب ن: = fwWrite (f.wbuf)
إذا كان n! = len (f.wbuf) {
إرجاع io.ErrShortWrite
}
العودة لا شيء
}
`` That is one line longer, but hopefully still "light on the page". Personally, I think that (arguably) reads more cleanly, but more importantly it is easier to see the try`.

كتب Deanveloper أعلاه في https://github.com/golang/go/issues/32437#issuecomment -498932961:

يبدو أن العودة من وظيفة كانت شيئًا "مقدسًا" يجب القيام به

انتهى هذا المثال http2Framer المحدد ليس قصيرًا كما يمكن أن يكون. ومع ذلك ، فإنه يحمل العودة من وظيفة أكثر "مقدسة" إذا كان يجب أن يكون try أول شيء في السطر.

crawshaw المذكورة:

الأمر الثاني هو أن عددًا قليلاً جدًا من هؤلاء ، أقل من 1 من كل 10 ، سيضع المحاولة داخل تعبير آخر. الحالة النموذجية هي عبارات بالصيغة x: = try (...) أو ^ try (...) $.

ربما يكون من المقبول مساعدة هؤلاء 1 من 10 أمثلة بشكل أكثر تقييدًا من try جزئيًا ، خاصة إذا كانت الحالة النموذجية من هذه الأمثلة تنتهي بنفس عدد السطور حتى لو كان try مطلوب ليكون أول شيء على الخط؟

تضمين التغريدة

شكرًا crawshaw على القيام بذلك ، كان من الرائع رؤيته أثناء العمل. لكن رؤيتي له أثناء العمل جعلني آخذ الحجج ضد معالجة الخطأ المضمّن بجدية أكبر والتي كنت أرفضها حتى الآن.

نظرًا لأن هذا كان قريبًا جدًا من اقتراح thepudds المثير للاهتمام بجعل العبارة try ، أعدت كتابة جميع الأمثلة باستخدام بناء الجملة هذا ووجدته أكثر وضوحًا من التعبير- try أو الوضع الراهن ، دون الحاجة إلى عدد كبير جدًا من الأسطر الإضافية:

func gt(arg1, arg2 reflect.Value) (bool, error) {
        // > is the inverse of <=.
        try lessOrEqual := le(arg1, arg2)
        return !lessOrEqual, nil
}

يوضح المثال الأول جيدًا سبب تفضيلي الشديد للتعبير- try . في نسختك ، يجب أن أضع نتيجة الاستدعاء على le في متغير ، لكن هذا المتغير ليس له معنى دلالي أن المصطلح le لا يعنيه بالفعل. لذلك لا يوجد اسم يمكنني إعطائه أنه ليس بلا معنى (مثل x ) أو زائداً عن الحاجة (مثل lessOrEqual ). مع التعبير- try ، لا حاجة إلى متغير وسيط ، لذلك لا تظهر هذه المشكلة.

أفضل ألا أبذل مجهودًا عقليًا لابتكار أسماء لأشياء من الأفضل تركها مجهولة.

يسعدني أن أقدم دعمي وراء المنشورات القليلة الماضية حيث تم نقل try (الكلمة الأساسية) إلى بداية السطر. يجب حقًا مشاركة نفس المساحة المرئية مثل return .

رد: اقتراحjimmyfrasche بالسماح بدولار try ضمن بيانات المركب if ، هذا هو بالضبط نوع الأشياء التي أعتقد أن الكثيرين هنا يحاولون تجنبها ، لعدة أسباب:

  • إنه يدمج آليتين مختلفتين تمامًا لتدفق التحكم في سطر واحد
  • يتم تقييم التعبير try أولاً ، ويمكن أن يتسبب في إرجاع الدالة ، ومع ذلك يظهر بعد if
  • يعودون بأخطاء مختلفة تمامًا ، أحدها لا نراه بالفعل في الكود ، والآخر نفعله
  • يجعل الأمر أقل وضوحًا أن try لم يتم التعامل معه بالفعل ، لأن الكتلة تشبه إلى حد كبير كتلة معالج (على الرغم من أنها تتعامل مع مشكلة مختلفة تمامًا)

يمكن للمرء أن يتعامل مع هذا الموقف من زاوية مختلفة قليلاً تفضل دفع الناس للتعامل مع try s. ماذا عن السماح لصيغة try / else بأن تحتوي على شروط لاحقة (وهو نمط شائع مع العديد من وظائف الإدخال / الإخراج التي تُرجع كلاً من err و n ، قد يشير أي منهما إلى وجود مشكلة):

func (f *http2Framer) endWrite() error {
        // ...
        try n := f.w.Write(f.wbuf) else err {
                return errors.Wrap(err, "error writing:")
        } else if n != len(f.wbuf) {
                return io.ErrShortWrite
        }
        return nil
}

في حالة عدم معالجة الخطأ الذي تم إرجاعه بمقدار .Write ، فلا يزال لديك تعليق توضيحي واضح بأن .Write قد يكون خطأ (كما أشارthepudds):

func (f *http2Framer) endWrite() error {
        // ...
        try n := f.w.Write(f.wbuf)
        if n != len(f.wbuf) {
                return io.ErrShortWrite
        }
        return nil
}

أنا ثاني استجابة daved . في رأيي ، أصبح كل مثال تم تمييزهcrawshaw أقل وضوحًا وأكثر عرضة للخطأ كنتيجة لـ try .

يسعدني أن أقدم دعمي وراء المنشورات القليلة الماضية حيث تم نقل try (الكلمة الأساسية) إلى بداية السطر. يجب حقًا مشاركة نفس المساحة المرئية مثل return .

بالنظر إلى الخيارين لهذه النقطة وبافتراض أنه تم اختيار أحدهما ، وبالتالي تم تعيينه إلى حد ما سابقة للميزات المحتملة في المستقبل:

أ.)

try f := os.Open(filepath) else err {
    return errors.Wrap(err, "can't open")
}

ب.)

f := try os.Open(filepath) else err {
    return errors.Wrap(err, "can't open")
}

أي من الاثنين يوفر مزيدًا من المرونة لاستخدام الكلمات الرئيسية الجديدة في المستقبل؟ _ (لا أعرف الإجابة على هذا لأنني لم أتقن الفن المظلم لكتابة المترجمين.) _ هل سيكون أسلوب ما أكثر تقييدًا من الآخر؟

تضمين التغريدة
أنا أميل إلى الاتفاق مع Daves على هذا: في أمثلة crawshaw ، هناك الكثير من عبارات try مضمنة في أعماق سطور بها الكثير من الأشياء الأخرى. حقا من الصعب تحديد نقاط الخروج. علاوة على ذلك ، يبدو أن أقواس try تشوش الأشياء بشكل سيء في بعض الأمثلة.

إن رؤية مجموعة من التعليمات البرمجية stdlib التي تم تحويلها بهذا الشكل مفيدة للغاية ، لذلك أخذت نفس الأمثلة ولكن أعدت كتابتها وفقًا للاقتراح البديل ، وهو أكثر تقييدًا:

  • try ككلمة رئيسية
  • فقط try لكل سطر
  • يجب أن يكون try في بداية السطر

نأمل أن يساعدنا هذا في المقارنة. أنا شخصياً أجد أن هذه الأمثلة تبدو أكثر إيجازًا من أصولها ، ولكن دون حجب تدفق التحكم. يظل try مرئيًا جدًا في أي مكان يتم استخدامه فيه.

نص / نموذج

func gt(arg1, arg2 reflect.Value) (bool, error) {
        // > is the inverse of <=.
        lessOrEqual, err := le(arg1, arg2)
        if err != nil {
                return false, err
        }
        return !lessOrEqual, nil
}

يصبح:

func gt(arg1, arg2 reflect.Value) (bool, error) {
        // > is the inverse of <=.
        try lessOrEqual := le(arg1, arg2)
        return !lessOrEqual, nil
}

نص / نموذج

func index(item reflect.Value, indices ...reflect.Value) (reflect.Value, error) {
...
        switch v.Kind() {
        case reflect.Map:
                index, err := prepareArg(index, v.Type().Key())
                if err != nil {
                        return reflect.Value{}, err
                }
                if x := v.MapIndex(index); x.IsValid() {
                        v = x
                } else {
                        v = reflect.Zero(v.Type().Elem())
                }
        ...
        }
}

يصبح

func index(item reflect.Value, indices ...reflect.Value) (reflect.Value, error) {
        ...
        switch v.Kind() {
        case reflect.Map:
                try index := prepareArg(index, v.Type().Key())
                if x := v.MapIndex(index); x.IsValid() {
                        v = x
                } else {
                        v = reflect.Zero(v.Type().Elem())
                }
        ...
        }
}

regexp / بناء الجملة:

regexp/syntax/parse.go

func Parse(s string, flags Flags) (*Regexp, error) {
        ...
        if c, t, err = nextRune(t); err != nil {
                return nil, err
        }
        p.literal(c)
        ...
}

يصبح

func Parse(s string, flags Flags) (*Regexp, error) {
        ...
        try c, t = nextRune(t)
        p.literal(c)
        ...
}

صافي / http

net / http / request.go: readRequest

        mimeHeader, err := tp.ReadMIMEHeader()
        if err != nil {
                return nil, err
        }
        req.Header = Header(mimeHeader)

يصبح:

        try mimeHeader := tp.ReadMIMEHeader()
        req.Header = Header(mimeHeader)

قاعدة البيانات / SQL

        if driverCtx, ok := driveri.(driver.DriverContext); ok {
                connector, err := driverCtx.OpenConnector(dataSourceName)
                if err != nil {
                        return nil, err
                }
                return OpenDB(connector), nil
        }

يصبح

        if driverCtx, ok := driveri.(driver.DriverContext); ok {
                try connector := driverCtx.OpenConnector(dataSourceName)
                return OpenDB(connector), nil
        }

قاعدة البيانات / SQL

        si, err := ctxDriverPrepare(ctx, dc.ci, query)
        if err != nil {
                return nil, err
        }
        ds := &driverStmt{Locker: dc, si: si}

يصبح

        try si := ctxDriverPrepare(ctx, dc.ci, query)
        ds := &driverStmt{Locker: dc, si: si}

صافي / http

        if http2isconnectioncloserequest(req) && dialonmiss {
                // it gets its own connection.
                http2tracegetconn(req, addr)
                const singleuse = true
                cc, err := p.t.dialclientconn(addr, singleuse)
                if err != nil {
                        return nil, err
                }
                return cc, nil
        }

يصبح

        if http2isconnectioncloserequest(req) && dialonmiss {
                // it gets its own connection.
                http2tracegetconn(req, addr)
                const singleuse = true
                try cc := p.t.dialclientconn(addr, singleuse)
                return cc, nil
        }

صافي / http
هذا لا يحفظنا في الواقع أي سطور ، لكنني أجده أكثر وضوحًا لأن if err == nil بناء غير مألوف نسبيًا.

func (f *http2Framer) endWrite() error {
        ...
        n, err := f.w.Write(f.wbuf)
        if err == nil && n != len(f.wbuf) {
                err = io.ErrShortWrite
        }
        return err
}

يصبح

func (f *http2Framer) endWrite() error {
        ...
        try n := f.w.Write(f.wbuf)
        if n != len(f.wbuf) {
                return io.ErrShortWrite
        }
        return nil
}

صافي / http

        if f, err := fr.ReadFrame(); err != nil {
                return nil, err
        } else {
                hc = f.(*http2ContinuationFrame) // guaranteed by checkFrameOrder
        }

يصبح

        try f := fr.ReadFrame()
        hc = f.(*http2ContinuationFrame) // guaranteed by checkFrameOrder
}

صافي:

        if ctrlFn != nil {
                c, err := newRawConn(fd)
                if err != nil {
                        return err
                }
                if err := ctrlFn(fd.ctrlNetwork(), laddr.String(), c); err != nil {
                        return err
                }
        }

يصبح

        if ctrlFn != nil {
                try c := newRawConn(fd)
                try ctrlFn(fd.ctrlNetwork(), laddr.String(), c)
        }

@ james-lawrence ردًا على https://github.com/golang/go/issues/32437#issuecomment -500116099: لا أتذكر أفكارًا مثل , err الاختياري الذي يتم النظر فيه بجدية ، لا. أنا شخصياً أعتقد أنها فكرة سيئة ، لأنها تعني أنه إذا تغيرت دالة لإضافة معلمة error لاحقة ، ستستمر الشفرة الحالية في التجميع ، ولكنها ستتصرف بشكل مختلف تمامًا.

استخدام التأجيل لمعالجة الأخطاء له معنى كبير ، لكنه يؤدي إلى الحاجة إلى تسمية الخطأ ونوع جديد من المتغيرات المتغيرة if err != nil .

تحتاج المعالجات الخارجية إلى القيام بذلك:

func handler(err *error) {
  if *err != nil {
    *err = handle(*err)
  }
} 

الذي يستخدم مثل

defer handler(&err)

يجب كتابة المعالجات الخارجية مرة واحدة فقط ولكن يجب أن يكون هناك نسختان من العديد من وظائف معالجة الأخطاء: الأولى التي يجب تأجيلها والأخرى التي سيتم استخدامها بالطريقة المعتادة.

تحتاج المعالجات الداخلية إلى القيام بذلك:

defer func() {
  if err != nil {
    err = handle(err)
  }
}()

في كلتا الحالتين ، يجب تسمية خطأ الوظيفة الخارجية ليتم الوصول إليها.

كما ذكرت سابقًا في الموضوع ، يمكن تلخيص ذلك في وظيفة واحدة:

func catch(err *error, handle func(error) error) {
  if *err != nil && handle != nil {
    *err = handle(*err)
  }
}

يتعارض هذا مع مخاوفgriesemer بشأن غموض تحويلات المعالج nil ولها defer و func(err error) error boilerplate ، بالإضافة إلى الحاجة إلى تسمية err في الوظيفة الخارجية.

إذا انتهى الأمر بـ try ككلمة رئيسية ، فمن المنطقي أن يكون لديك كلمة رئيسية catch ، على أن يتم وصفها أدناه أيضًا.

من الناحية التركيبية ، سيكون مثل handle :

catch err {
  return handleThe(err)
}

من الناحية الدلالية ، سيكون سكرًا لكود المعالج الداخلي أعلاه:

defer func() {
  if err != nil {
    err = handleThe(err)
  }
}()

نظرًا لأنه سحري إلى حد ما ، فقد يمسك خطأ الوظيفة الخارجية ، حتى لو لم يتم تسميته. (يشبه err بعد catch اسم معلمة للكتلة catch ).

catch سيكون له نفس القيد مثل try الذي يجب أن يكون في دالة لها إرجاع خطأ نهائي ، لأن كلاهما سكر يعتمد على ذلك.

هذا ليس قريبًا من قوة الاقتراح الأصلي handle ، ولكنه سيتجنب مطلب تسمية خطأ من أجل التعامل معه وسيؤدي إلى إزالة النموذج المعياري الجديد الذي تمت مناقشته أعلاه للمعالجات الداخلية مع تسهيل ذلك بما يكفي لعدم تتطلب إصدارات منفصلة من الوظائف للمعالجات الخارجية.

قد تتطلب معالجة الأخطاء المعقدة عدم استخدام catch كما قد يتطلب عدم استخدام try .

نظرًا لأن كلاهما سكر ، فلا داعي لاستخدام catch مع try . يتم تشغيل معالجات catch عندما تقوم الدالة بإرجاع خطأ غير nil ، مما يسمح ، على سبيل المثال ، بالالتزام ببعض التسجيل السريع:

catch err {
  log.Print(err)
  return err
}

أو قم فقط بلف جميع الأخطاء التي تم إرجاعها:

catch err {
  return fmt.Errorf("foo: %w", err)
}

تضمين التغريدة

_ "أعتقد أنها فكرة سيئة ، لأنها تعني أنه إذا تغيرت دالة لإضافة معلمة error لاحقة ، ستستمر الشفرة الحالية في التجميع ، ولكنها ستتصرف بشكل مختلف تمامًا." _

ربما تكون هذه هي الطريقة الصحيحة للنظر إليها ، إذا كنت قادرًا على التحكم في كل من الكود الأولي والتعليمي ، بحيث إذا كنت بحاجة إلى تغيير توقيع الوظيفة من أجل إرجاع خطأ أيضًا ، فيمكنك القيام بذلك.

لكني أود أن أطلب منكم التفكير فيما يحدث عندما لا يتحكم شخص ما في الحزم الخاصة به في المنبع أو في اتجاه مجرى النهر؟ وأيضًا للنظر في حالات الاستخدام حيث يمكن إضافة الأخطاء ، وماذا يحدث إذا كانت هناك حاجة إلى إضافة أخطاء ولكن لا يمكنك إجبار التعليمات البرمجية النهائية على التغيير؟

هل يمكنك التفكير في مثال يقوم فيه شخص ما بتغيير التوقيع لإضافة قيمة مرتجعة؟ بالنسبة لي ، يقعون عادةً في فئة _ "لم أدرك حدوث خطأ" _ أو _ "أشعر بالكسل ولا أريد بذل الجهد لأن الخطأ لن يحدث على الأرجح". _

في كلتا الحالتين قد أضيف خطأ إرجاع لأنه يصبح من الواضح أنه يجب معالجة خطأ. عندما يحدث ذلك ، إذا لم أتمكن من تغيير التوقيع لأنني لا أريد كسر التوافق مع المطورين الآخرين باستخدام الحزم الخاصة بي ، فماذا أفعل؟ أعتقد أن الغالبية العظمى من الوقت سيحدث الخطأ وأن الكود الذي يسمى func الذي لا يُرجع الخطأ سيعمل بشكل مختلف تمامًا ، _ بأي حال ._

في الواقع ، نادرًا ما أفعل هذا الأخير ، لكنني كثيرًا ما أقوم بالأول. لكنني لاحظت أن حزم الجهات الخارجية تتجاهل بشكل متكرر أخطاء الالتقاط حيث ينبغي أن تكون ، وأنا أعلم ذلك لأنني عندما أقوم بإحضار الكود الخاص بهم في علامات GoLand باللون البرتقالي الفاتح في كل حالة. أود أن أكون قادرًا على إرسال طلبات سحب لإضافة معالجة الأخطاء إلى الحزم التي أستخدمها كثيرًا ، ولكن إذا فعلت ذلك لن أقبلها كثيرًا لأنني سأكسر توقيعات الكود الخاصة بهم.

من خلال عدم تقديم طريقة متوافقة مع الإصدارات السابقة لإضافة أخطاء لإعادتها بواسطة الوظائف ، لن يتمكن المطورون الذين يوزعون التعليمات البرمجية ويهتمون بعدم كسر الأشياء لمستخدميهم من تطوير حزمهم لتشمل معالجة الأخطاء كما ينبغي.


ربما بدلاً من النظر في المشكلة المتمثلة في أن الكود سيعمل بشكل مختلف بدلاً من النظر إلى المشكلة باعتبارها تحديًا هندسيًا فيما يتعلق بكيفية تقليل الجانب السلبي للطريقة التي لا تلتقط خطأ بشكل نشط؟ سيكون لذلك قيمة أوسع وأطول أجلا.

على سبيل المثال ، ضع في اعتبارك إضافة معالج أخطاء الحزمة الذي يجب على المرء تعيينه قبل التمكن من تجاهل الأخطاء؟


لأكون صريحًا ، كان مصطلح Go الخاص بإرجاع الأخطاء بالإضافة إلى قيم الإرجاع العادية أحد أفضل ابتكاراته. ولكن كما يحدث غالبًا عند تحسين الأشياء ، غالبًا ما تكشف عن نقاط ضعف أخرى وسأجادل في أن معالجة أخطاء Go لم تبتكر بشكل كافٍ.

لقد انغمسنا نحن Gophers في إرجاع خطأ بدلاً من طرح استثناء ، لذا فإن السؤال الذي أطرحه هو _ "لماذا لا نعيد الأخطاء من كل وظيفة؟" _ لا نفعل ذلك دائمًا لأن كتابة التعليمات البرمجية بدون معالجة الأخطاء هي أكثر ملاءمة من الترميز معها. لذلك نحذف معالجة الأخطاء عندما نعتقد أنه يمكننا الابتعاد عنها. لكن كثيرا ما نخمن خطأ.

لذلك حقًا ، إذا كان من الممكن معرفة كيفية جعل الكود أنيقًا وقابلًا للقراءة ، فسأجادل بأن القيم والأخطاء المرتجعة يجب حقًا معالجتها بشكل منفصل ، وأن _ كل وظيفة _ يجب أن يكون لديها القدرة على إرجاع الأخطاء بغض النظر عن توقيعات الوظيفة السابقة. وسيكون جعل الكود الحالي يتعامل برشاقة مع الكود الذي يولد أخطاءً الآن مسعىً جديرًا بالاهتمام.

لم أقترح أي شيء لأنني لم أتمكن من تصور بناء جملة عملي ، ولكن إذا أردنا أن نكون صادقين مع أنفسنا ، فليس كل شيء في هذا الموضوع والمتعلق بمعالجة أخطاء Go بشكل عام حول حقيقة أن معالجة الأخطاء و منطق البرنامج عبارة عن رفقاء غريبين ، لذا فمن الأفضل التعامل مع الأخطاء خارج النطاق بطريقة ما؟

تساعد try ككلمة رئيسية بالتأكيد في سهولة القراءة (مقابل استدعاء الوظيفة) وتبدو أقل تعقيدًا. brynbellomycrawshaw شكرًا لك على الوقت الذي قضيته في كتابة الأمثلة.

أفترض أن تفكيري العام هو أن try يفعل الكثير. إنه يحل: وظيفة الاستدعاء ، وتعيين المتغيرات ، والتحقق من الخطأ ، وإرجاع الخطأ إذا كان موجودًا. أقترح بدلاً من ذلك أن نقطع النطاق ونحل فقط من أجل العودة المشروطة: "العودة إذا كان الجدل الأخير لا شيء".

ربما لا تكون هذه فكرة جديدة ... ولكن بعد تخطي المقترحات في ويكي ملاحظات الخطأ ، لم أجدها (لا يعني أنها ليست موجودة)

اقتراح صغير حول العودة المشروطة

مقتطفات:

err, thing := newThing(name)
refuse nil, err

أضفته إلى الويكي أيضًا تحت عنوان "أفكار بديلة"

يبدو أن عدم القيام بأي شيء هو أيضًا خيار معقول للغاية.

alexhornbake الذي يعطيني فكرة مختلفة قليلاً قد تكون أكثر فائدة

assert(nil, err)
assert(len(a), len(b))
assert(true, condition)
assert(expected, given)

وبهذه الطريقة لن يتم تطبيقه على التحقق من الأخطاء فحسب ، بل ينطبق أيضًا على العديد من أنواع الأخطاء المنطقية.

سيتم تغليف المعطى في خطأ وإعادته.

تضمين التغريدة

تمامًا كما try لا يحاول فعلاً ، فإن refuse ليس "رافضًا" في الواقع. كان القصد المشترك هنا هو أننا نضع "مرحلًا وقائيًا" ( relay قصير ودقيق ومعبّر عن return ) أن "الرحلات" عندما تفي إحدى القيم السلكية بشرط (أي ليس خطأ لا شيء). إنه نوع من قاطع الدائرة ، وأعتقد أنه يمكن أن يضيف قيمة إذا كان التصميم مقصورًا على حالات غير مثيرة للاهتمام لتقليل بعض النماذج المعيارية الأقل تعليقًا. يجب أن يعتمد أي شيء معقد عن بُعد على كود Go العادي.

وأثني أيضًا على كرانشو لعمله في البحث في المكتبة القياسية ، لكنني توصلت إلى نتيجة مختلفة تمامًا ... أعتقد أنه يجعل قراءة كل مقتطفات الشفرات هذه تقريبًا أكثر صعوبة وأكثر عرضة لسوء الفهم.

        req.Header = Header(try(tp.ReadMIMEHeader())

سأفتقد كثيرًا أن هذا يمكن أن يخطئ. تحصل القراءة السريعة على "حسنًا ، قم بتعيين الرأس على رأس ReadMimeHeader الخاص بالشيء".

        if driverCtx, ok := driveri.(driver.DriverContext); ok {
                return OpenDB(try(driverCtx.OpenConnector(dataSourceName))), nil
        }

هذه ، عيني فقط تحاول تحليل خط OpenDB هذا. هناك كثافة كبيرة هناك ... يوضح هذا المشكلة الرئيسية التي تواجهها جميع استدعاءات الوظائف المتداخلة ، حيث يتعين عليك القراءة من الداخل إلى الخارج ، وعليك تحليلها في رأسك لمعرفة مكان الجزء الأعمق .

لاحظ أيضًا أن هذا يمكن أن يعود من مكانين مختلفين في نفس السطر .. ستعمل على تصحيح الأخطاء ، وسيشير إلى وجود خطأ تم إرجاعه من هذا السطر ، وأول شيء سيفعله الجميع هو محاولة اكتشف سبب فشل OpenDB مع هذا الخطأ الغريب ، في حين أنه في الواقع فشل OpenConnector (أو العكس).

        ds := &driverStmt{
                Locker: dc,
                si: try(ctxDriverPrepare(ctx, dc.ci, query)),
        }   

هذا هو المكان الذي يمكن أن تفشل فيه الشفرة حيث كان من المستحيل في السابق. بدون try ، لا يمكن أن يفشل البناء الحرفي للهيكل. سوف تتصفح عيناي مثل "حسنًا ، إنشاء برنامج تشغيل Stmt ... المضي قدمًا ..." وسيكون من السهل جدًا تفويت ذلك في الواقع ، فقد يتسبب ذلك في حدوث خطأ في وظيفتك. الطريقة الوحيدة التي كان من الممكن أن تكون ممكنة من قبل هي إذا كان ctxDriver مستعدًا للذعر ... ونعلم جميعًا أن هذه الحالة يجب ألا تحدث أبدًا و 2) إذا حدث ذلك ، فهذا يعني أن شيئًا ما خطأ بشكل كبير.

يؤدي إجراء تجربة كلمة رئيسية وبيان إلى إصلاح الكثير من مشاكلي معها. أعلم أن هذا ليس متوافقًا مع الإصدارات السابقة ، لكنني لا أعتقد أن استخدام إصدار أسوأ منه هو الحل لمشكلة التوافق مع الإصدارات السابقة.

daved لست متأكدا من أتابع. هل تكره الاسم أو تكره الفكرة؟

على أي حال ، لقد نشرت هذا هنا كبديل ... إذا كانت هناك مصلحة مشروعة ، يمكنني فتح عدد جديد للمناقشة ، لا أريد تلويث هذا الموضوع (ربما بعد فوات الأوان؟) لنا احساس .. طبعا انفتح على اسماء بديلة. جزء مهم هو "العودة المشروطة دون محاولة التعامل مع التخصيص".

على الرغم من أنني أحب اقتراح الصيد المقدم من jimmyfrasche ، إلا أنني أود اقتراح بديل:
go handler fmt.HandleErrorf("copy %s %s", src, dst)
سيكون معادلاً لـ:
go defer func(){ if(err != nil){ fmt.HandleErrorf(&err,"copy %s %s", src, dst) } }()
حيث Err هو آخر قيمة إرجاع مسماة ، مع نوع الخطأ. ومع ذلك ، يمكن أيضًا استخدام المعالجات عندما لا يتم تسمية قيم الإرجاع. سيتم السماح بالحالة الأكثر عمومية أيضًا:
go handler func(err *error){ *err = fmt.Errorf("foo: %w", *err) }() `
المشكلة الرئيسية التي أواجهها مع استخدام قيم الإرجاع المسماة (التي لا يحلها الصيد) هي أن الخطأ غير ضروري. عند تأجيل مكالمة إلى معالج مثل fmt.HandleErrorf ، لا توجد وسيطة أولية معقولة باستثناء مؤشر إلى قيمة إرجاع الخطأ ، فلماذا إعطاء المستخدم خيار ارتكاب خطأ؟

بالمقارنة مع المصيد ، فإن الاختلاف الرئيسي هو أن المعالج يجعل من الأسهل قليلاً استدعاء معالجات محددة مسبقًا على حساب جعل المزيد من الإسهاب لتعريفها في مكانها. لست متأكدًا من أن هذا مثالي ، لكنني أعتقد أنه يتماشى أكثر مع الاقتراح الأصلي.

yiyus catch ، كما عرّفته لا يتطلب تسمية err على الوظيفة التي تحتوي على catch .

في catch err { ، فإن err هو ما يسمى الخطأ داخل الكتلة catch . إنه يشبه اسم معلمة الوظيفة.

مع ذلك ، ليست هناك حاجة لشيء مثل fmt.HandleErrorf لأنه يمكنك فقط استخدام fmt.Errorf العادي:

func f() error {
  catch err {
    return fmt.Errorf("foo: %w", err)
  }
  return errors.New("bar")
}

الذي يُرجع خطأ يُطبع كـ foo: bar .

لا أحب هذا النهج للأسباب التالية:

  • try() يقوم استدعاء الوظيفة بمقاطعة تنفيذ التعليمات البرمجية في الوظيفة الرئيسية.
  • لا توجد كلمة رئيسية return ، لكن الرمز يعود بالفعل.

تم اقتراح العديد من الطرق لعمل المعالجات ، لكنني أعتقد أنهم غالبًا ما يفوتون شرطين رئيسيين:

  1. يجب أن يكون مختلفًا بشكل كبير وأفضل من if x, err := thingie(); err != nil { handle(err) } . أعتقد أن الاقتراحات على غرار try x := thingie else err { handle(err) } لا تتوافق مع هذا الشريط. لماذا لا نقول فقط if ؟

  2. يجب أن تكون متعامدة مع الوظيفة الحالية defer . أي أنه يجب أن يكون مختلفًا بدرجة كافية بحيث يكون من الواضح أن آلية المناولة المقترحة مطلوبة في حد ذاتها دون إنشاء حالات زاوية غريبة عند التعامل مع التفاعل وتأجيله.

يرجى وضع هذه الرغبات في الاعتبار أثناء مناقشة الآليات البديلة لـ try / handle.

carlmjohnson تعجبني فكرةjimmyfrasche catch بخصوص نقطتك 2 - إنها مجرد بناء جملة sugar لـ defer الذي يحفظ سطرين ويتيح لك تجنب الاضطرار إلى تسمية قيمة إرجاع الخطأ (والتي في بدوره سيتطلب منك أيضًا تسمية كل الآخرين إذا لم تكن قد فعلت ذلك بالفعل). لا تثير مشكلة التعامد مع defer ، لأنها defer .

مرددًا ما قاله ubombi :

try () وظيفة استدعاء المقاطعات تنفيذ التعليمات البرمجية في الدالة الأصل .؛ لا توجد كلمة رئيسية للإرجاع ، لكن الرمز يعود بالفعل.

في Ruby ، ​​تعتبر procs و lambdas مثالاً لما يفعله try ... proc عبارة عن كتلة من التعليمات البرمجية لا تُرجعها عبارة الإرجاع الخاصة بها من الكتلة نفسها ، ولكن من المتصل.

هذا بالضبط ما يفعله try ... إنه مجرد عملية Ruby محددة مسبقًا.

أعتقد أننا إذا كنا سنذهب إلى هذا الطريق ، فربما يمكننا في الواقع السماح للمستخدم بتحديد وظيفة try الخاصة به من خلال تقديم proc functions

ما زلت أفضل if err != nil ، لأنه أكثر قابلية للقراءة ولكن أعتقد أن try سيكون أكثر فائدة إذا حدد المستخدم عملية الشراء الخاصة به:

proc try(err *error, msg string) {
  if *err != nil {
    *err = fmt.Errorf("%v: %w", msg, *err)
    return
  }
}

وبعد ذلك يمكنك تسميتها:

func someFunc() (string, error) {
  err := doSomething()
  try(&err, "someFunc failed")
}

الفائدة هنا ، هي أنك تحصل على تعريف معالجة الأخطاء بشروطك الخاصة. ويمكنك أيضًا جعل proc مكشوفًا أو خاصًا أو داخليًا.

إنها أيضًا أفضل من عبارة handle {} في اقتراح Go2 الأصلي لأنه يمكنك تحديد هذا مرة واحدة فقط لقاعدة الكود بأكملها وليس في كل دالة.

أحد الاعتبارات المتعلقة بقابلية القراءة ، هو أنه قد يتم استدعاء func () و proc () بشكل مختلف مثل func() و proc!() حتى يعرف المبرمج أن استدعاء proc قد يعود فعلاً من وظيفة الاتصال.

@ marwan-at-work ، ألا يجب أن يكون try(err, "someFunc failed") try(&err, "someFunc failed") في مثالك؟

dpinela شكرا على التصحيح ، تحديث الكود :)

الممارسة الشائعة التي نحاول تجاوزها هنا هي ما يقترحه فك المكدس القياسي عبر العديد من اللغات في استثناء ، (ومن هنا تم اختيار كلمة "try" ...).
ولكن إذا كان بإمكاننا السماح فقط بوظيفة (... جرب () أو غير ذلك) من شأنها أن تقفز إلى الوراء مستويين في التتبع ، إذن

try := handler(err error) {     //which corelates with - try := func(err error) 
   if err !=nil{
       //do what ever you want to do when there's an error... log/print etc
       return2   //2 levels
   }
} 

ثم رمز مثل
f: = try (os.Open (filename))
يمكن أن يفعل ما ينصح به الاقتراح تمامًا ، ولكن كدالة (أو في الواقع "وظيفة معالج") ، سيكون لدى المطور قدر أكبر من التحكم في ما تفعله الوظيفة ، وكيفية تنسيق الخطأ في حالات مختلفة ، واستخدام معالج مشابه في كل مكان الكود المطلوب معالجته (دعنا نقول) os.Open ، بدلاً من wrting fmt.Errorf ("خطأ في فتح الملف٪ s ....") في كل مرة.
سيؤدي هذا أيضًا إلى إجبار معالجة الأخطاء كما لو أن "try" لن يتم تعريفها - إنها خطأ في وقت التجميع.

guybrand إن الحصول على مثل هذا العائد على مستويين return2 (أو "عائد غير محلي" كما يطلق على المفهوم العام في Smalltalk) سيكون آلية جيدة للأغراض العامة (اقترحها أيضًا mikeschinkel في # 32473) . ولكن يبدو أن try لا يزال مطلوبًا في اقتراحك ، لذلك لا أرى سببًا لـ return2 - يمكن لـ try تنفيذ return . سيكون الأمر أكثر إثارة للاهتمام إذا كان بإمكان المرء أيضًا كتابة try محليًا ، لكن هذا غير ممكن للتوقيعات العشوائية.

تضمين التغريدة

_ "لذلك لا أرى سببًا لـ return2 - يمكن لـ try تنفيذ return ." _

أحد الأسباب - كما أشرت في # 32473 _ (شكرًا على المرجع) _ - هو السماح بمستويات متعددة من break و continue ، بالإضافة إلى return .

شكرا مرة أخرى للجميع على كل التعليقات الجديدة. إنه استثمار كبير للوقت لمواكبة المناقشة وكتابة تعليقات مكثفة. والأفضل من ذلك ، على الرغم من الحجج العاطفية في بعض الأحيان ، فقد كان هذا خيطًا مدنيًا إلى حد ما حتى الآن. شكرا!

إليك ملخص سريع آخر ، هذه المرة أكثر تكثيفًا ؛ أعتذر لمن لم أذكرهم أو نسيتهم أو أسيء تمثيلهم. في هذه المرحلة ، أعتقد أن بعض الموضوعات الأكبر آخذة في الظهور:

1) بشكل عام ، يعتبر استخدام وظيفة مضمنة للوظيفة try اختيارًا سيئًا: نظرًا لأنه يؤثر على تدفق التحكم ، يجب أن يكون _ على الأقل _ كلمة رئيسية (يفضل carloslenz "جعلها عبارة بدون أقواس")؛ try كتعبير لا يبدو فكرة جيدة ، فهو يضر بالقراءة ( ChrisHines ،jimmyfrasche) ، فهي "ترجع بدون return ". أجرىbrynbellomy تحليلًا فعليًا لـ try مستخدمًا كمعرفات ؛ يبدو أن هناك عددًا قليلاً جدًا من النسبة المئوية ، لذلك قد يكون من الممكن استخدام مسار الكلمات الرئيسية بدون التأثير على الكثير من التعليمات البرمجية.

2) استغرقت crawshaw بعض الوقت لتحليل بضع مئات من حالات الاستخدام من مكتبة الأمراض المنقولة جنسياً وتوصلت إلى استنتاج مفاده أن try كما هو مقترح يحسن دائمًا قابلية القراءة. jimmyfrasche وصلوا إلى النتيجة المعاكسة .

3) موضوع آخر هو أن استخدام defer لتزيين الأخطاء ليس مثاليًا. يشير josharian إلى أن defer تعمل دائمًا عند عودة الوظيفة ، ولكن إذا كانوا هنا لتزيين الخطأ ، فنحن نهتم فقط بجسمهم إذا كان هناك خطأ ، والذي قد يكون مصدر ارتباك.

4) كتب العديد اقتراحات لتحسين الاقتراح. zeebo ، @ patrick-nyt يدعمان gofmt تنسيق عبارات بسيطة if في سطر واحد (وكن سعيدًا بالوضع الراهن). اقترح jargv أن try() (بدون وسيطات) قد يُعيد مؤشرًا إلى الخطأ "المعلق" حاليًا ، والذي من شأنه أن يزيل الحاجة إلى تسمية نتيجة الخطأ فقط حتى يتمكن المرء من الوصول إليها في defer ؛ اقترح masterada استخدام errorfunc() بدلاً من ذلك. أعاد velovix فكرة وجود وسيطتين try حيث تكون الوسيطة الثانية عبارة عن معالج أخطاء.

klaidliadon ، @ networkimprov يفضلون "مشغلي المهام" الخاصين مثل f, # := os.Open() بدلاً من try . قدم networkimprov اقتراحًا بديلاً أكثر شمولاً للتحقيق في مثل هذه الأساليب (راجع العدد رقم 32500). قدم mikeschinkel أيضًا اقتراحًا بديلاً يقترح تقديم ميزتين جديدتين للغة للأغراض العامة يمكن استخدامهما لمعالجة الأخطاء أيضًا ، بدلاً من الخطأ المحدد try (راجع المشكلة رقم 32473). أعاد josharian إحياء احتمال ناقشناه في GopherCon العام الماضي حيث لا يعود try عند حدوث خطأ ولكن بدلاً من ذلك يقفز (مع goto ) إلى ملصق يسمى error (بدلاً من ذلك) ، try قد يأخذ اسم التصنيف الهدف).

5) حول موضوع try ككلمة رئيسية ، ظهر سطرين من الأفكار. اقترح brynbellomy إصدارًا قد يحدد المعالج بدلاً من ذلك:

a, b := try f()
a, b := try f() else err { /* handle error */ }

يخطوthepudds خطوة إلى الأمام ويقترح try في بداية السطر ، مما يمنح try نفس الرؤية مثل return :

try a, b := f()

يمكن أن يعمل كلاهما defer .

تضمين التغريدة

شكرًا لك على المرجع إلى mikeschinkel # 32473 ، هناك الكثير من القواسم المشتركة.

بخصوص

ولكن يبدو أن المحاولة لا تزال مطلوبة في اقتراحك
على الرغم من أنه يمكن تنفيذ اقتراحي باستخدام معالج "أي" وليس "بناء" محجوز / كلمة رئيسية / تعبير "لا أعتقد أن" محاولة () "فكرة سيئة (وبالتالي لم تصوت ضدها) ، فأنا أحاول "لتوسيعه" - لذلك سيظهر المزيد من المكاسب ، توقع الكثير "بمجرد تقديم الإصدار 2.0"

أعتقد أن هذا قد يكون أيضًا مصدر "المشاعر المختلطة" التي أبلغت عنها في ملخصك الأخير - إنه ليس "محاولة () لا يحسن معالجة الأخطاء" - متأكد من ذلك ، "انتظار Go 3.0 لحل بعض الأخطاء الرئيسية الأخرى التعامل مع الآلام "المذكورة أعلاه ، تبدو طويلة جدًا :)

أقوم بإجراء دراسة استقصائية حول "آلام التعامل مع الأخطاء" (وأظهر أن بعض الآلام هي مجرد "أنا لا أستخدم الممارسات الجيدة" ، في حين أن البعض لم أتخيل حتى أن الناس (معظمهم من لغات أخرى) يريدون القيام بذلك - بشكل رائع إلى WTF).

آمل أن أتمكن من مشاركة بعض النتائج المثيرة للاهتمام قريبًا.

أخيرًا - شكرًا على العمل الرائع والصبر!

بالنظر ببساطة إلى طول الصيغة الحالية المقترحة مقابل ما هو متاح الآن ، فإن الحالة التي يحتاج فيها الخطأ فقط إلى إرجاعه دون معالجته أو تزيينه هي الحالة التي يتم فيها الحصول على أكبر قدر من الراحة. مثال على النحو المفضل لدي حتى الآن:

try a, b := f() else err { return fmt.Errorf("Decorated: %s", err); }
if a,b, err :=f;  err != nil { return fmt.Errorf("Decorated: %s", err); }
try a, b := f()
if a,b, err :=f;  err != nil { return err; }

لذا ، بخلاف ما اعتقدت من قبل ، ربما يكفي ببساطة تغيير go fmt ، على الأقل بالنسبة لحالة الخطأ المزخرفة / المعالجة. أثناء تخطي حالة الخطأ للتو ، قد يكون شيء مثل المحاولة مرغوبًا فيه مثل السكر النحوي لحالة الاستخدام الشائعة جدًا هذه.

بالنسبة try else ، أعتقد أن وظائف الخطأ الشرطي مثل fmt.HandleErrorf (تحرير: أفترض أنها ترجع صفرًا عندما يكون الإدخال صفرًا) في التعليق الأولي يعمل بشكل جيد ، لذا فإن إضافة else غير ضروري.

a, b, err := doSomething()
try fmt.HandleErrorf(&err, "Decorated "...)

مثل كثيرين هنا ، أفضل أن يكون try عبارة بدلاً من تعبير ، غالبًا لأن التعبير الذي يغير تدفق التحكم يعد غريبًا تمامًا عن Go. أيضًا لأن هذا ليس تعبيرًا ، يجب أن يكون في بداية السطر.

أتفق أيضًا مع daved أن الاسم غير مناسب. بعد كل شيء ، ما نحاول تحقيقه هنا هو مهمة خاضعة للحراسة ، فلماذا لا تستخدم guard مثل Swift وتجعل جملة else اختيارية؟ شيء مثل

GuardStmt = "guard" ( Assignment | ShortVarDecl | Expression ) [  "else" Identifier Block ] .

حيث Identifier هو اسم متغير الخطأ المرتبط بـ Block التالي. مع عدم وجود عبارة else ، فقط قم بالعودة من الوظيفة الحالية (واستخدم معالج التأجيل لتزيين الأخطاء إذا لزم الأمر).

في البداية لم تعجبني عبارة else لأنها مجرد سكر نحوي حول المهمة المعتادة متبوعة بـ if err != nil ، ولكن بعد الاطلاع على بعض الأمثلة ، يكون الأمر منطقيًا: استخدام guard يجعل النية أكثر وضوحًا.

تحرير: اقترح البعض استخدام أشياء مثل catch لتحديد معالجات أخطاء مختلفة بطريقة ما. أجد else قابل للتطبيق بنفس القدر من الناحية اللغوية وهو موجود بالفعل في اللغة.

بينما تعجبني عبارة try-else ، ماذا عن بناء الجملة هذا؟

a, b, (err) := func() else { return err }

التعبير try - else عامل تشغيل ثلاثي.

a, b := try f() else err { ... }
fmt.Println(try g() else err { ... })`

كشف الحساب try - else عبارة عن كشف حساب if .

try a, b := f() else err { ... }
// (modulo scope of err) same as
a, b, err := f()
if err != nil { ... }

يمكن تحقيق إنشاء try باستخدام معالج اختياري باستخدام وظيفة مساعد (أدناه) أو عدم استخدام try (غير مصور ، نعلم جميعًا كيف يبدو ذلك).

a, b := try(f(), decorate)
// same as
a, b := try(g())
// where g is
func g() (whatever, error) {
  x, err := f()
  if err != nil {
    try(decorate(err))
  }
  return x, nil
}

كل ثلاثة يقطعون على النمذجة ويساعدون في احتواء نطاق الأخطاء.

إنه يوفر أكبر قدر من المدخرات للمبنى try لكن هذا به المشكلات المذكورة في مستند التصميم.

بالنسبة لكشف الحساب try - else ، فإنه يوفر ميزة على استخدام if بدلاً من try . لكن الميزة هامشية للغاية لدرجة أنني أجد صعوبة في رؤيتها تبرر نفسها ، على الرغم من أنني أحبها.

يفترض الثلاثة أنه من الشائع الحاجة إلى معالجة خطأ خاص للأخطاء الفردية.

يمكن معالجة جميع الأخطاء بالتساوي في defer . إذا تم إجراء معالجة الخطأ نفسها في كل كتلة else فهذا متكرر بعض الشيء:

func printSum(a, b string) error {
  try x := strconv.Atoi(a) else err {
    return fmt.Errorf("printSum: %w", err)
  }
  try y := strconv.Atoi(b) else err {
    return fmt.Errorf("printSum: %w", err)
  }
  fmt.Println("result:", x + y)
  return nil
}

أنا أعلم بالتأكيد أن هناك أوقاتًا يتطلب فيها خطأ معين معالجة خاصة. تلك هي الحالات التي ما زالت بارزة في ذاكرتي. ولكن ، إذا حدث ذلك فقط ، على سبيل المثال ، مرة واحدة من كل 100 مرة ، ألن يكون من الأفضل الاحتفاظ بـ try بسيطًا وعدم استخدام try في تلك المواقف؟ من ناحية أخرى ، إذا كان الأمر أشبه بـ 1 من 10 مرات ، فإن إضافة else / Handler يبدو أكثر منطقية.

سيكون من المثير للاهتمام أن نرى التوزيع الفعلي لعدد المرات try بدون else / معالج مقابل try مع else / معالج سيكون مفيدًا ، على الرغم من ليس من السهل جمع البيانات.

أريد التوسع في تعليقjimmyfrasche الأخير.

الهدف من هذا الاقتراح هو تقليل الصيغة المعيارية

    a, b, err := f()
    if err != nil {
        return nil, err
    }

هذا الرمز سهل القراءة. الأمر يستحق توسيع اللغة فقط إذا تمكنا من تحقيق انخفاض كبير في الصيغة المعيارية. عندما أرى شيئًا مثل

    try a, b := f() else err { return nil, err }

لا يسعني إلا أن أشعر أننا لا ندخر الكثير. نحن نحتفظ بثلاثة أسطر ، وهو أمر جيد ، لكن حسب إحصائي ، فإننا نخفض من 56 إلى 46 حرفًا. هذا ليس كثيرًا. قارن ب

    a, b := try(f())

التي تقطع من 56 إلى 18 حرفًا ، وهو تقليل أكثر أهمية. وعلى الرغم من أن عبارة try تجعل التغيير المحتمل لتدفق التحكم أكثر وضوحًا ، بشكل عام لا أجد العبارة أكثر قابلية للقراءة. على الرغم من أنه على الجانب الإيجابي ، فإن عبارة try تجعل من السهل شرح الخطأ.

على أي حال ، فإن وجهة نظري هي: إذا أردنا تغيير شيء ما ، فيجب أن يقلل بشكل كبير من الصيغة المعيارية أو يجب أن يكون أكثر قابلية للقراءة بشكل ملحوظ. هذا الأخير صعب جدًا ، لذا فإن أي تغيير يحتاج إلى العمل حقًا على الأول. إذا حصلنا فقط على انخفاض طفيف في النموذج المعياري ، فهذا في رأيي لا يستحق القيام به.

مثل الآخرين ، أود أن أشكر crawshaw على الأمثلة.

عند قراءة هذه الأمثلة ، أشجع الناس على محاولة تبني عقلية لا تقلق فيها بشأن تدفق التحكم بسبب الوظيفة try . أعتقد ، ربما بشكل غير صحيح ، أن تدفق التحكم هذا سيصبح سريعًا طبيعة ثانية للأشخاص الذين يعرفون اللغة. في الحالة العادية ، أعتقد أن الناس سيتوقفون ببساطة عن القلق بشأن ما يحدث في حالة الخطأ. جرب قراءة هذه الأمثلة أثناء التزجيج فوق try تمامًا كما لو كنت تزجج بالفعل فوق if err != nil { return err } .

بعد قراءة كل شيء هنا ، وبعد مزيد من التفكير ، لست متأكدًا من أنني أرى المحاولة حتى كبيان يستحق الإضافة.

  1. يبدو أن الأساس المنطقي لذلك هو تقليل معالجة رمز لوحة الغلاية للخطأ. IMHO هو "يبطل" الكود ولكنه لا يزيل التعقيد ؛ إنه يحجبها فقط. هذا لا يبدو قويا بما يكفي لسبب. الذهاب"تم التقاط بناء الجملة بشكل جميل عند بدء سلسلة رسائل متزامنة. لا أفهم هذا النوع من الشعور" آها! "هنا. لا أشعر بالرضا. نسبة التكلفة / الفائدة ليست كبيرة بما يكفي.

  2. اسمها لا يعكس وظيفتها. في أبسط أشكالها ، ما تفعله هو: "إذا قامت دالة بإرجاع خطأ ، فارجع من المتصل مع وجود خطأ" ولكن هذا طويل جدًا :-) على الأقل يلزم اسم مختلف.

  3. مع الإرجاع الضمني للخطأ في try ، يبدو الأمر وكأن Go هو نوع من الدعم على مضض في معالجة الاستثناءات. هذا إذا كانت مكالمات A في وضع try guard واستدعى B C في حارس try ، واستدعى C D في حارس try ، إذا أرجع D خطأً في الواقع ، فقد تسببت في انتقال غير محلي. إنه شعور "سحري" للغاية.

  4. ومع ذلك أعتقد أنه قد يكون من الممكن إيجاد طريقة أفضل. سيؤدي اختيار المحاولة الآن إلى إغلاق هذا الخيار.

تضمين التغريدة
إذا فهمت اقتراح "try else" بشكل صحيح ، فيبدو أن الكتلة else اختيارية ، ومخصصة للمعالجة التي يوفرها المستخدم. في المثال الخاص بك ، try a, b := f() else err { return nil, err } ، عبارة else زائدة عن الحاجة بالفعل ، ويمكن كتابة التعبير بالكامل ببساطة كـ try a, b := f()

أتفق مع ianlancetaylor ،
تعد القابلية للقراءة والنمط المعياري من الاهتمامات الرئيسية وربما الدافع إلى
معالجة أخطاء go 2.0 (على الرغم من أنه يمكنني إضافة بعض المخاوف المهمة الأخرى)

أيضا ، أن التيار

a, b, err := f()
if err != nil {
    return nil, err
}

مقروء للغاية.
ومنذ أن أؤمن

if a, b, err := f(); err != nil {
    return nil, err
}

يكاد يكون مقروءًا ، ولكن به "مشكلات" تتعلق بالنطاق ، ربما أ

ifErr a, b, err := f() {
    return nil, err
}

هذا من شأنه فقط ؛ يخطئ! = لا شيء ، ولن يقوم بإنشاء نطاق ، أو

بصورة مماثلة

جرب أ ، ب ، يخطئ: = f () {
العودة لا شيء ، يخطئ
}

يحتفظ بالسطرين الإضافيين ، لكن لا يزال من الممكن قراءتهما.

في الثلاثاء ، 11 حزيران (يونيو) 2019 ، الساعة 20:19 مساءً ، ديمتري ماترينيتشيف ، [email protected]
كتب:

ianlancetaylor https://github.com/ianlancetaylor
إذا فهمت اقتراح "حاول غير ذلك" بشكل صحيح ، فيبدو أن حظر آخر
اختياري ، ومحجوز للمعالجة المقدمة من المستخدم. في مثالك
جرب أ ، ب: = f () وإلا يخطئ {إرجاع لا شيء ، يخطئ} فإن جملة else هي في الواقع
زائدة عن الحاجة ، ويمكن كتابة التعبير بالكامل ببساطة بتجربة a ، b: =
F()

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/golang/go/issues/32437؟email_source=notifications&email_token=ABNEY4XPURMASWKZKOBPBVDPZ7NALA5CNFSM4HTGCZ72YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63S4DFVREXG43VMVBW63S4DFVREXG43VMVBW63HM
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/ABNEY4SAFK4M5NLABF3NZO3PZ7NALANCNFSM4HTGCZ7Q
.

تضمين التغريدة

على أي حال ، فإن وجهة نظري هي: إذا أردنا تغيير شيء ما ، فيجب أن يقلل بشكل كبير من الصيغة المعيارية أو يجب أن يكون أكثر قابلية للقراءة بشكل ملحوظ. هذا الأخير صعب جدًا ، لذا فإن أي تغيير يحتاج إلى العمل حقًا على الأول. إذا حصلنا فقط على انخفاض طفيف في النموذج المعياري ، فهذا في رأيي لا يستحق القيام به.

متفق عليه ، وبالنظر إلى أن else سيكون سكرًا نحويًا فقط (مع تركيب غريب!) ، من المحتمل جدًا استخدامه نادرًا ، لا أهتم كثيرًا به. ما زلت أفضل أن يكون try بيانًا بالرغم من ذلك.

ianlancetaylor EchoingDmitriyMV ، ستكون كتلة else اختيارية. دعني أطرح مثالاً يوضح كليهما (ولا يبدو بعيدًا جدًا عن العلامة من حيث النسبة النسبية للكتل التي تم التعامل معها try القطع غير المعالجة في الكود الحقيقي):

func createMergeCommit(r *repo.Repo, index *git.Index, remote *git.Remote, remoteBranch *git.Branch) error {
    headRef, err := r.Head()
    if err != nil {
        return err
    }

    parentObjOne, err := headRef.Peel(git.ObjectCommit)
    if err != nil {
        return err
    }

    parentObjTwo, err := remoteBranch.Reference.Peel(git.ObjectCommit)
    if err != nil {
        return err
    }

    parentCommitOne, err := parentObjOne.AsCommit()
    if err != nil {
        return err
    }

    parentCommitTwo, err := parentObjTwo.AsCommit()
    if err != nil {
        return err
    }

    treeOid, err := index.WriteTree()
    if err != nil {
        return err
    }

    tree, err := r.LookupTree(treeOid)
    if err != nil {
        return err
    }

    remoteBranchName, err := remoteBranch.Name()
    if err != nil {
        return err
    }

    userName, userEmail, err := r.UserIdentityFromConfig()
    if err != nil {
        userName = ""
        userEmail = ""
    }

    var (
        now       = time.Now()
        author    = &git.Signature{Name: userName, Email: userEmail, When: now}
        committer = &git.Signature{Name: userName, Email: userEmail, When: now}
        message   = fmt.Sprintf(`Merge branch '%v' of %v`, remoteBranchName, remote.Url())
        parents   = []*git.Commit{
            parentCommitOne,
            parentCommitTwo,
        }
    )

    _, err = r.CreateCommit(headRef.Name(), author, committer, message, tree, parents...)
    if err != nil {
        return err
    }
    return nil
}
func createMergeCommit(r *repo.Repo, index *git.Index, remote *git.Remote, remoteBranch *git.Branch) error {
    try headRef := r.Head()
    try parentObjOne := headRef.Peel(git.ObjectCommit)
    try parentObjTwo := remoteBranch.Reference.Peel(git.ObjectCommit)
    try parentCommitOne := parentObjOne.AsCommit()
    try parentCommitTwo := parentObjTwo.AsCommit()
    try treeOid := index.WriteTree()
    try tree := r.LookupTree(treeOid)
    try remoteBranchName := remoteBranch.Name()
    try userName, userEmail := r.UserIdentityFromConfig() else err {
        userName = ""
        userEmail = ""
    }

    var (
        now       = time.Now()
        author    = &git.Signature{Name: userName, Email: userEmail, When: now}
        committer = &git.Signature{Name: userName, Email: userEmail, When: now}
        message   = fmt.Sprintf(`Merge branch '%v' of %v`, remoteBranchName, remote.Url())
        parents   = []*git.Commit{
            parentCommitOne,
            parentCommitTwo,
        }
    )

    try r.CreateCommit(headRef.Name(), author, committer, message, tree, parents...)
    return nil
}

بينما لا يحفظ النمط try / else العديد من الأحرف فوق المركب if ، فإنه يفعل:

  • توحيد بناء جملة معالجة الخطأ باستخدام try غير المعالج
  • أوضح بلمحة أن الكتلة الشرطية تتعامل مع حالة خطأ
  • امنحنا فرصة لتقليل غرابة النطاق التي يعاني منها المركب if s

من المحتمل أن يكون try هو الأكثر شيوعًا.

تضمين التغريدة

جرب قراءة هذه الأمثلة أثناء التزجيج بالمحاولة تمامًا كما فعلت بالفعل إذا أخطأت! = لا شيء {إرجاع خطأ}.

لا أعتقد أن هذا ممكن / متساوٍ. في عداد المفقودين وجود المحاولة في سطر مزدحم ، أو ما يلفه بالضبط ، أو أن هناك حالات متعددة في سطر واحد ... هذه ليست هي نفسها بسهولة / بسرعة تحديد نقطة عودة وعدم القلق بشأن التفاصيل الموجودة فيها.

تضمين التغريدة

عندما أرى علامة توقف ، أتعرف عليها من خلال الشكل واللون أكثر من قراءة الكلمة المطبوعة عليها والتفكير في آثارها الأعمق.

قد تتألق عيناي بأكثر من if err != nil { return err } ولكن في نفس الوقت لا تزال تسجل — بوضوح وعلى الفور.

ما يعجبني في المتغير try -statement هو أنه يقلل من الصيغة المعيارية ولكن بطريقة يسهل التزجيج بها ولكن يصعب تفويتها.

قد يعني ذلك سطرًا إضافيًا هنا أو هناك ، لكن هذا لا يزال أقل من سطور الوضع الراهن.

تضمين التغريدة

  1. كيف تعرض التعامل مع الدوال التي تُرجع قيمًا متعددة ، مثل:
    func createMergeCommit (r * repo.Repo، index * git.Index، remote * git.Remote، remoteBranch * git.Branch) (التجزئة ، الخطأ) {
  2. كيف يمكنك تتبع السطر الصحيح الذي أعاد الخطأ
  3. تجاهل مشكلة النطاق (التي يمكن حلها بطرق أخرى) ، لست متأكدًا
func createMergeCommit(r *repo.Repo, index *git.Index, remote *git.Remote, remoteBranch *git.Branch) error {

    if headRef, err := r.Head(); err != nil {
        return err
    } else if parentObjOne, err := headRef.Peel(git.ObjectCommit); err != nil {
        return err
    } else parentObjTwo, err := remoteBranch.Reference.Peel(git.ObjectCommit); err != nil {
        return err
    } ...

لا تختلف سهولة القراءة من حيث الحكمة ، ومع ذلك (أو fmt.Errorf ("خطأ في الحصول على العنوان:٪ s" ، err.Error ()) يسمح لك بتعديل وإعطاء بيانات إضافية بسهولة.

ما لا يزال تذمر هو

  1. الاضطرار إلى إعادة الفحص ؛ يخطئ! = لا شيء
  2. إعادة الخطأ كما هو إذا كنا لا نريد تقديم معلومات إضافية - وهي في بعض الحالات ليست ممارسة جيدة ، لأنك تعتمد على الوظيفة التي تستدعيها لتعكس خطأ "جيد" من شأنه أن يلمح إلى "الخطأ الذي حدث "، في حالات file.Open ، والإغلاق ، والإزالة ، ووظائف Db وما إلى ذلك ، يمكن للعديد من استدعاءات الوظائف إرجاع نفس الخطأ (يمكننا القول إذا كان هذا يعني أن المطور الذي كتب الخطأ قام بعمل جيد أم لا ... يحدث) - وبعد ذلك - لديك خطأ ، ربما قم بتسجيله من الوظيفة التي استدعت
    "createMergeCommit" ، لكن لا يمكن تتبعه إلى السطر الذي حدث فيه بالضبط.

عذرًا إذا نشر شخص ما بالفعل شيئًا كهذا (هناك الكثير من الأفكار الجيدة: P) ماذا عن هذه البنية البديلة:

fail := func(err error) error {
  log.Print("unexpected error", err)
  return err
}

a, b, err := f1()          // normal
c, d := f2() -> throw      // calls throw(err)
e, f := f3() -> panic      // calls panic(err)
g, h := f4() -> t.Error    // calls t.Error(err)
i, j := f5() -> fail       // calls fail(err)

وهذا يعني أن لديك -> handler على يمين استدعاء دالة يسمى إذا تم إرجاع الخطأ! = لا شيء. المعالج هو أي دالة تقبل خطأ كوسيطة واحدة وتقوم بشكل اختياري بإرجاع خطأ (على سبيل المثال ، func(error) أو func(error) error ). إذا قام المعالج بإرجاع خطأ لا شيء ، فستستمر الوظيفة وإلا يتم إرجاع الخطأ.

لذا فإن a := b() -> handler يعادل:

a, err := b()
if err != nil {
  if herr := handler(err); herr != nil {
    return herr
  }
}

الآن ، كاختصار ، يمكنك دعم try مدمج (أو كلمة رئيسية أو عامل تشغيل ?= أو أيًا كان) وهو اختصار لـ a := b() -> throw لذا يمكنك كتابة شيء مثل:

func() error {
  a, b := try(f1())
  c, d := try(f2())
  e, f := try(f3())
  ...
  return nil
}() -> panic // or throw/fail/whatever

أنا شخصياً أجد عامل تشغيل ?= أسهل في القراءة من تجربة الكلمة الأساسية / المضمنة:

func() error {
  a, b ?= f1()
  c, d ?= f2()
  e, f ?= f3()
  ...
  return nil
}() -> panic

ملحوظة : هنا أستخدم رمية كعنصر نائب للمدمج الذي سيعيد الخطأ إلى المتصل.

لم أعلق على مقترحات معالجة الأخطاء حتى الآن لأنني أؤيدها بشكل عام ، وأحب الطريقة التي يتجهون بها. يبدو أن كل من وظيفة try المحددة في الاقتراح وبيان try الذي اقترحه thepudds سيكونان إضافات معقولة إلى اللغة. أنا واثق من أن كل ما سيأتي به فريق Go سيكون جيدًا.

أريد أن أتطرق إلى ما أراه مشكلة ثانوية في طريقة تعريف المحاولة في الاقتراح وكيف يمكن أن تؤثر على الامتدادات المستقبلية.

تُعرّف المحاولة بأنها دالة تأخذ عددًا متغيرًا من الوسائط.

func try(t1 T1, t2 T2, … tn Tn, te error) (T1, T2, … Tn)

تمرير نتيجة استدعاء دالة إلى try كما في try(f()) يعمل ضمنيًا بسبب الطريقة التي تعمل بها قيم الإرجاع المتعددة في Go.

من خلال قراءتي للاقتراح ، فإن المقتطفات التالية صحيحة ومتكافئة لغويًا.

a, b = try(f())
//
u, v, err := f()
a, b = try(u, v, err)

يثير الاقتراح أيضًا إمكانية تمديد try مع وسيطات إضافية.

إذا قررنا أن وجود شكل من أشكال وظيفة معالج الأخطاء المقدمة صراحة ، أو أي معلمة إضافية أخرى لهذه المسألة ، هي فكرة جيدة ، فمن الممكن بشكل ضئيل تمرير هذه الوسيطة الإضافية إلى استدعاء المحاولة.

افترض أننا نريد إضافة وسيطة معالج. يمكن أن تذهب في بداية قائمة الحجج أو نهايتها.

var h handler
a, b = try(h, f())
// or
a, b = try(f(), h)

لا يعمل وضعه في البداية ، لأنه (بالنظر إلى الدلالات أعلاه) لن يتمكن try من التمييز بين وسيطة المعالج الصريحة والدالة التي ترجع المعالج.

func f() (handler, error) { ... }
func g() (error) { ... }
try(f())
try(h, g())

من المحتمل أن ينجح وضعها في النهاية ، ولكن بعد ذلك ستكون المحاولة فريدة في اللغة باعتبارها الوظيفة الوحيدة مع معلمة varargs في بداية قائمة الوسائط.

ليست أيًا من هاتين المشكلتين بمثابة أداة عرض ، لكنها تجعل try تشعر بعدم الاتساق مع بقية اللغة ، ولذا فأنا لست متأكدًا من أن try سيكون من السهل تمديده في المستقبل مثل تنص على الاقتراح.

magical

يعتبر وجود معالج أمرًا قويًا ، ربما:
لقد أعلنت بالفعل ح ،

يمكنك

var h handler
a, b, h = f()

أو

a, b, h.err = f()

إذا كانت تشبه الوظيفة:

h:= handler(err error){
 log(...)
 return ....
} 

ثم كان هناك نداء ل

a, b, h(err) = f()

يمكن للجميع استدعاء المعالج
ويمكنك أيضًا "تحديد" المعالج الذي يقوم بإرجاع أو التقاط الخطأ فقط (conitnue / break / return) كما اقترح البعض.

وهكذا اختفت قضية فارارجس.

أحد البدائل لاقتراح @ brynbellomy else لـ:

a, b := try f() else err { /* handle error */ }

يمكن أن يكون لدعم وظيفة الزخرفة مباشرة بعد الآخر:

decorate := func(err error) error { return fmt.Errorf("foo failed: %v", err) }

try a, b := f() else decorate
try c, d := g() else decorate

وربما تعمل أيضًا بعض الوظائف المفيدة على غرار ما يلي:

decorate := fmt.DecorateErrorf("foo failed")

يمكن أن يكون لوظيفة الزخرفة التوقيع func(error) error ، ويمكن استدعاؤها عن طريق المحاولة في حالة وجود خطأ ، قبل محاولة العودة من الوظيفة المرتبطة التي يتم تجربتها.

سيكون ذلك مماثلاً في الروح لواحد من "تكرارات التصميم" السابقة من وثيقة الاقتراح:

f := try(os.Open(filename), handler)              // handler will be called in error case

إذا أراد شخص ما شيئًا أكثر تعقيدًا أو مجموعة من العبارات ، فيمكنه بدلاً من ذلك استخدام if (تمامًا كما يمكنهم اليوم).

ومع ذلك ، هناك شيء لطيف حول المحاذاة المرئية لـ try الموضحة في مثال @ brynbellomy في https://github.com/golang/go/issues/32437#issuecomment -500949780.

كل هذا يمكن أن يعمل مع defer إذا تم اختيار هذا الأسلوب لتزيين خطأ موحد (أو حتى من الناحية النظرية يمكن أن يكون هناك شكل بديل لتسجيل وظيفة الزخرفة ، ولكن هذه نقطة منفصلة).

على أي حال ، لست متأكدًا من الأفضل هنا ، لكنني أردت توضيح خيار آخر.

إليك مثالbrynbellomy المعاد كتابته باستخدام الدالة try ، باستخدام كتلة var للاحتفاظ بالمحاذاة اللطيفة التي أشار إليها thepudds في https://github.com/golang/go/issues / 32437 # issuecomment -500998690.

package main

import (
    "fmt"
    "time"
)

func createMergeCommit(r *repo.Repo, index *git.Index, remote *git.Remote, remoteBranch *git.Branch) error {
    var (
        headRef          = try(r.Head())
        parentObjOne     = try(headRef.Peel(git.ObjectCommit))
        parentObjTwo     = try(remoteBranch.Reference.Peel(git.ObjectCommit))
        parentCommitOne  = try(parentObjOne.AsCommit())
        parentCommitTwo  = try(parentObjTwo.AsCommit())
        treeOid          = try(index.WriteTree())
        tree             = try(r.LookupTree(treeOid))
        remoteBranchName = try(remoteBranch.Name())
    )

    userName, userEmail, err := r.UserIdentityFromConfig()
    if err != nil {
        userName = ""
        userEmail = ""
    }

    var (
        now       = time.Now()
        author    = &git.Signature{Name: userName, Email: userEmail, When: now}
        committer = &git.Signature{Name: userName, Email: userEmail, When: now}
        message   = fmt.Sprintf(`Merge branch '%v' of %v`, remoteBranchName, remote.Url())
        parents   = []*git.Commit{
            parentCommitOne,
            parentCommitTwo,
        }
    )

    _, err = r.CreateCommit(headRef.Name(), author, committer, message, tree, parents...)
    return err
}

إنه موجز مثل إصدار بيان try ، وأود أن أجادل بأنه مقروء تمامًا. نظرًا لأن try هو تعبير ، يمكن حذف عدد قليل من تلك المتغيرات الوسيطة ، على حساب بعض المقروئية ، ولكن هذا يبدو وكأنه مسألة أسلوب أكثر من أي شيء آخر.

إنه يثير السؤال حول كيفية عمل try في كتلة var ، على الرغم من ذلك. أفترض أن كل سطر من var يعد بيانًا منفصلاً ، بدلاً من كون الكتلة بأكملها عبارة واحدة ، بقدر ترتيب ما يتم تعيينه ومتى.

سيكون من الجيد إذا دعا اقتراح "المحاولة" صراحةً النتائج المترتبة على أدوات مثل cmd / cover التي تقريبية لإحصائيات تغطية الاختبار باستخدام حساب العبارات الساذج. أخشى أن يؤدي تدفق التحكم في الخطأ غير المرئي إلى تقليل العدد.

تضمين التغريدة

جرب أ ، ب: = f () تزيين آخر

ربما يكون ذلك حرقًا عميقًا جدًا في خلايا دماغي ، لكن هذا يضربني كثيرًا مثل a

try a, b := f() ;catch(decorate)

ومنحدر زلق إلى أ

a, b := f()
catch(decorate)

أعتقد أنه يمكنك معرفة إلى أين يقودنا ذلك ، وبالنسبة لي المقارنة

    try headRef := r.Head()
    try parentObjOne := headRef.Peel(git.ObjectCommit)
    try parentObjTwo := remoteBranch.Reference.Peel(git.ObjectCommit)
    try parentCommitOne := parentObjOne.AsCommit()
    try parentCommitTwo := parentObjTwo.AsCommit()
    try treeOid := index.WriteTree()
    try tree := r.LookupTree(treeOid)
    try remoteBranchName := remoteBranch.Name()

مع

    try ( 
    headRef := r.Head()
    parentObjOne := headRef.Peel(git.ObjectCommit)
    parentObjTwo := remoteBranch.Reference.Peel(git.ObjectCommit)
    parentCommitOne := parentObjOne.AsCommit()
    parentCommitTwo := parentObjTwo.AsCommit()
    treeOid := index.WriteTree()
    tree := r.LookupTree(treeOid)
    remoteBranchName := remoteBranch.Name()
    )

(أو حتى الالتقاط في النهاية)
والثاني أكثر قابلية للقراءة ، لكنه يؤكد حقيقة أن الدوال أدناه تعيد متغيرين ، ونحن نتجاهل أحدهما بطريقة سحرية ، ونجمعه في "خطأ إرجاع سحري".

    try(err) ( 
    headRef := r.Head()
    parentObjOne := headRef.Peel(git.ObjectCommit)
    parentObjTwo := remoteBranch.Reference.Peel(git.ObjectCommit)
    parentCommitOne := parentObjOne.AsCommit()
    parentCommitTwo := parentObjTwo.AsCommit()
    treeOid := index.WriteTree()
    tree := r.LookupTree(treeOid)
    remoteBranchName := remoteBranch.Name()
    ); err!=nil {
      //handle the err
    }

على الأقل يحدد المتغير بشكل صريح للعودة ، واسمحوا لي أن أتعامل معه داخل الوظيفة ، متى أردت.

فقط أقوم بمداخلة تعليق معين لأنني لم أر أي شخص آخر يذكره صراحةً ، وتحديداً حول تغيير gofmt لدعم تنسيق السطر الفردي التالي ، أو أي متغير:

if f() { return nil, err }

من فضلك لا. إذا أردنا سطرًا واحدًا if ، فالرجاء إنشاء سطر واحد if ، على سبيل المثال:

if f() then return nil, err

ولكن من فضلك ، من فضلك ، لا تتبنى سلطة بناء الجملة بإزالة فواصل الأسطر التي تسهل قراءة الكود الذي يستخدم الأقواس.

أود التأكيد على بعض الأشياء التي ربما تم نسيانها في خضم المناقشة:

1) بيت القصيد من هذا الاقتراح هو جعل معالجة الأخطاء الشائعة تتلاشى في الخلفية - يجب ألا يهيمن التعامل مع الأخطاء على الكود. لكن يجب أن تكون صريحة. أي من الاقتراحات البديلة التي تجعل معالجة الخطأ تظهر بشكل أكبر تفتقد إلى الهدف. كما قال ianlancetaylor بالفعل ، إذا لم تقلل هذه الاقتراحات البديلة من مقدار النموذج المعياري بشكل كبير ، فيمكننا فقط البقاء مع بيانات if . (يأتي طلب تقليل النموذج المعياري منك ، مجتمع Go.)

2) إحدى الشكاوى حول الاقتراح الحالي هي الحاجة إلى تسمية نتيجة الخطأ من أجل الوصول إليها. أي اقتراح بديل سيواجه نفس المشكلة ما لم يقدم البديل صياغة إضافية ، أي ، أكثر متداخلة (مثل ... else err { ... } وما شابه) لتسمية هذا المتغير صراحة. ولكن الشيء المثير للاهتمام: إذا كنا لا نهتم بتزيين خطأ ولم نقم بتسمية معلمات النتيجة ، ولكننا ما زلنا نطلب صريحًا return لأن هناك معالجًا واضحًا من نوع ما ، تلك العبارة return سيتعين عليك تعداد جميع قيم النتائج (صفر عادةً) حيث لا يُسمح بالعودة المجردة في هذه الحالة. خاصة إذا كانت الدالة ترجع الكثير من الأخطاء بدون تزيين الخطأ ، فإن هذه العوائد الصريحة ( return nil, err ، إلخ) تضيف إلى النموذج المعياري. الاقتراح الحالي وأي بديل لا يتطلب صراحة return يلغي ذلك. من ناحية أخرى ، إذا كان المرء لا يريد تزيين الخطأ ، فإن الاقتراح الحالي _ يتطلب _ أن يكون اسمًا واحدًا نتيجة الخطأ (ومع ذلك جميع النتائج الأخرى) للوصول إلى قيمة الخطأ. هذا له تأثير جانبي لطيف أنه في المعالج الصريح يمكن للمرء استخدام إرجاع عارية وليس عليه تكرار جميع قيم النتائج الأخرى. (أعلم أن هناك بعض المشاعر القوية حول العوائد المجردة ، ولكن الحقيقة هي أنه عندما يكون كل ما يهمنا هو نتيجة الخطأ ، فمن المزعج الحقيقي أن نضطر إلى تعداد جميع قيم النتائج الأخرى (صفر عادةً) - لا يضيف شيئًا إلى فهم الكود). بعبارة أخرى ، فإن الاضطرار إلى تسمية نتيجة الخطأ بحيث يمكن تزيينها يتيح مزيدًا من تقليل النموذج المعياري.

magical شكرا للإشارة إلى هذا . لقد لاحظت الأمر نفسه بعد فترة وجيزة من نشر الاقتراح (لكنني لم أقم بإحضاره حتى لا يسبب مزيدًا من الارتباك). أنت محق في أنه لا يمكن تمديد try كما هو. لحسن الحظ ، فإن الإصلاح سهل بما فيه الكفاية. (كما يحدث ، لم تواجه مقترحاتنا الداخلية السابقة هذه المشكلة - فقد تم تقديمها عندما أعدت كتابة نسختنا النهائية للنشر وحاولت تبسيط try لمطابقة قواعد تمرير المعلمة الحالية بشكل وثيق. - ولكن كما اتضح ، معيبة ، وغير مجدية في الغالب - فائدة لتكون قادرًا على كتابة try(a, b, c, handle) .)

قام إصدار سابق من try بتعريفه تقريبًا على النحو التالي: يأخذ try(expr, handler) تعبيرًا واحدًا (أو ربما اثنين) كوسيطات ، حيث قد يكون التعبير الأول متعدد القيم (يمكن أن يحدث فقط إذا كان التعبير مكالمة وظيفية). يجب أن تكون القيمة الأخيرة لهذا التعبير (ربما متعدد القيم) من النوع error ، ويتم اختبار هذه القيمة مقابل لا شيء. (إلخ - الباقي يمكنك تخيله).

على أي حال ، فإن النقطة المهمة هي أن try يقبل بشكل نحوي تعبير واحد فقط ، أو ربما تعبيرين. (لكن من الصعب بعض الشيء وصف دلالات try .) ستكون النتيجة تلك الشفرة مثل:

a, b := try(u, v, err)

لن يسمح بعد الآن. ولكن ليس هناك سبب وجيه لإجراء هذا العمل في المقام الأول: في معظم الحالات (ما لم يتم تسمية نتائج a و b ) ، يمكن إعادة كتابة هذا الرمز - إذا كان مهمًا لسبب ما - بسهولة في

a, b := u, v  // we don't care if the assignment happens in case of an error
try(err)

(أو استخدم كشف حساب if حسب الحاجة). لكن مرة أخرى ، هذا يبدو غير مهم.

يجب أن تقوم عبارة الإرجاع هذه بتعداد جميع قيم النتائج (صفر عادةً) حيث لا يُسمح بالعودة المجردة في هذه الحالة

العودة عارية غير مسموح بها ، لكن المحاولة ستكون كذلك. أحد الأشياء التي أحبها في المحاولة (إما كدالة أو بيان) هو أنني لن أحتاج إلى التفكير في كيفية تعيين قيم غير أخطاء عند إرجاع خطأ بعد الآن ، سأستخدم المحاولة.

griesemer شكرا على الشرح. هذا هو الاستنتاج الذي توصلت إليه أيضًا.

تعليق موجز على try كبيان: كما أعتقد يمكن رؤيته في المثال في https://github.com/golang/go/issues/32437#issuecomment -501035322 ، try دفن الديد. يصبح الرمز سلسلة من عبارات try ، مما يحجب ما يفعله الكود بالفعل.

قد تعيد التعليمات البرمجية الحالية استخدام متغير خطأ تم إعلانه حديثًا بعد كتلة if err != nil . قد يؤدي إخفاء المتغير إلى كسر ذلك ، ولن تؤدي إضافة متغير إرجاع مسمى إلى توقيع الوظيفة إلى إصلاحه دائمًا.

ربما يكون من الأفضل ترك إعلان / تعيين الخطأ كما هو ، والعثور على خطأ من سطر واحد في معالجة stmt.

err := f() // followed by one of

on err, return err            // any type can be tested for non-zero
on err, return fmt.Errorf(...)
on err, fmt.Println(err)      // doesn't stop the function
on err, hname err             // handler invocation without parens
on err, ignore err            // optional ignore handler logs error if defined

if err, return err            // alternatively, use if

handle hname(err error, clr caller) { // type caller has results of runtime.Caller()
   if err == io.Bad { return err }
   fmt.Println(clr.name, err)
}

قد يصاب التعبير الجزئي try بالذعر ، مما يعني أن الخطأ غير متوقع أبدًا. البديل من ذلك يمكن أن يتجاهل أي خطأ.

f(try g()) // panic on error
f(try_ g()) // ignore any error

بيت القصيد من هذا الاقتراح هو جعل معالجة الأخطاء الشائعة تتلاشى في الخلفية - يجب ألا تهيمن معالجة الأخطاء على الكود. لكن يجب أن تكون صريحة.

تعجبني فكرة إدراج التعليقات try كبيان. إنه واضح ، ولا يزال من السهل تلميعه (لأنه بطول ثابت) ، ولكن ليس من السهل إخفاءه (لأنه دائمًا في نفس المكان) بحيث يمكن إخفاؤه بعيدًا في خط مزدحم. يمكن أيضًا دمجه مع defer fmt.HandleErrorf(...) كما هو مذكور من قبل ، ومع ذلك فإنه يحتوي على مأزق إساءة استخدام المعلمات المحددة من أجل التفاف الأخطاء (التي لا تزال تبدو وكأنها اختراق ذكي بالنسبة لي. الاختراقات الذكية سيئة.)

أحد الأسباب التي جعلتني لا أحب try كتعبير هو أنه إما أنه من السهل جدًا التستر عليه ، أو أنه ليس من السهل إخفاءه. خذ المثالين التاليين:

جرب كتعبير

// Too hidden, it's in a crowded function with many symbols that complicate the function.

f, err := os.OpenFile(try(FileName()), os.O_APPEND|os.O_WRONLY, 0600)

// Not easy enough, the word "try" already increases horizontal complexity, and it
// being an expression only encourages more horizontal complexity.
// If this code weren't collapsed to multiple lines, it would be extremely
// hard to read and unclear as to what's executing when.

fullContents := try(io.CopyN(
    os.Stdout,
    try(net.Dial("tcp", "localhost:"+try(buf.ReadString("\n"))),
    try(strconv.Atoi(try(buf.ReadString("\n")))),
))

جرب كبيان

// easy to see while still not being too verbose

try name := FileName()
os.OpenFile(name, os.O_APPEND|os.O_WRONLY, 0600)

// does not allow for clever one-liners, code remains much more readable.
// also, the code reads in sequence from top-to-bottom.

try port := r.ReadString("\n")
try lengthStr := r.ReadString("\n")
try length := strconv.Atoi(lengthStr)

try con := net.Dial("tcp", "localhost:"+port)
try io.CopyN(os.Stdout, con, length)

الوجبات الجاهزة

أنا أعترف أن هذا الرمز مفتعل بالتأكيد. لكن ما أفهمه هو أنه ، بشكل عام ، try كتعبير لا يعمل بشكل جيد في:

  1. وسط التعبيرات المزدحمة التي لا تحتاج إلى الكثير من تدقيق الأخطاء
  2. عبارات متعددة الأسطر بسيطة نسبيًا تتطلب الكثير من التحقق من الأخطاء

ومع ذلك ، أتفق مع ianlancetaylor على أن بدء كل سطر بـ try يبدو أنه يعيق الجزء المهم من كل عبارة (المتغير الذي يتم تعريفه أو الوظيفة التي يتم تنفيذها). ومع ذلك ، أعتقد أنه في نفس الموقع وبعرض ثابت ، فمن الأسهل بكثير إخفاءه ، بينما لا يزال يتم ملاحظته. ومع ذلك ، فإن عيون الجميع مختلفة.

أعتقد أيضًا أن تشجيع الخط الفردي الماهر في الكود هو مجرد فكرة سيئة بشكل عام. أنا مندهش من أنني أستطيع صنع مثل هذا الخط القوي كما في المثال الأول ، إنه مقتطف يستحق وظيفته بالكامل لأنه يفعل الكثير - لكنه يناسب سطرًا واحدًا إذا لم أقم بإنهياره لمضاعفة من أجل سهولة القراءة. الكل في سطر واحد:

fullContents := try(io.CopyN(os.Stdout, try(net.Dial("tcp", "localhost:"+try(r.ReadString("\n"))), try(strconv.Atoi(try(r.ReadString("\n"))))))

يقرأ منفذًا من *bufio.Reader ، ويبدأ اتصال TCP ، وينسخ عددًا من البايتات المحددة بواسطة نفس *bufio.Reader إلى stdout . كل ذلك مع معالجة الأخطاء. بالنسبة للغة ذات اتفاقيات التشفير الصارمة ، لا أعتقد أنه يجب السماح بذلك حقًا. أعتقد أن gofmt يمكن أن يساعد في هذا ، رغم ذلك.

بالنسبة للغة ذات اتفاقيات التشفير الصارمة ، لا أعتقد أنه يجب السماح بذلك حقًا.

من الممكن كتابة كود بغيض في Go. بل من الممكن تنسيقها بشكل رهيب ؛ هناك فقط قواعد وأدوات قوية ضده. الذهاب لديه حتى goto .

أثناء مراجعات الكود ، أطلب أحيانًا من الأشخاص تقسيم التعبيرات المعقدة إلى عبارات متعددة ، بأسماء وسيطة مفيدة. كنت سأفعل شيئًا مشابهًا لـ try s المتداخلة بشدة ، للسبب نفسه.

وهذا هو كل ما يجب قوله: دعونا لا نحاول جاهدين تحريم الشفرات السيئة على حساب تشويه اللغة. لدينا آليات أخرى للحفاظ على الكود نظيفًا أكثر ملاءمة لشيء يتضمن بشكل أساسي حكمًا بشريًا على أساس كل حالة على حدة.

من الممكن كتابة كود بغيض في Go. بل من الممكن تنسيقها بشكل رهيب ؛ هناك فقط قواعد وأدوات قوية ضده. الذهاب حتى لديه الانتقال.

أثناء مراجعات الكود ، أطلب أحيانًا من الأشخاص تقسيم التعبيرات المعقدة إلى عبارات متعددة ، بأسماء وسيطة مفيدة. كنت سأفعل شيئًا مشابهًا للمحاولات المتداخلة بشدة ، للسبب نفسه.

وهذا هو كل ما يجب قوله: دعونا لا نحاول جاهدين تحريم الشفرات السيئة على حساب تشويه اللغة. لدينا آليات أخرى للحفاظ على الكود نظيفًا أكثر ملاءمة لشيء يتضمن بشكل أساسي حكمًا بشريًا على أساس كل حالة على حدة.

هذه نقطة جيدة. لا يجب أن نحرم فكرة جيدة لمجرد أنه يمكن استخدامها لإنشاء كود سيئ. ومع ذلك ، أعتقد أنه إذا كان لدينا بديل يروج لشفرة أفضل ، فقد تكون فكرة جيدة. لم أر حقًا الكثير من الحديث _ ضد_ الفكرة الأولية وراء try كإقرار (بدون كل else { ... } غير المرغوب فيه) حتى تعليق ianlancetaylor ، ولكن ربما فاتني للتو.

أيضًا ، لا يوجد لدى كل شخص مراجعو أكواد ، سيضطر بعض الأشخاص (خاصة في المستقبل البعيد) إلى الاحتفاظ برمز Go غير المراجَع. عادةً ما تقوم لغة Go كلغة بعمل جيد جدًا في التأكد من أن جميع التعليمات البرمجية المكتوبة تقريبًا يمكن صيانتها جيدًا (على الأقل بعد go fmt ) ، وهذا ليس بالأمر الفذ الذي يجب التغاضي عنه.

ومع ذلك ، فإنني أنتقد هذه الفكرة بشدة في حين أنها ليست مروعة حقًا.

جرب كبيان يقلل من الصيغة المعيارية بشكل كبير ، وأكثر من المحاولة كتعبير ، إذا سمحنا له بالعمل على كتلة من التعبيرات كما تم اقتراحه من قبل ، حتى بدون السماح لكتلة أخرى أو معالج أخطاء. باستخدام هذا ، يصبح مثال deandeveloper:

try (
    name := FileName()
    file := os.OpenFile(name, os.O_APPEND|os.O_WRONLY, 0600)
    port := r.ReadString("\n")
    lengthStr := r.ReadString("\n")
    length := strconv.Atoi(lengthStr)
    con := net.Dial("tcp", "localhost:"+port)
    io.CopyN(os.Stdout, con, length)
)

إذا كان الهدف هو تقليل النموذج المعياري if err!= nil {return err} ، إذن أعتقد أن العبارة التي تسمح بأخذ كتلة من التعليمات البرمجية لديها أكبر إمكانية للقيام بذلك ، دون أن تصبح غير واضحة.

beoran في هذه المرحلة ، لماذا المحاولة على الإطلاق؟ ما عليك سوى السماح بمهمة حيث تكون قيمة الخطأ الأخيرة مفقودة وجعلها تتصرف كما لو كانت عبارة try (أو استدعاء دالة). لا يعني ذلك أنني أقترح ذلك ، ولكنه سيقلل من مستوى النمطي المعياري أكثر من ذلك.

أعتقد أنه سيتم تقليل النموذج المعياري بكفاءة من خلال كتل var هذه ، لكنني أخشى أن يؤدي إلى زيادة مقدار كبير من التعليمات البرمجية إلى مستوى إضافي ، وهو أمر مؤسف.

تضمين التغريدة

fullContents := try(io.CopyN(os.Stdout, try(net.Dial("tcp", "localhost:"+try(r.ReadString("\n"))), try(strconv.Atoi(try(r.ReadString("\n"))))))

يجب أن أعترف بأنه غير مقروء بالنسبة لي ، ربما أشعر أنه يجب علي:

fullContents := try(io.CopyN(os.Stdout, 
                               try(net.Dial("tcp", "localhost:"+try(r.ReadString("\n"))),
                                     try(strconv.Atoi(try(r.ReadString("\n"))))))

أو ما شابه ، لسهولة القراءة ، ثم نعود بـ "المحاولة" في بداية كل سطر ، مع المسافة البادئة.

حسنًا ، أعتقد أننا ما زلنا بحاجة إلى محاولة التوافق مع الإصدارات السابقة ، وأيضًا أن نكون واضحين بشأن العودة التي يمكن أن تحدث في الكتلة. لكن لاحظ أنني أتبع منطق تقليل صفيحة الغلاية ثم أرى إلى أين يقودنا. هناك دائمًا توتر بين تقليل النموذج المعياري والوضوح. أعتقد أن القضية الرئيسية المطروحة في هذه القضية هي أننا جميعًا نختلف على ما يبدو حول مكان التوازن.

أما بالنسبة للمسافات البادئة ، فهذا ما يعنيه الأمر ، لذلك أنا شخصياً لا أشعر أنها مشكلة كبيرة.

أود الانضمام إلى المعركة لأذكر احتمالين آخرين ، كل منهما مستقل ، لذلك سأحتفظ بهما في منشورات منفصلة.

اعتقدت أن الاقتراح القائل بأنه يمكن تعريف try() (بدون وسيطات) لإرجاع مؤشر إلى متغير إرجاع الخطأ ، كان اقتراحًا مثيرًا للاهتمام ، لكنني لم أكن حريصًا على هذا النوع من العقاب - إنه ينم عن زيادة في التحميل على الوظيفة ، وهو شيء يتجنبه Go.

ومع ذلك ، فقد أحببت الفكرة العامة للمعرف المحدد مسبقًا الذي يشير إلى قيمة الخطأ المحلي.

إذن ، ماذا عن التحديد المسبق للمعرف err نفسه ليكون اسمًا مستعارًا لمتغير إرجاع الخطأ؟ لذلك سيكون هذا صحيحًا:

func foo() error {
    defer handleError(&err, etc)
    try(something())
}

سيكون مطابقًا وظيفيًا لـ:

func foo() (err error) {
    defer handleError(&err, etc)
    try(something())
}

سيتم تعريف المعرف err في نطاق الكون ، على الرغم من أنه يعمل كاسم مستعار محلي للوظيفة ، لذا فإن أي تعريف على مستوى الحزمة أو تعريف دالة محلية لـ err سيتجاوزه. قد يبدو هذا خطيرًا لكنني قمت بمسح 22 مترًا من خطوط Go in the Go وهو نادر جدًا. لا يوجد سوى 4 حالات مميزة err مستخدمة كمتغير عام (كل ذلك كمتغير ، وليس نوعًا أو ثابتًا) - هذا شيء يمكن أن يحذر منه vet .

من الممكن أن يكون هناك متغيرين في دالة إرجاع الخطأ في النطاق ؛ في هذه الحالة ، أعتقد أنه من الأفضل أن يشكو المترجم من وجود غموض ويطلب من المستخدم تسمية متغير الإرجاع الصحيح صراحة. لذلك سيكون هذا غير صالح:

func foo() error {
    f := func() error {
        defer handleError(&err, etc)
        try(something())
        return nil
    }
    return f()
}

لكن يمكنك دائمًا كتابة هذا بدلاً من ذلك:

func foo() error {
    f := func() (err error) {
        defer handleError(&err, etc)
        try(something())
        return nil
    }
    return f()
}

حول موضوع try كمعرف محدد مسبقًا وليس عامل تشغيل ،
وجدت نفسي أتجه نحو تفضيل الخيار الأخير بعد أن أخطأت في الأقواس مرارًا وتكرارًا عند الكتابة:

try(try(os.Create(filename)).Write(data))

تحت عنوان "لماذا لا نستخدم؟ مثل Rust" ، تقول الأسئلة الشائعة:

لقد تجنبنا حتى الآن الاختصارات أو الرموز المبهمة في اللغة ، بما في ذلك العوامل غير العادية مثل؟ ، التي لها معاني غامضة أو غير واضحة.

لست متأكدًا تمامًا من صحة ذلك. عامل التشغيل .() غير معتاد حتى تعرف Go ، وكذلك مشغلي القناة. إذا أضفنا عامل تشغيل ? ، فأعتقد أنه سيصبح قريبًا في كل مكان بما يكفي بحيث لن يكون عائقا كبيرا.

تمت إضافة عامل التشغيل Rust ? بعد قوس الإغلاق لاستدعاء الوظيفة ، وهذا يعني أنه من السهل تفويتها عندما تكون قائمة الوسائط طويلة.

ماذا عن إضافة ?() كمعامل اتصال:

لذا بدلاً من:

x := try(foo(a, b))

كنت ستفعل:

x := foo?(a, b)

دلالات ?() ستكون مشابهة جدًا لتلك الخاصة بـ try المُضمَّن. سيكون بمثابة استدعاء دالة فيما عدا أن الوظيفة أو الطريقة التي يتم استدعاؤها يجب أن تُرجع خطأً كوسيطة أخيرة لها. كما هو الحال مع try ، إذا كان الخطأ غير صفري ، فإن عبارة ?() ستعيده.

يبدو أن المناقشة قد أصبحت مركزة بدرجة كافية لدرجة أننا الآن نلتف حول سلسلة من المقايضات المحددة والمناقش عليها جيدًا. هذا أمر يثلج الصدر ، على الأقل بالنسبة لي ، لأن التسوية هي إلى حد كبير في روح هذه اللغة.

ianlancetaylor سأعترف تمامًا بأننا سننتهي بعشرات الأسطر مسبوقة بـ try . ومع ذلك ، لا أرى كيف أن هذا أسوأ من عشرات السطور التي تم إصلاحها بعد الإصلاح بتعبير شرطي من سطرين إلى أربعة أسطر يشير صراحةً إلى نفس التعبير return . في الواقع ، يجعل try (مع عبارات else ) من السهل تحديد ما إذا كان معالج الأخطاء يقوم بشيء خاص / غير افتراضي. أيضًا ، بشكل عرضي ، إعادة: تعبيرات if المشروطة ، أعتقد أنها دفنت حرف LED أكثر من العبارة المقترحة try -as-a: استدعاء الوظيفة يعيش على نفس السطر مثل الشرطي ، ينتهي الأمر الشرطي نفسه في نهاية السطر المزدحم بالفعل ، ويتم تحديد نطاق التخصيصات المتغيرة إلى الكتلة (الأمر الذي يتطلب صياغة مختلفة إذا كنت بحاجة إلى هذه المتغيرات بعد الكتلة).

@ josharian لقد فكرت في هذا الأمر مؤخرًا. تسعى Go جاهدة من أجل البراغماتية ، وليس الكمال ، ويبدو أن تطورها غالبًا ما يكون مدفوعًا بالبيانات وليس مدفوعًا بالمبادئ. يمكنك كتابة رهيبة Go ، لكنها عادة ما تكون أصعب من كتابة Go اللائقة (وهو أمر جيد بما يكفي لمعظم الناس). تجدر الإشارة أيضًا إلى - لدينا العديد من الأدوات لمكافحة التعليمات البرمجية السيئة: ليس فقط gofmt و go vet ، ولكن زملائنا ، والثقافة التي وضعها هذا المجتمع (بعناية شديدة) لتوجيه نفسه. أكره الابتعاد عن التحسينات التي تساعد الحالة العامة لمجرد أن شخصًا ما في مكان ما قد يسير بنفسه.

beoran هذا أمر أنيق ، وعندما تفكر في الأمر ، يكون في الواقع مختلفًا جوهريًا عن كتل اللغات الأخرى try ، لأنه يحتوي على نتيجة واحدة محتملة فقط: العودة من الوظيفة مع وجود خطأ غير معالج. ومع ذلك: 1) من المحتمل أن يكون هذا محيرًا لمبرمجي Go الجدد الذين عملوا مع تلك اللغات الأخرى (بصراحة ليست أكبر اهتماماتي ؛ أنا أثق في ذكاء المبرمجين) ، و 2) سيؤدي ذلك إلى وضع مسافة بادئة بكميات هائلة من التعليمات البرمجية عبر العديد قواعد البيانات. بقدر ما يتعلق الأمر بالشفرة الخاصة بي ، فأنا أميل إلى تجنب الكتل type / const / var لهذا السبب. أيضًا ، الكلمات الرئيسية الوحيدة التي تسمح حاليًا بمثل هذه الكتل هي التعريفات ، وليست عبارات التحكم.

yiyus أنا لا أوافق على إزالة الكلمة الأساسية ، لأن الوضوح هو (في رأيي) إحدى فضائل Go. لكنني أوافق على أن وضع مسافة بادئة لمقدار ضخم من التعليمات البرمجية للاستفادة من تعبيرات try فكرة سيئة. لذلك ربما لا توجد كتل try على الإطلاق؟

rogpeppe أعتقد أن هذا النوع من العمليات الدقيقة معقول فقط للمكالمات التي لا ينبغي أبدًا أن تُرجع الخطأ ، ولذا فإن الذعر إذا فعلوا ذلك. أو المكالمات حيث تتجاهل الخطأ دائمًا. لكن يبدو أن كلاهما نادر. إذا كنت منفتحًا على عامل جديد ، فراجع # 32500.

لقد اقترحت أن f(try g()) يجب أن يصاب بالذعر في https://github.com/golang/go/issues/32437#issuecomment -501074836 ، جنبًا إلى جنب مع معالجة سطر واحد stmt:
on err, return ...

أعتقد أن else الاختياري في try ... else { ... } سيدفع الكود كثيرًا إلى اليمين ، وربما يحجبه. أتوقع أن تستغرق كتلة الخطأ 25 حرفًا على الأقل معظم الوقت. أيضًا ، حتى الآن ، لا يتم الاحتفاظ بالكتل على نفس السطر بواسطة go fmt وأتوقع أن يتم الاحتفاظ بهذا السلوك مقابل try else . لذلك يجب أن نناقش ونقارن العينات حيث تكون الكتلة else في سطر منفصل. ولكن حتى ذلك الحين لست متأكدًا من قابلية قراءة else { في نهاية السطر.

yiyus https://github.com/golang/go/issues/32437#issuecomment -501139662

beoran في هذه المرحلة ، لماذا المحاولة على الإطلاق؟ ما عليك سوى السماح بمهمة حيث تكون قيمة الخطأ الأخيرة مفقودة وجعلها تتصرف كما لو كانت عبارة try (أو استدعاء دالة). لا يعني ذلك أنني أقترح ذلك ، ولكنه سيقلل من مستوى النمطي المعياري أكثر من ذلك.

لا يمكن القيام بذلك لأن Go1 يسمح بالفعل بالاتصال بـ func foo() error كـ foo() فقط. ستؤدي إضافة , error إلى قيم الإرجاع الخاصة بالمتصل إلى تغيير سلوك الكود الموجود داخل هذه الوظيفة. راجع https://github.com/golang/go/issues/32437#issuecomment -500289410

rogpeppe في تعليقك حول الحصول على الأقواس بشكل صحيح مع try 's المتداخلة: هل لديك أي آراء حول أسبقية try ؟ راجع أيضًا مستند التصميم التفصيلي حول هذا الموضوع .

griesemer أنا في الواقع لست حريصًا على try كمعامل بادئة أحادي للأسباب المشار إليها هناك. لقد خطر لي أن الطريقة البديلة هي السماح try كطريقة زائفة على دالة إرجاع tuple:

 f := os.Open(path).try()

هذا يحل مشكلة الأسبقية ، على ما أعتقد ، لكنه ليس حقًا مثل Go.

تضمين التغريدة

مثير جدا! . قد تكون حقًا في شيء ما هنا.

وماذا عن توسيع هذه الفكرة على هذا النحو؟

for _,fp := range filepaths {
    f := os.Open(path).try(func(err error)bool{
        fmt.Printf( "Cannot open file %s\n", fp );
        continue;
    });
}

راجع للشغل ، قد أفضّل اسمًا مختلفًا مقابل try() مثل ربما guard() لكن لا يجب أن أقوم بتدوير الاسم قبل الهندسة المعمارية التي يناقشها الآخرون.

ضد :

for _,fp := range filepaths {
    if f,err := os.Open(path);err!=nil{
        fmt.Printf( "Cannot open file %s\n", fp )
    }
}

؟

أنا أحب try a,b := foo() بدلاً من if err!=nil {return err} لأنه يحل محل النموذج المعياري لحالة بسيطة حقًا. ولكن بالنسبة لكل شيء آخر يضيف سياقًا ، فهل نحتاج حقًا إلى شيء آخر غير if err!=nil {...} (سيكون من الصعب جدًا العثور على أفضل)؟

إذا كان الخط الإضافي مطلوبًا عادةً للزخرفة / الالتفاف ، فلنقم فقط "بتخصيص" خط له.

f, err := os.Open(path)  // normal Go \o/
on err, return fmt.Errorf("Cannot open %s, due to %v", path, err)

@ networkimprov أعتقد أنني يمكن أن أحب ذلك أيضًا. دفع مصطلحًا أكثر جاذبية ووصفًا قد تطرقت إليه بالفعل ...

f, err := os.Open(path)
relay err { nil, fmt.Errorf("Cannot open %s, due to %v", path, err) }

// marginally shorter, doesn't trigger vertical formatting unless excessively wide
// enclosed expression restricted to a list of values that match the return args

أو

f, err := os.Open(path)
relay(err) nil, fmt.Errorf("Cannot open %s, due to %v", path, err)

// somewhere between statement and func, prob more pleasing to type w/out completion
// trailing expression restricted to a list of values that match the return args
// maybe excessive width triggers linting noise - with a reformatter available
// providing a reformatter would make swapping old (narrow enough) code easy

@ daved سعيد لأنك تعجبك! سيسمح on err, ... بأي معالج أحادي stmt:

err := f() // followed by one of

on err, return err            // any type can be tested for non-zero
on err, return fmt.Errorf(...)
on err, fmt.Println(err)      // doesn't stop the function
on err, continue              // retry in a loop
on err, hname err             // named handler invocation without parens
on err, ignore err            // logs error if handle ignore() defined

handle hname(err error, clr caller) { // type caller has results of runtime.Caller()
   if err == io.Bad { return err } // non-local return
   fmt.Println(clr, err)
}

تحرير: on يستعير من Javascript. لم أرغب في زيادة التحميل على if .
الفاصلة ليست ضرورية ، لكني لا أحب الفاصلة المنقوطة هناك. ربما القولون؟

أنا لا أتابع relay تمامًا ؛ يعني العودة على خطأ؟

يتم تعطل المرحل الواقي عند استيفاء بعض الشروط. في هذه الحالة ، عندما لا تكون قيمة الخطأ صفرية ، يغير المرحل تدفق التحكم للعودة باستخدام القيم اللاحقة.

* لا أرغب في زيادة تحميل , لهذه الحالة ، ولست من المعجبين بالمصطلح on ، لكني أحب الفرضية والمظهر العام لهيكل الكود.

بالنسبة إلى نقطة josharian في وقت سابق ، أشعر أن جزءًا كبيرًا من النقاش حول مطابقة الأقواس هو في الغالب افتراضي ويستخدم أمثلة مفتعلة. لا أعرف عنك ولكني لا أجد نفسي أجد صعوبة في كتابة مكالمات وظيفية في البرمجة اليومية الخاصة بي. إذا وصلت إلى نقطة يصعب فيها قراءة التعبير أو فهمه ، أقسمه إلى تعبيرات متعددة باستخدام متغيرات وسيطة. لا أفهم لماذا سيكون try() مع بناء جملة استدعاء الوظيفة مختلفًا في هذا الصدد من الناحية العملية.

eandre في العادة ، لا تحتوي الوظائف على مثل هذا التعريف الديناميكي. العديد من أشكال هذا الاقتراح تقلل من السلامة المحيطة بتواصل تدفق التحكم ، وهذا أمر مزعج.

networkimprovdaved أنا لا أكره هاتين الفكرتين ، لكنهما لا يشعران بالقدر الكافي من التحسن على مجرد السماح بعبارات سطر واحد if err != nil { ... } لضمان تغيير اللغة. أيضًا ، هل يفعل أي شيء لتقليل النموذج المعياري المتكرر في حالة إرجاع الخطأ؟ أو فكرة أنه عليك دائمًا كتابة return ؟

brynbellomy في المثال الخاص بي ، لا يوجد return . relay هو مرحل وقائي يعرف بأنه "إذا لم يكن هذا الخطأ صفريًا ، فسيتم إرجاع التالي".

باستخدام المثال الثاني من السابق:

f, err := os.Open(path)
relay(err) nil, fmt.Errorf("Cannot open %s, due to %v", path, err)

يمكن أيضًا أن يكون شيئًا مثل:

f, err := os.Open(path)
relay(err)

مع الخطأ الذي يقوم برحلات الترحيل الذي يتم إرجاعه مع قيم صفرية لقيم الإرجاع الأخرى (أو أي قيم يتم تعيينها للقيم المرتجعة المسماة). شكل آخر قد يكون مفيدًا:

wrap := func(err error, msg string) error {
    if err != nil {
        fmt.Errorf("%s: %s", msg, err)
    }
    return nil
}

// ...

f, err := os.Open(path)
relay(err, wrap(err, fmt.Sprintf("Cannot open %s", path)))

حيث لا يتم استدعاء وسيط التتابع الثاني ما لم يتم تعثر التتابع بواسطة وسيطة الترحيل الأول. سيكون خطأ الترحيل الثاني الاختياري هو القيمة التي تم إرجاعها.

هل يجب أن تسمح _go fmt_ بسطر واحد if لكن ليس case, for, else, var () ؟ أريدهم جميعًا ، من فضلك ؛-)

لقد تجاهل فريق Go العديد من الطلبات لفحص الأخطاء من سطر واحد.

يمكن أن تكون عبارات on err, return err متكررة ، لكنها صريحة ومقتضبة وواضحة.

magical لقد تمت معالجة ملاحظاتك في الإصدار المحدث من الاقتراح التفصيلي .

شيء صغير ، ولكن إذا كانت try كلمة رئيسية ، فيمكن التعرف عليها على أنها بيان إنهاء ، لذا بدلاً من

func f() error {
  try(g())
  return nil
}

يمكنك فقط أن تفعل

func f() error {
  try g()
}

( try -statement يحصل على ذلك مجانًا ، try - يحتاج المشغل إلى معالجة خاصة ، أدرك أن ما ورد أعلاه ليس مثالًا رائعًا: ولكنه ضئيل للغاية)

يمكن التعرف على jimmyfrasche try على أنه بيان إنهاء حتى لو لم تكن كلمة رئيسية - لقد فعلنا ذلك بالفعل باستخدام panic ، ولا توجد حاجة إلى معالجة خاصة إضافية إلى جانب ما نقوم به بالفعل. ولكن إلى جانب هذه النقطة ، فإن try ليس بيان إنهاء ، ومحاولة جعله واحدًا بشكل مصطنع يبدو غريبًا.

جميع النقاط الصالحة. أعتقد أنه لا يمكن اعتباره بشكل موثوق إلا بمثابة بيان إنهاء إذا كان هو آخر سطر من وظيفة لا تعرض سوى خطأ ، مثل CopyFile في الاقتراح المفصل ، أو يتم استخدامه كـ try(err) في if حيث يُعرف أن err != nil . لا يبدو أنه يستحق ذلك.

نظرًا لأن هذا الموضوع يستغرق وقتًا طويلاً ويصعب متابعته (ويبدأ في تكرار نفسه إلى حد ما) ، أعتقد أننا جميعًا نتفق على أننا سنحتاج إلى تقديم تنازلات بشأن "بعض الجوانب الإيجابية التي يقدمها أي اقتراح.

نظرًا لأننا نستمر في الإعجاب أو عدم الإعجاب بالتبديلات البرمجية المقترحة أعلاه ، فإننا لا نساعد أنفسنا في الحصول على إحساس حقيقي "هل هذا حل وسط أكثر منطقية من الآخر / ما الذي تم تقديمه بالفعل"؟

أعتقد أننا بحاجة إلى بعض المعايير الموضوعية لتقييم الاختلافات والاقتراحات البديلة "للمحاولة".

  • هل تقلل من المستوى المعياري؟
  • مقروئية
  • تمت إضافة التعقيد إلى اللغة
  • توحيد الخطأ
  • الذهاب العش
    ...
    ...
  • جهود التنفيذ والمخاطر
    ...

يمكننا بالطبع أيضًا وضع بعض القواعد الأساسية لـ no-go (لن يكون التوافق مع الإصدارات السابقة واحدًا) ، وترك منطقة رمادية لـ "هل تبدو جذابة / الشعور الغريزي وما إلى ذلك (يمكن أيضًا أن تكون المعايير" الصعبة "أعلاه قابلة للنقاش .. .).

إذا اختبرنا أي اقتراح مقابل هذه القائمة ، وقمنا بتقييم كل نقطة (معيار 5 نقاط ، قابلية القراءة 4 نقاط ، إلخ) ، فعندئذ أعتقد أنه يمكننا بدلاً من ذلك التوافق مع:
من المحتمل أن تكون خياراتنا هي A و B و C ، علاوة على ذلك ، يمكن للشخص الذي يرغب في إضافة عرض جديد أن يختبر (إلى حد ما) ما إذا كان اقتراحه يفي بالمعايير.

إذا كان هذا منطقيًا ، قم بإبهام هذا الأمر ، فيمكننا محاولة مراجعة الاقتراح الأصلي
https://github.com/golang/proposal/blob/master/design/32437-try-builtin.md

وربما بعض المقترحات الأخرى مضمنة في التعليقات أو مرتبطة ، ربما نتعلم شيئًا ما ، أو حتى نأتي بمزيج من شأنه أن يكون أعلى.

المعايير + = إعادة استخدام رمز معالجة الخطأ عبر الحزمة وداخل الوظيفة

شكرا للجميع على ردود الفعل المستمرة على هذا الاقتراح.

لقد انحرف النقاش قليلاً عن جوهر القضية. لقد أصبح أيضًا خاضعًا لسيطرة أكثر من عشرة مساهمين (أنت تعرف من أنت) الذين يبحثون عن المقترحات البديلة.

لذلك اسمحوا لي فقط بتذكيرك بطريقة ودية بأن هذه المسألة تتعلق بمقترح _ محدد _. هذا _ليس _ استجداءً لأفكار نحوية جديدة لمعالجة الأخطاء (وهو أمر جيد يجب القيام به ، ولكنه ليس _ هذه _ المشكلة).

دعنا نركز المناقشة مرة أخرى ونعود إلى المسار الصحيح.

تكون التعليقات مفيدة للغاية إذا كانت تساعد في تحديد الحقائق التقنية التي فاتتنا ، مثل "هذا الاقتراح لا يعمل بشكل صحيح في هذه الحالة" أو "سيكون له هذا الضمني الذي لم ندركه".

على سبيل المثال ، أشار magical إلى أن الاقتراح كما هو مكتوب لم يكن قابلاً للتوسيع كما يُزعم (كان النص الأصلي سيجعل من المستحيل إضافة وسيطة ثانية في المستقبل). لحسن الحظ ، كانت هذه مشكلة بسيطة تمت معالجتها بسهولة من خلال تعديل بسيط للاقتراح. ساعدت مساهمته بشكل مباشر في جعل الاقتراح أفضل.

استغرق crawshaw وقتًا لتحليل بضع مئات من حالات الاستخدام من مكتبة الأمراض المنقولة جنسياً وأظهر أن try نادرًا ما ينتهي به الأمر داخل تعبير آخر ، وبالتالي دحض القلق بشكل مباشر من أن try قد يصبح مدفونًا وغير مرئي. هذه تعليقات مفيدة للغاية قائمة على الحقائق ، وفي هذه الحالة يتم التحقق من صحة التصميم.

في المقابل ، لا تكون الأحكام "الجمالية" الشخصية مفيدة للغاية. يمكننا تسجيل هذه التعليقات ، لكن لا يمكننا التصرف بناءً عليها (إلى جانب الخروج باقتراح آخر).

فيما يتعلق بالخروج بمقترحات بديلة: الاقتراح الحالي هو ثمرة الكثير من العمل ، بدءًا من مسودة تصميم العام الماضي. لقد كررنا هذا التصميم عدة مرات وطلبنا التعليقات من العديد من الأشخاص قبل أن نشعر بالراحة الكافية لنشره والتوصية بتطويره إلى مرحلة التجربة الفعلية ، لكننا لم نقم بالتجربة بعد. من المنطقي العودة إلى لوحة الرسم إذا فشلت التجربة ، أو إذا أخبرتنا التعليقات مسبقًا أنها ستفشل بشكل واضح. إذا قمنا بإعادة التصميم بسرعة ، بناءً على الانطباعات الأولى ، فإننا نضيع وقت الجميع ، والأسوأ من ذلك ، لا نتعلم شيئًا في هذه العملية.

بعد كل ما قيل ، فإن الشاغل الأكثر أهمية الذي أعرب عنه الكثيرون مع هذا الاقتراح هو أنه لا يشجع صراحة زخرفة الأخطاء إلى جانب ما يمكننا فعله بالفعل في اللغة. شكرا لك ، لقد سجلنا هذه التعليقات. لقد تلقينا نفس التعليقات داخليًا ، قبل نشر هذا الاقتراح. لكن لا أحد من البدائل التي نظرنا فيها أفضل مما لدينا الآن (وقد بحثنا كثيرًا بعمق). بدلاً من ذلك ، قررنا اقتراح الحد الأدنى من الفكرة التي تتناول جزءًا واحدًا من معالجة الخطأ بشكل جيد ، والتي يمكن تمديدها إذا لزم الأمر ، لمعالجة هذا القلق بالضبط (يتحدث الاقتراح حول هذا الأمر بالتفصيل).

شكرا.

(ألاحظ أن اثنين من الأشخاص الذين يدافعون عن مقترحات بديلة بدأوا قضاياهم المنفصلة. وهذا أمر جيد ويساعد في الحفاظ على تركيز القضايا المعنية. شكرًا.)

تضمين التغريدة
أوافق تمامًا على أننا يجب أن نركز وهذا بالضبط ما دفعني إلى الكتابة:

إذا كان هذا منطقيًا ، قم بإبهام هذا الأمر ، فيمكننا محاولة مراجعة الاقتراح الأصلي
https://github.com/golang/proposal/blob/master/design/32437-try-builtin.md

سؤالين:

  1. هل توافق إذا وضعنا علامة على الجوانب الإيجابية (الحد المعياري ، إمكانية القراءة ، إلخ) مقابل الجوانب السلبية (لا يوجد زخرفة خطأ واضحة / قدرة تتبع أقل لمصدر خط الخطأ) يمكننا في الواقع أن نقول: يهدف هذا الاقتراح بشدة إلى حل أ ، ب ، إلى حد ما مساعدة ج ، لا تهدف إلى حل د ، ه
    وبهذا تفقد كل فوضى "لكنها لا د" ، "كيف يمكن أن ت" وتجعلها أكثر تجاه القضايا التقنية مثل magical أشار
    وأيضًا عدم تشجيع تعليقات "لكن الحل XXX يحل د ، ه بشكل أفضل.
  2. العديد من المنشورات المضمنة هي "اقتراحات لتغييرات طفيفة في الاقتراح" - أعلم أنها خط رفيع ، لكنني أعتقد أنه من المنطقي الاحتفاظ بها.

LMKWYT.

يستخدم try() مع صفر args (أو مدمج مختلف) لا يزال قيد الدراسة أو تم استبعاد ذلك.

بعد التغييرات التي تم إجراؤها على الاقتراح ، ما زلت قلقًا بشأن كيفية جعل استخدام قيم الإرجاع المسماة أكثر "شيوعًا". ومع ذلك ، ليس لدي بيانات لدعم ذلك: upside_down_ face : .
إذا تمت إضافة try() مع صفر args (أو مضمنة مختلفة) إلى الاقتراح ، فهل يمكن تحديث الأمثلة في الاقتراح لاستخدام try() (أو مضمنة مختلفة) لتجنب المرتجعات المسماة؟

guybrand يُعد التصويت المؤيد والتصويت السلبي أمرًا رائعًا للتعبير عن _sentiment_ - ولكن هذا يتعلق به. لا يوجد مزيد من المعلومات هناك. لن نتخذ قرارًا بناءً على عدد الأصوات ، أي المشاعر وحدها. بالطبع ، إذا كان الجميع - لنقل 90٪ + - يكرهون عرضًا ما ، فربما تكون هذه علامة سيئة ويجب علينا التفكير مرتين قبل المضي قدمًا. ولكن لا يبدو أن هذا هو الحال هنا. يبدو أن عددًا كبيرًا من الأشخاص سعداء بتجربة الأشياء ، وانتقلوا إلى أشياء أخرى (ولا تكلف نفسك عناء التعليق على هذا الموضوع).

كما حاولت أن أعبر أعلاه ، فإن المشاعر في هذه المرحلة من الاقتراح لا تستند إلى أي تجربة فعلية مع الميزة ؛ انه شعور. تميل المشاعر إلى التغيير بمرور الوقت ، خاصةً عندما يكون لدى المرء فرصة لتجربة الموضوع الذي تدور حوله المشاعر فعليًا ... :-)

@ Goodwine لم يستبعد أحد try() للوصول إلى قيمة الخطأ ؛ على الرغم من أن _if_ هناك حاجة إلى شيء مثل هذا ، فقد يكون من الأفضل أن يكون لديك متغير err محدد مسبقًا كما اقترح rogpeppe (أعتقد).

مرة أخرى ، هذا الاقتراح لا يستبعد أيًا من ذلك. دعنا نذهب إلى هناك إذا اكتشفنا أنه ضروري.

تضمين التغريدة
أعتقد أنك أسأت فهمي تمامًا.
أنا لا أتطلع إلى التصويت / التصويت ضد هذا الاقتراح أو أي اقتراح آخر ، كنت أبحث فقط عن طريقة للحصول على فكرة جيدة عن "هل نعتقد أنه من المنطقي اتخاذ قرار بناءً على معايير صارمة بدلاً من" أحب x "أو" لا تبدو لطيفة ""

مما كتبته - هذا بالضبط ما تعتقده ... لذا يرجى التصويت لتعليقي كما يلي:

"أعتقد أننا يجب أن نضع قائمة بما يهدف هذا الاقتراح إلى تحسينه ، وبناءً على ذلك يمكننا ذلك
أ. تقرر ما إذا كان ذلك مفيدًا بدرجة كافية
ب. تقرر ما إذا كان يبدو أن الاقتراح يحل حقًا ما يهدف إلى حله
ج. (كما أضفت) بذل جهدًا إضافيًا في محاولة لمعرفة ما إذا كان ذلك ممكنًا ...

guybrand من الواضح أنهم مقتنعون بأنه يستحق وضع نماذج أولية في الإصدار التجريبي 1.14 (؟) وجمع التعليقات من المستخدمين العمليين. IOW تم اتخاذ قرار.

أيضًا ، تم تقديم # 32611 لمناقشة on err, <statement>

guybrand اعتذاري. نعم ، أوافق على أننا بحاجة إلى النظر في الخصائص المختلفة لاقتراح ما ، مثل الاختزال المعياري ، هل يحل المشكلة المطروحة ، وما إلى ذلك. لكن الاقتراح أكثر من مجموع أجزائه - في نهاية اليوم نحن بحاجة إلى إلقاء نظرة على الصورة العامة. هذه هندسة ، والهندسة فوضوية: هناك العديد من العوامل التي تلعب دورًا في التصميم ، وحتى إذا كان جزء من التصميم بشكل موضوعي (بناءً على معايير صارمة) غير مرضٍ ، فقد يظل التصميم "الصحيح" بشكل عام. لذلك أنا متردد قليلاً في دعم قرار يعتمد على نوع من التصنيف "المستقل" للجوانب الفردية للاقتراح.

(نأمل أن يكون هذا العنوان أفضل لما قصدته.)

لكن فيما يتعلق بالمعايير ذات الصلة ، أعتقد أن هذا الاقتراح يوضح ما يحاول معالجته. أي أن القائمة التي تشير إليها موجودة بالفعل:

... ، هدفنا هو جعل معالجة الأخطاء أكثر خفة من خلال تقليل كمية الكود المصدري المخصص فقط للتحقق من الأخطاء. نريد أيضًا أن نجعل الأمر أكثر ملاءمة لكتابة رمز معالجة الأخطاء ، لزيادة احتمالية أن يستغرق المبرمجون الوقت الكافي للقيام بذلك. في الوقت نفسه ، نريد إبقاء رمز معالجة الخطأ مرئيًا بشكل واضح في نص البرنامج.

يحدث فقط أنه من أجل زخرفة الخطأ ، نقترح استخدام معلمات النتائج المسماة defer (أو عبارة if القديمة) لأن ذلك لا يحتاج إلى تغيير اللغة - وهو أمر رائع لأن التغييرات اللغوية لها تكاليف خفية هائلة. لقد حصلنا على الكثير من المعلقين الذين يشعرون أن هذا الجزء من التصميم "سيء تمامًا". ومع ذلك ، في هذه المرحلة ، في الصورة العامة ، مع كل ما نعرفه ، نعتقد أنه قد يكون جيدًا بما فيه الكفاية. من ناحية أخرى ، نحتاج إلى تغيير اللغة - دعم اللغة ، بدلاً من ذلك - للتخلص من النموذج المعياري ، و try هو الحد الأدنى من التغيير الذي يمكن أن نتوصل إليه. ومن الواضح أن كل شيء لا يزال واضحًا في الكود.

أود أن أقول إن الأسباب التي أدت إلى وجود الكثير من ردود الفعل والعديد من المقترحات المصغرة هي أن هذه مشكلة يتفق فيها الجميع تقريبًا على أن لغة Go لا تحتاج إلى فعل شيء لتقليل النموذج المعياري لمعالجة الأخطاء ، لكننا لا نفعل ذلك حقًا نتفق على كيفية القيام بذلك.

يتلخص هذا الاقتراح ، في جوهره ، في "ماكرو" مدمج لحالة شائعة جدًا ، لكنها محددة من النموذج المعياري ، تشبه إلى حد كبير الوظيفة المضمنة في append() . لذا ، في حين أنه مفيد لحالة الاستخدام المعينة id err!=nil { return err } ، فهذا أيضًا كل ما يفعله. نظرًا لأنه ليس مفيدًا جدًا في حالات أخرى ، ولا ينطبق حقًا بشكل عام ، أود أن أقول إنه محبط. لدي شعور بأن معظم مبرمجي Go يتوقعون المزيد قليلاً ، وبالتالي تستمر المناقشة في هذا الموضوع.

إنها غير بديهية كوظيفة. لأنه ليس من الممكن في Go الحصول على وظيفة بترتيب الوسائط هذا func(... interface{}, error) .
يتم كتابته أولاً ثم الرقم المتغير لأي نمط موجود في كل مكان في وحدات Go.

كلما اعتقدت ، أحب الاقتراح الحالي ، كما هو.

إذا احتجنا إلى معالجة الأخطاء ، فلدينا دائمًا عبارة if.

مرحبا جميعا. شكرا لك على المناقشة الهادئة والاحترام والبناءة حتى الآن. قضيت بعض الوقت في تدوين الملاحظات ، وفي النهاية شعرت بالإحباط بدرجة كافية لأنني أنشأت برنامجًا لمساعدتي في الحفاظ على وجهة نظر مختلفة لسلسلة التعليقات هذه والتي يجب أن تكون أكثر قابلية للتصفح وكاملة مما يعرضه GitHub. (يتم تحميله أيضًا بشكل أسرع!) راجع https://swtch.com/try.html. سأبقيها محدثة ولكن على دفعات ، وليس دقيقة بدقيقة. (هذه مناقشة تتطلب تفكيرًا دقيقًا ولا يساعدها "وقت الإنترنت".)

لدي بعض الأفكار لأضيفها ، لكن من المحتمل أن ينتظر ذلك حتى يوم الاثنين. شكرا لك مرة أخرى.

@ mishak87 نتناول هذا في الاقتراح المفصل . لاحظ أن لدينا عناصر أخرى مضمنة ( try ، make ، unsafe.Offsetof ، إلخ.) والتي تعتبر "غير منتظمة" - وهذا هو الغرض المضمّن.

rsc ، مفيدة للغاية! إذا كنت لا تزال تراجعها ، فربما تربط المراجع #id المسألة؟ ونمط الخط بلا الرقيق؟

ربما تمت تغطية هذا من قبل ، لذا أعتذر عن إضافة المزيد من الضوضاء ولكني أردت فقط توضيح نقطة حول فكرة المحاولة المبنية مقابل المحاولة ... أخرى.

أعتقد أن تجربة الوظيفة المدمجة يمكن أن تكون محبطة بعض الشيء أثناء التطوير. قد نرغب في بعض الأحيان في إضافة رموز تصحيح الأخطاء أو إضافة المزيد من السياق المحدد للخطأ قبل العودة. على المرء أن يعيد كتابة سطر مثل

user := try(getUser(userID))

ل

user, err := getUser(userID)
if err != nil {  
    // inspect error here
    return err
}

يمكن أن تساعد إضافة عبارة تأجيل ولكنها لا تزال ليست أفضل تجربة عندما تتسبب إحدى الوظائف في حدوث أخطاء متعددة لأنها قد تؤدي إلى كل استدعاء try ().

قد تكون إعادة كتابة استدعاءات try () متعددة متداخلة في نفس الوظيفة أكثر إزعاجًا.

من ناحية أخرى ، فإن إضافة السياق أو رمز التفتيش إلى

user := try getUser(userID)

سيكون بسيطًا مثل إضافة عبارة catch في النهاية متبوعة بالشفرة

user := try getUser(userID) catch {
   // inspect error here
}

ستكون إزالة المعالج أو تعطيله مؤقتًا أمرًا بسيطًا مثل كسر الخط قبل التقاطه والتعليق عليه.

يبدو التبديل بين try() و if err != nil أكثر إزعاجًا من IMO.

ينطبق هذا أيضًا على إضافة سياق الخطأ أو إزالته. يمكن للمرء كتابة try func() أثناء عمل نموذج أولي لشيء ما بسرعة كبيرة ثم إضافة سياق إلى أخطاء محددة حسب الحاجة مع نضوج البرنامج بدلاً من try() باعتباره برنامجًا مدمجًا حيث يتعين على المرء إعادة كتابة خطوط لإضافة سياق أو إضافة رمز فحص إضافي أثناء التصحيح.

أنا متأكد من أن المحاولة () ستكون مفيدة ولكن كما أتخيل استخدامها في عملي اليومي ، لا يسعني إلا أن أتخيل كيف سيكون try ... catch أكثر فائدة بكثير وأقل إزعاجًا عندما ' د تحتاج إلى إضافة / إزالة رمز إضافي خاص ببعض الأخطاء.


أيضًا ، أشعر أن إضافة try() ثم التوصية باستخدام if err != nil لإضافة سياق مشابه جدًا لوجود make() مقابل new() مقابل := مقابل var . هذه الميزات مفيدة في سيناريوهات مختلفة ولكن ألن يكون من الجيد إذا كانت لدينا طرق أقل أو حتى طريقة واحدة لتهيئة المتغيرات؟ بالطبع لا أحد يجبر أي شخص على استخدام المحاولة ويمكن للأشخاص الاستمرار في الاستخدام إذا أخطأ! = لا شيء ولكني أشعر أن هذا سيؤدي إلى تقسيم معالجة الأخطاء في Go تمامًا مثل الطرق المتعددة لتعيين متغيرات جديدة. أعتقد أن أي طريقة تتم إضافتها إلى اللغة يجب أن توفر أيضًا طريقة لإضافة / إزالة معالجات الأخطاء بسهولة بدلاً من إجبار الأشخاص على إعادة كتابة سطور كاملة لإضافة / إزالة معالجات. هذا لا يبدو أنه نتيجة جيدة بالنسبة لي.

آسف مرة أخرى على الضوضاء ولكن أردت أن أشير إليها في حال أراد شخص ما كتابة عرض تفصيلي منفصل لفكرة try ... else .

// ccbrynbellomy

شكرًا ، owais ، على طرح هذا الأمر مرة أخرى - إنها نقطة عادلة (وقد تم بالفعل ذكر مشكلة تصحيح الأخطاء من قبل ). يترك try الباب مفتوحًا للإضافات ، مثل الوسيطة الثانية ، والتي يمكن أن تكون دالة معالج. لكن من الصحيح أن وظيفة try لا تجعل تصحيح الأخطاء أسهل - قد يضطر المرء إلى إعادة كتابة الكود أكثر قليلاً من try - catch أو try - else .

@أويس

يمكن أن تساعد إضافة عبارة تأجيل ولكنها لا تزال ليست أفضل تجربة عندما تتسبب إحدى الوظائف في حدوث أخطاء متعددة لأنها قد تؤدي إلى كل استدعاء try ().

يمكنك دائمًا تضمين مفتاح نوع في الوظيفة المؤجلة والتي ستتعامل (أو لا تعالج) أنواعًا مختلفة من الأخطاء بطريقة مناسبة قبل العودة.

بالنظر إلى المناقشة حتى الآن - وتحديدًا الردود من فريق Go - لدي انطباع قوي بأن الفريق يخطط للمضي قدمًا في الاقتراح المطروح على الطاولة. إذا كانت الإجابة بنعم ، فحينئذٍ تعليق وطلب:

  1. سينتج عن الاقتراح كما هو IMO انخفاض غير مهم في جودة الكود في اتفاقيات إعادة الشراء المتاحة للجمهور. أتوقع أن يسلك العديد من المطورين المسار الأقل مقاومة ، ويستخدمون بفعالية تقنيات معالجة الاستثناءات ويختارون استخدام try() بدلاً من معالجة الأخطاء عند حدوثها. ولكن بالنظر إلى المشاعر السائدة حول هذا الموضوع ، أدركت أن أي عرض الآن سيكون مجرد خوض معركة خاسرة ، لذلك أنا فقط أسجل اعتراضي للأجيال القادمة.

  2. بافتراض أن الفريق يتقدم في الاقتراح كما هو مكتوب حاليًا ، هل يمكنك من فضلك إضافة مفتاح التحويل البرمجي الذي سيعطل try() لأولئك الذين لا يريدون أي كود يتجاهل الأخطاء بهذه الطريقة ويمنع المبرمجين الذين يوظفونهم من استخدامه؟ _ (عبر CI ، بالطبع.) _ شكرًا لك مقدمًا على هذا الاعتبار.

هل يمكنك من فضلك إضافة مفتاح التحويل البرمجي الذي سيعطل المحاولة ()

يجب أن يكون هذا على أداة فحص ، وليس على IMO المترجم ، لكنني أوافق

يجب أن يكون هذا على أداة فحص ، وليس على IMO المترجم ، لكنني أوافق

أنا أطالب صراحةً بخيار مترجم وليس أداة فحص لأنه لا يسمح بالتجميع مثل الخيار. وإلا فسيكون من السهل جدًا _ "نسيان" _ التلاعب أثناء التنمية المحلية.

mikeschinkel ألن يكون من السهل نسيان تشغيل خيار المترجم في هذه الحالة؟

يجب ألا تغير علامات المترجم مواصفات اللغة. هذا أكثر ملاءمة للطبيب البيطري / الوبر

ألن يكون من السهل نسيان تشغيل خيار المترجم في هذه الحالة؟

ليس عند استخدام أدوات مثل GoLand حيث لا توجد طريقة لفرض تشغيل الوبر قبل التجميع.

يجب ألا تغير علامات المترجم مواصفات اللغة.

يغير -nolocalimports المواصفات ، ويحذر -s .

يجب ألا تغير علامات المترجم مواصفات اللغة.

يغير -nolocalimports المواصفات ، ويحذر -s .

لا ، لا يغير المواصفات. لا تظل قواعد اللغة كما هي فحسب ، ولكن المواصفات تنص على وجه التحديد:

يعتمد تفسير ImportPath على التنفيذ ولكنه عادةً ما يكون سلسلة فرعية لاسم الملف الكامل للحزمة المترجمة وقد يكون مرتبطًا بمستودع الحزم المثبتة.

ليس عند استخدام أدوات مثل GoLand حيث لا توجد طريقة لفرض تشغيل الوبر قبل التجميع.

https://github.com/vmware/dispatch/wiki/Configure-GoLand-with-golint

تضمين التغريدة

https://github.com/vmware/dispatch/wiki/Configure-GoLand-with-golint

بالتأكيد هذا موجود ، لكنك تقارن التفاح بالبرتقال. ما تعرضه هو مراقب ملفات يعمل على تغيير الملفات وبما أن GoLand يقوم بحفظ الملفات تلقائيًا ، فهذا يعني أنه يعمل باستمرار مما ينتج عنه ضوضاء أكثر بكثير من الإشارة.

لا يتم دائمًا تكوين النسالة (AFAIK) ولا يمكن تكوينها كشرط مسبق لتشغيل المترجم:

image

لا ، لا يغير المواصفات. لا تظل قواعد اللغة كما هي فحسب ، ولكن المواصفات تنص على وجه التحديد:

أنت تلعب هنا مع الدلالات بدلاً من التركيز على النتيجة. لذلك سأفعل نفس الشيء.

أطلب إضافة خيار برنامج التحويل البرمجي الذي لن يسمح بتجميع الكود مع try() . هذا ليس طلبًا لتغيير مواصفات اللغة ، إنه مجرد طلب لإيقاف المترجم في هذه الحالة الخاصة.

وإذا كان ذلك مفيدًا ، فيمكن تحديث مواصفات اللغة لتقول شيئًا مثل:

تفسير try() يعتمد على التنفيذ ولكنه عادةً ما يؤدي إلى إرجاع عندما يكون المعامل الأخير خطأ ومع ذلك يمكن تنفيذه حتى لا يُسمح به.

حان الوقت لطلب تبديل مترجم أو فحص بيطري بعد أن يهبط النموذج الأولي try() في نصيحة 1.14 (؟). في هذه المرحلة ، ستقدم مشكلة جديدة لها (ونعم أعتقد أنها فكرة جيدة). لقد طُلب منا قصر التعليقات هنا على المدخلات الواقعية حول مستند التصميم الحالي.

مرحبًا ، فقط للإضافة إلى المشكلة برمتها مع إضافة بيانات التصحيح وما شابه ذلك أثناء التطوير.
أعتقد أن فكرة المعلمة الثانية مناسبة لوظيفة try() ، لكن هناك فكرة أخرى فقط للتخلص منها وهي إضافة جملة emit لتكون جزءًا ثانيًا مقابل try() .

على سبيل المثال ، أعتقد أنه عند التطوير ، قد تكون هناك حالة عندما أريد الاتصال بـ fmt لهذه اللحظة لطباعة الخطأ. لذلك يمكنني الانتقال من هذا:

func writeStuff(filename string) (io.ReadCloser, error) {
    f := try(os.Open(filename))
    try(fmt.Fprintf(f, "stuff\n"))

    return f, nil
}

يمكن إعادة كتابتها إلى شيء مثل هذا لتصحيح الأخطاء أو المعالجة العامة أو الخطأ قبل العودة.

func writeStuff(filename string) (io.ReadCloser, error) {
    emit err {
        fmt.Printf("something happened [%v]\n", err.Error())
        return nil, err
    }

    f := try(os.Open(filename))
    try(fmt.Fprintf(f, "stuff\n"))

    return f, nil
}

لذلك انتهيت هنا من وضع اقتراح لكلمة رئيسية جديدة emit والتي يمكن أن تكون عبارة أو بطانة واحدة للرجوع الفوري مثل الوظيفة الأولية try() :

emit return nil, err

ما سيكون الانبعاث هو في الأساس مجرد جملة حيث يمكنك وضع أي منطق ترغب فيه إذا تم تشغيل try() بسبب خطأ لا يساوي الصفر. إمكانية أخرى مع الكلمة الرئيسية emit هي أنك تستطيع الوصول إلى الخطأ هناك مباشرةً إذا أضفت اسمًا متغيرًا بعد الكلمة الرئيسية مثلما فعلت في المثال الأول باستخدامه.

ينشئ هذا الاقتراح القليل من الإسهاب للدالة try() ، لكنني أعتقد أنه على الأقل أكثر وضوحًا حول ما يحدث مع الخطأ. بهذه الطريقة يمكنك تزيين الأخطاء أيضًا دون أن تتكدس جميعها في سطر واحد ويمكنك أن ترى كيف يتم معالجة الأخطاء فورًا عند قراءة الوظيفة.

هذا رد على mikeschinkel ، أنا أضع إجابتي في قالب تفصيلي حتى لا أشغل النقاش كثيرًا. في كلتا الحالتين ، @ networkimprov صحيح أنه يجب جدولة هذه المناقشة حتى بعد تنفيذ هذا الاقتراح (إذا تم ذلك).

تفاصيل حول علامة لتعطيل المحاولة
تضمين التغريدة

لا يتم دائمًا تكوين النسالة (AFAIK) ولا يمكن تكوينها كشرط مسبق لتشغيل المترجم:

أعد تثبيت GoLand فقط لاختبار ذلك. يبدو أن هذا يعمل بشكل جيد ، والفرق الوحيد هو أنه إذا وجد النسالة شيئًا لا تحبه ، فإنه لا يفشل في التجميع. يمكن إصلاح ذلك بسهولة باستخدام برنامج نصي مخصص ، على الرغم من ذلك ، يتم تشغيله golint ويفشل مع رمز خروج غير صفري إذا كان هناك أي إخراج.
image

(تعديل: أصلحت الخطأ الذي كان يحاول إخباري به في الأسفل. كان يعمل بشكل جيد حتى أثناء وجود الخطأ ، ولكن تغيير "Run Kind" إلى دليل أزال الخطأ وعمل بشكل جيد)

سبب آخر لعدم كونه علامة مترجم - يتم تجميع كل كود Go من المصدر. يتضمن المكتبات. هذا يعني أنك إذا كنت تريد تشغيل try من خلال المترجم ، فستقوم بإيقاف تشغيل try لكل واحدة من المكتبات التي تستخدمها أيضًا. انها مجرد فكرة سيئة أن يكون علم مترجم.

أنت تلعب هنا مع الدلالات بدلاً من التركيز على النتيجة.

لا لست كذلك. يجب ألا تغير علامات المترجم مواصفات اللغة. المواصفات مصممة بشكل جيد للغاية ولكي يكون الشيء "Go" ، يجب أن يتبع المواصفات. تعمل إشارات المترجم التي ذكرتها على تغيير سلوك اللغة ، ولكن بغض النظر عن أي شيء ، فإنها تتأكد من أن اللغة لا تزال تتبع المواصفات. هذا جانب مهم من Go. طالما أنك تتبع مواصفات Go ، يجب أن يتم تجميع التعليمات البرمجية الخاصة بك على أي مترجم Go.

أطلب إضافة خيار برنامج التحويل البرمجي الذي لن يسمح بتجميع التعليمات البرمجية باستخدام try (). هذا ليس طلبًا لتغيير مواصفات اللغة ، إنه مجرد طلب لإيقاف المترجم في هذه الحالة الخاصة.

إنه طلب لتغيير المواصفات. هذا الاقتراح في حد ذاته هو طلب لتغيير المواصفات. يتم تضمين الوظائف المضمنة على وجه التحديد في المواصفات. . طلب الحصول على علامة مترجم تزيل try المضمن سيكون بالتالي علامة مترجم من شأنها تغيير مواصفات اللغة التي يتم تجميعها.

ومع ذلك ، أعتقد أن ImportPath يجب أن يكون معياريًا في المواصفات. يمكنني تقديم اقتراح لهذا.

وإذا كان ذلك مفيدًا ، فيمكن تحديث مواصفات اللغة لتقول شيئًا مثل [...]

في حين أن هذا صحيح ، لن ترغب في أن يكون تنفيذ try معتمداً على التنفيذ. لقد تم تصميمه ليكون جزءًا مهمًا من معالجة أخطاء اللغة ، وهو شيء يجب أن يكون هو نفسه في كل مترجم Go.

تضمين التغريدة

_ "في كلتا الحالتين ، @ networkimprov صحيح أنه يجب جدولة هذه المناقشة حتى بعد تنفيذ هذا الاقتراح (إذا تم ذلك)." _

إذن لماذا قررت تجاهل هذا الاقتراح والنشر في هذا الموضوع على أي حال بدلاً من الانتظار لاحقًا؟ لقد ناقشت نقاطك هنا بينما أكدت في نفس الوقت أنه لا ينبغي أن أتحدى نقاطك. مارس عظتك...

بالنظر إلى اختيارك ، سأختار الرد أيضًا ، أيضًا في جزء تفصيلي

هنا:

_ "يمكن إصلاح ذلك بسهولة باستخدام برنامج نصي مخصص ، على الرغم من ذلك ، يقوم بتشغيل golint ويفشل مع رمز خروج غير صفري إذا كان هناك أي إخراج." _

نعم ، مع وجود عدد كافٍ من الترميز يمكن إصلاح أي مشكلة. لكن كلانا يعلم من التجربة أنه كلما كان الحل أكثر تعقيدًا ، قل عدد الأشخاص الذين يرغبون في استخدامه سينتهي بهم الأمر في الواقع إلى استخدامه.

لذلك كنت أطلب صراحة حلاً بسيطًا هنا ، وليس حلاً خاصًا بك.

_ "ستقوم بإيقاف تشغيل المحاولة لكل واحدة من المكتبات التي تستخدمها أيضًا." _

وهذا _صراحة_ سبب طلبها. لأنني أريد التأكد من أن جميع التعليمات البرمجية التي تستخدم هذه _ "الميزة" _ المزعجة لن تشق طريقها إلى الملفات التنفيذية التي نوزعها.

_ "إنه طلب لتغيير المواصفات. هذا الاقتراح بحد ذاته هو طلب لتغيير المواصفات ._"

إنه بالتأكيد ليس تغييرًا في المواصفات. إنه طلب لمحول لتغيير سلوك الأمر build ، وليس تغييرًا في مواصفات اللغة.

إذا طلب شخص ما أن يكون للأمر go مفتاح تبديل لعرض مخرجاته النهائية بلغة الماندرين ، فهذا لا يعد تغييرًا في مواصفات اللغة.

وبالمثل ، إذا رأى go build رمز التبديل هذا ، فسيصدر ببساطة رسالة خطأ ويتوقف عندما يصادف try() . لا حاجة لتغييرات مواصفات اللغة.

_ "تم تصميمه ليكون جزءًا مهمًا من معالجة أخطاء اللغة ، وهو شيء يجب أن يكون هو نفسه في كل مترجم Go." _

سيكون جزءًا إشكاليًا من معالجة أخطاء اللغة وجعلها اختيارية سيسمح لأولئك الذين يريدون تجنب مشاكلها بالتمكن من القيام بذلك.

بدون التبديل ، من المحتمل أن يرى معظم الناس ميزة جديدة ويتبنونها ولا يسألون أنفسهم أبدًا عما إذا كان يجب استخدامها في الواقع.

_ مع التبديل_ - والمقالات التي تشرح الميزة الجديدة التي تشير إلى التبديل - سيتفهم العديد من الأشخاص أن لها إمكانية إشكالية وبالتالي سيسمح لفريق Go بدراسة ما إذا كان تضمينًا جيدًا أم لا من خلال معرفة مقدار الكود العام الذي يتجنب استخدامه مقابل كيفية استخدام الكود العام له. قد يكون ذلك مفيدًا لتصميم Go 3.

_ "لا ، لست كذلك. يجب ألا تغير إشارات المترجم مواصفات اللغة." _

إن القول بأنك لا تلعب دلالات لا يعني أنك لا تلعب دلالات.

بخير. ثم أطلب بدلاً من ذلك أمرًا جديدًا من المستوى الأعلى يسمى _ ​​(شيء مثل) _ build-guard يُستخدم لعدم السماح بالميزات التي بها مشكلات أثناء التجميع ، بدءًا من عدم السماح بـ try() .

بالطبع أفضل نتيجة هي أن الميزة try() قد تم وضعها مع خطة لإعادة النظر في حل المشكلة بطريقة مختلفة عن المستقبل ، وهي الطريقة التي تتفق معها الغالبية العظمى. لكني أخشى أن السفينة قد أبحرت بالفعل على try() لذلك آمل تقليل جانبها السلبي.


لذا الآن ، إذا كنت توافق حقًا مع @ networkimprov ، فاحتفظ بردك حتى وقت لاحق ، كما اقترحوا.

آسف على المقاطعة ، لكن لديّ حقائق أريد الإبلاغ عنها :-)

أنا متأكد من أن فريق Go قد قام بالفعل بتأجيل ، لكنني لم أر أي أرقام ...

$ go test -bench=.
goos: linux
goarch: amd64
BenchmarkAlways2-2      20000000                72.3 ns/op
BenchmarkAlways4-2      20000000                68.1 ns/op
BenchmarkAlways6-2      20000000                68.0 ns/op

BenchmarkNever2-2       100000000               16.5 ns/op
BenchmarkNever4-2       100000000               13.1 ns/op
BenchmarkNever6-2       100000000               13.5 ns/op

مصدر

package deferbench

import (
   "fmt"
   "errors"
   "testing"
)

func Always(iM, iN int) (err error) {
   defer func() {
      if err != nil {
         err = fmt.Errorf("d: %v", err)
      }
   }()
   if iN % iM == 0 {
      return errors.New("e")
   }
   return nil
}

func Never(iM, iN int) (err error) {
   if iN % iM == 0 {
      return fmt.Errorf("r: %v", errors.New("e"))
   }
   return nil
}

func BenchmarkAlways2(iB *testing.B) { for a := 0; a < iB.N; a++ { Always(1e2, a) }}
func BenchmarkAlways4(iB *testing.B) { for a := 0; a < iB.N; a++ { Always(1e4, a) }}
func BenchmarkAlways6(iB *testing.B) { for a := 0; a < iB.N; a++ { Always(1e6, a) }}

func BenchmarkNever2(iB *testing.B) { for a := 0; a < iB.N; a++ { Never(1e2, a) }}
func BenchmarkNever4(iB *testing.B) { for a := 0; a < iB.N; a++ { Never(1e4, a) }}
func BenchmarkNever6(iB *testing.B) { for a := 0; a < iB.N; a++ { Never(1e6, a) }}

تضمين التغريدة

من https://github.com/golang/proposal/blob/master/design/32437-try-builtin.md#efficiency -of-defer (أؤكد بخط عريض)

بشكل مستقل ، ناقش فريق وقت التشغيل والمجمع Go خيارات التنفيذ البديلة ونعتقد أنه يمكننا استخدام استخدامات مؤجلة نموذجية للتعامل مع الأخطاء بكفاءة مثل الكود "اليدوي" الحالي. نأمل أن نجعل هذا التنفيذ السريع للإرجاء متاحًا في Go 1.14 (انظر أيضًا * CL 171758 * وهي خطوة أولى في هذا الاتجاه).

على سبيل المثال ، أصبح "التأجيل" الآن تحسينًا بنسبة 30٪ في أداء go1.13 للاستخدام الشائع ، ويجب أن يكون أسرع وفعالية مثل الوضع غير المؤجل في go 1.14

ربما يمكن لشخص ما نشر أرقام لـ 1.13 و 1.14 CL؟

لا تنجو التحسينات دائمًا من الاتصال بالعدو ... إيه ، النظام البيئي.

1.13 المؤجل سيكون حوالي 30٪ أسرع:

name     old time/op  new time/op  delta
Defer-4  52.2ns ± 5%  36.2ns ± 3%  -30.70%  (p=0.000 n=10+10)

هذا ما حصلت عليه في اختباراتnetworkimprov أعلاه ( 1.12.5 إلى تلميح):

name       old time/op  new time/op  delta
Always2-4  59.8ns ± 1%  47.5ns ± 1%  -20.57%  (p=0.008 n=5+5)
Always4-4  57.9ns ± 2%  43.5ns ± 1%  -24.96%  (p=0.008 n=5+5)
Always6-4  57.6ns ± 2%  44.1ns ± 1%  -23.43%  (p=0.008 n=5+5)
Never2-4   13.7ns ± 8%   3.8ns ± 4%  -72.27%  (p=0.008 n=5+5)
Never4-4   10.5ns ± 6%   1.3ns ± 2%  -87.76%  (p=0.008 n=5+5)
Never6-4   10.8ns ± 6%   1.2ns ± 1%  -88.46%  (p=0.008 n=5+5)

(لست متأكدًا من سبب عدم سرعة أي منها أبدًا. ربما تضمين التغييرات؟)

لم يتم تنفيذ التحسينات الخاصة بـ defers لـ 1.14 حتى الآن ، لذلك لا نعرف ماذا سيكون الأداء. لكننا نعتقد أننا يجب أن نقترب من أداء استدعاء دالة عادي.

إذن لماذا قررت تجاهل هذا الاقتراح والنشر في هذا الموضوع على أي حال بدلاً من الانتظار لاحقًا؟

تم تحرير جزء التفاصيل لاحقًا ، بعد أن قرأت تعليق networkimprov . أنا آسف لجعل الأمر يبدو وكأنني قد فهمت ما قاله وتجاهله. لقد أنهيت المناقشة بعد هذا البيان ، وأردت أن أشرح نفسي لأنك سألتني عن سبب نشر التعليق.


فيما يتعلق بالتحسينات التي يجب تأجيلها ، أنا متحمس لها. لقد ساعدوا هذا الاقتراح قليلاً ، مما يجعل defer HandleErrorf(...) أقل ثقلاً. ما زلت لا أحب فكرة إساءة استخدام المعلمات المحددة حتى تنجح هذه الحيلة. ما المقدار المتوقع لتسريع 1.14؟ هل يجب أن يجروا بسرعات مماثلة؟

griesemer أحد المجالات التي قد تستحق التوسع أكثر قليلاً هي كيفية عمل التحولات في عالم try ، ربما يتضمن:

  • تكلفة الانتقال بين أنماط زخرفة الخطأ.
  • فئات الأخطاء المحتملة التي قد تنتج عند الانتقال بين الأنماط.
  • ما هي فئات الأخطاء التي ستكون (أ) يتم اكتشافها فورًا بواسطة خطأ مترجم ، مقابل (ب) تم اكتشافها بواسطة vet أو staticcheck أو ما شابه ، مقابل (c) قد يؤدي إلى خطأ ذلك قد لا يتم ملاحظتها أو قد يلزم اكتشافها من خلال الاختبار.
  • الدرجة التي يمكن للأدوات أن تخفف من التكلفة وفرصة الخطأ عند الانتقال بين الأنماط ، وعلى وجه الخصوص ، ما إذا كان يمكن أو لا ينبغي أن يكون لـ gopls (أو أداة أخرى) دور في أتمتة انتقالات نمط الزخرفة الشائعة.

مراحل الزخرفة الخطأ

هذا ليس شاملاً ، ولكن مجموعة تمثيلية من المراحل يمكن أن تكون شيئًا مثل:

0. لا خطأ في الزخرفة (على سبيل المثال ، استخدام try بدون أي زخرفة).
1. زخرفة خطأ موحدة (على سبيل المثال ، استخدام try + defer للزينة الموحدة).
2. تحتوي نقاط الخروج N-1 على زخرفة خطأ موحدة ، ولكن نقطة خروج واحدة لها زخرفة مختلفة (على سبيل المثال ، ربما زخرفة خطأ تفصيلية دائمة في مكان واحد فقط ، أو ربما سجل تصحيح مؤقت ، وما إلى ذلك).
3. كل نقاط الخروج لها زخرفة خطأ فريدة ، أو شيء فريد من نوعه.

لن يكون لأي وظيفة معينة تقدم صارم خلال تلك المراحل ، لذلك ربما تكون كلمة "مراحل" هي الكلمة الخاطئة ، لكن بعض الوظائف ستنتقل من نمط زخرفة إلى آخر ، وقد يكون من المفيد أن تكون أكثر وضوحًا بشأن ماهية تلك التحولات هي مثل متى أو إذا حدثت.

يبدو أن المرحلتين 0 والمرحلة 1 هما نقطتا حلوة للاقتراح الحالي ، كما أنهما حالات استخدام شائعة إلى حد ما. يبدو أن المرحلة الانتقالية من 0 إلى> 1 واضحة ومباشرة. إذا كنت تستخدم try بدون أي زخرفة في المرحلة 0 ، يمكنك إضافة شيء مثل defer fmt.HandleErrorf(&err, "foo failed with %s", arg1) . قد تحتاج في هذه اللحظة أيضًا إلى تقديم معلمات إرجاع مسماة ضمن الاقتراح كما هو مكتوب في البداية. ومع ذلك ، إذا كان الاقتراح يتبنى أحد الاقتراحات على غرار متغير مضمن محدد مسبقًا وهو اسم مستعار لمعامل نتيجة الخطأ النهائي ، فقد تكون تكلفة ومخاطر الخطأ هنا صغيرة؟

من ناحية أخرى ، يبدو الانتقال للمرحلة 1> 2 محرجًا (أو "مزعجًا" كما قال البعض الآخر) إذا كانت المرحلة 1 عبارة عن زخرفة خطأ موحدة مع defer . لإضافة جزء معين من الزخرفة عند نقطة خروج واحدة ، ستحتاج أولاً إلى إزالة defer (لتجنب الزخرفة المزدوجة) ، ثم يبدو أن المرء سيحتاج إلى زيارة جميع نقاط العودة إلى desugar try يستخدم if ، حيث يتم تزيين N-1 من الأخطاء بنفس الطريقة وتزيين 1 بشكل مختلف.

تبدو المرحلة الانتقالية من 1 إلى 3 أيضًا محرجة إذا تم إجراؤها يدويًا.

أخطاء عند الانتقال بين أنماط الزخرفة

تتضمن بعض الأخطاء التي قد تحدث كجزء من عملية إزالة الأخطاء اليدوية التظليل العرضي لمتغير ، أو تغيير كيفية تأثر معلمة الإرجاع المسماة ، وما إلى ذلك ، على سبيل المثال ، إذا نظرت إلى المثال الأول والأكبر في قسم "الأمثلة" في محاولة الاقتراح ، وظيفة CopyFile لها 4 try الاستخدامات ، بما في ذلك في هذا القسم:

        w := try(os.Create(dst))
        defer func() {
                w.Close()
                if err != nil {
                        os.Remove(dst) // only if a “try” fails
                }
        }()

إذا قام شخص ما بإلغاء تحديد يدوي "واضح" بقيمة w := try(os.Create(dst)) ، فيمكن توسيع هذا السطر إلى:

        w, err := os.Create(dst)
        if err != nil {
            // do something here
            return err
        }

يبدو هذا جيدًا للوهلة الأولى ، ولكن اعتمادًا على الكتلة التي يوجد بها هذا التغيير ، قد يؤدي ذلك أيضًا إلى تظليل معلمة الإرجاع المسماة err بشكل عرضي وكسر معالجة الخطأ في defer اللاحقة.

أتمتة الانتقال بين أنماط الديكور

للمساعدة في تحديد تكلفة الوقت ومخاطر الأخطاء ، ربما يكون لدى gopls (أو أداة مساعدة أخرى) نوع من الأوامر لإلغاء تحديد try ، أو أمر desugar لجميع الاستخدامات try في وظيفة معينة يمكن أن تكون خالية من الأخطاء بنسبة 100٪ من الوقت. قد يتمثل أحد الأساليب في أي أوامر gopls تركز فقط على إزالة واستبدال try ، ولكن ربما يمكن لأمر مختلف إلغاء استخدامات try مع تحويل الحالات الشائعة على الأقل للأشياء مثل defer fmt.HandleErrorf(&err, "copy %s %s", src, dst) في الجزء العلوي من الوظيفة إلى الكود المكافئ في كل موقع من مواقع try السابقة (مما سيساعد عند الانتقال من المرحلة 1 -> 2 أو المرحلة 1 -> 3). هذه ليست فكرة كاملة ، ولكن ربما تستحق المزيد من التفكير فيما هو ممكن أو مرغوب فيه أو تحديث الاقتراح بالتفكير الحالي.

نتائج اصطلاحية؟

تعليق ذو صلة هو أنه ليس من الواضح على الفور هو مدى تكرار حدوث خطأ برمجي للتحول الحر لـ try يبدو وكأنه كود Go اصطلاحي عادي. تكييف أحد الأمثلة من الاقتراح ، إذا أردت على سبيل المثال إلغاء الأمر:

x1, x2, x3 = try(f())

في بعض الحالات ، يمكن أن ينتهي التحويل البرمجي الذي يحافظ على السلوك بشيء مثل:

t1, t2, t3, te := f()  // visible temporaries
if te != nil {
        return x1, x2, x3, te
}
x1, x2, x3 = t1, t2, t3

قد يكون هذا الشكل الدقيق نادرًا ، ويبدو أن نتائج المحرر أو IDE الذي يقوم بإزالة الصغائر المبرمجة يمكن أن ينتهي بها الأمر في كثير من الأحيان إلى الظهور بمزيد من الاصطلاحات ، ولكن سيكون من المثير للاهتمام معرفة مدى صحة ذلك ، بما في ذلك في مواجهة معامِلات الإرجاع المسماة التي من المحتمل أن تصبح أكثر شيوعًا ، مع مراعاة التظليل ، := مقابل = ، الاستخدامات الأخرى لـ err في نفس الوظيفة ، إلخ.

يتحدث الاقتراح عن الاختلافات السلوكية المحتملة بين if و try بسبب معلمات النتائج المسماة ، ولكن في هذا القسم المحدد ، يبدو أنه يتحدث بشكل أساسي عن الانتقال من if إلى try (في القسم الذي يختتم _ "بالرغم من أن هذا اختلاف طفيف ، نعتقد أن مثل هذه الحالات نادرة. إذا كان السلوك الحالي متوقعًا ، احتفظ بعبارة if." _). في المقابل ، قد تكون هناك أخطاء محتملة مختلفة تستحق التفصيل عند الانتقال من try إلى if مع الحفاظ على نفس السلوك.


على أي حال ، آسف للتعليق الطويل ، ولكن يبدو أن الخوف من ارتفاع تكاليف الانتقال بين الأنماط هو الكامن وراء بعض المخاوف التي تم التعبير عنها في بعض التعليقات الأخرى المنشورة هنا ، ومن ثم فإن الاقتراح بأن يكون أكثر وضوحًا بشأن تكاليف النقل هذه و التخفيفات المحتملة.

thepudds I love you يسلط الضوء على التكاليف والأخطاء المحتملة المرتبطة بكيفية تأثير ميزات اللغة إما بشكل إيجابي أو سلبي على إعادة البناء. إنه ليس موضوعًا تتم مناقشته كثيرًا ، ولكنه موضوع يمكن أن يكون له تأثير كبير في المصب.

تبدو المرحلة 1 -> 2 محرجة إذا كانت المرحلة 1 عبارة عن زخرفة خطأ موحدة مع إرجاء. لإضافة جزء معين من الزخرفة عند نقطة خروج واحدة ، ستحتاج أولاً إلى إزالة المؤجل (لتجنب الزخرفة المزدوجة) ، ثم يبدو أن المرء سيحتاج إلى زيارة جميع نقاط الإرجاع لإلغاء استخدام عبارات if ، مع N يتم تزيين -1 من الأخطاء بنفس الطريقة و 1 يتم تزيينها بشكل مختلف.

هذا هو المكان الذي يتألق فيه استخدام break بدلاً من return مع 1.12. استخدمه في كتلة for range once { ... } حيث once = "1" لترسيم تسلسل الكود الذي قد ترغب في الخروج منه ، ثم إذا كنت بحاجة إلى زخرفة خطأ واحد فقط ، فأنت تفعل ذلك عند نقطة break . وإذا كنت بحاجة إلى تزيين جميع الأخطاء ، فعليك القيام بذلك قبل return الوحيد في نهاية الطريقة.

والسبب في كونه نمطًا جيدًا هو أنه مرن للمتطلبات المتغيرة ؛ نادراً ما تضطر إلى كسر كود العمل لتنفيذ المتطلبات الجديدة. وهو نهج IMO أنظف وأكثر وضوحًا من القفز مرة أخرى إلى بداية الطريقة قبل القفز منها.

fwiw

تظهر نتائج @ randall77 للمعيار الخاص بي 40 + ns لكل مكالمة لكل من 1.12 والإكرامية. هذا يعني أن التأجيل يمكن أن يمنع التحسينات ، مما يؤدي إلى تحسينات لتأجيل النقاش في بعض الحالات.

networkimprov Defer يمنع حاليًا التحسينات ، وهذا جزء مما نرغب في إصلاحه. على سبيل المثال ، سيكون من الجيد تضمين جسم وظيفة المؤجل تمامًا كما نضمّن المكالمات العادية.

لا أرى كيف ستكون أي تحسينات نجريها موضع نقاش. من أين يأتي هذا التأكيد؟

من أين يأتي هذا التأكيد؟

لم يتغير 40 + ns لكل مكالمة لوظيفة مع تأجيل التفاف الخطأ.

التغييرات في 1.13 هي جزء من تحسين التأجيل. هناك تحسينات أخرى مخطط لها. تمت تغطية هذا في مستند التصميم ، وفي جزء مستند التصميم المقتبس في مرحلة ما أعلاه.

رد swtch.com/try.html و https://github.com/golang/go/issues/32437#issuecomment -502192315:

rsc ، مفيدة للغاية! إذا كنت لا تزال تراجعها ، فربما تربط المراجع #id المسألة؟ ونمط الخط بلا الرقيق؟

هذه الصفحة تدور حول المحتوى. لا تركز على تفاصيل العرض. أنا أستخدم إخراج blackfriday على تخفيض الإدخال دون تغيير (لذلك لا توجد روابط #id خاصة بـ GitHub) ، وأنا سعيد بخط serif.

إعادة التعطيل / الفحص حاول :

أنا آسف ، ولكن لن تكون هناك خيارات مترجم لتعطيل ميزات Go المحددة ، ولن تكون هناك فحوصات بيطرية تنص على عدم استخدام هذه الميزات. إذا كانت الميزة سيئة بما يكفي لتعطيلها أو فحصها ، فلن نقوم بوضعها. وبالعكس ، إذا كانت الميزة موجودة ، فلا بأس من استخدامها. هناك لغة Go واحدة ، وليست لغة مختلفة لكل مطور بناءً على اختيارهم لأعلام المترجم.

mikeschinkel ، لقد وصفت مرتين الآن بخصوص هذه المشكلة استخدام المحاولة كأخطاء _ignoring_.
في 7 حزيران (يونيو) كتبت تحت العنوان "تسهيل تجاهل المطورين للأخطاء":

هذا تكرار لما لدى الآخرين من تعليقات ، ولكن ما يقدم try() بشكل أساسي هو مماثل من عدة نواحٍ لتبني ببساطة ما يلي كرمز اصطلاحي ، وهذا هو الكود الذي لن يجد طريقه أبدًا إلى أي رمز ذاتي - احترام سفن التطوير:

f, _ := os.Open(filename)

أعلم أنه يمكنني أن أكون أفضل في الكود الخاص بي ، لكنني أعرف أيضًا أن الكثير منا يعتمد على سخاء مطوري Go الآخرين الذين ينشرون بعض الحزم المفيدة للغاية ، ولكن مما رأيته في _ "رمز الأشخاص الآخرين (tm)" _ غالبًا ما يتم تجاهل أفضل الممارسات في معالجة الأخطاء.

على محمل الجد ، هل نريد حقًا تسهيل الأمر على المطورين لتجاهل الأخطاء والسماح لهم بالتعامل مع GitHub بحزم غير قوية؟

ثم في 14 حزيران (يونيو) أشرت مرة أخرى إلى استخدام try كـ "رمز يتجاهل الأخطاء بهذه الطريقة".

لولا مقتطف الشفرة f, _ := os.Open(filename) ، أعتقد أنك كنت تبالغ ببساطة من خلال وصف "التحقق من وجود خطأ وإعادته" على أنه "تجاهل" خطأ. لكن مقتطف الشفرة ، جنبًا إلى جنب مع العديد من الأسئلة التي تمت الإجابة عنها بالفعل في مستند الاقتراح أو في مواصفات اللغة تجعلني أتساءل عما إذا كنا نتحدث عن نفس الدلالات بعد كل شيء. لذلك فقط لتوضيح الأمر والإجابة على أسئلتك:

عند دراسة كود الاقتراح أجد أن السلوك غير واضح ومن الصعب نوعًا ما التفكير فيه.

عندما أرى try() يلف تعبيرًا ، ماذا سيحدث إذا تم إرجاع خطأ؟

عندما ترى try(f()) ، إذا أرجع f() خطأ ، فإن try سيتوقف عن تنفيذ الرمز ويعيد هذا الخطأ من الوظيفة التي في جسمها try يظهر

هل سيتم تجاهل الخطأ فقط؟

لا ، لا يتم تجاهل الخطأ أبدًا. يتم إرجاعه ، مثل استخدام بيان الإرجاع. يحب:

{ err := f(); if err != nil { return err } }

أم ستنتقل إلى أول أو أحدث defer ،

الدلالات هي نفسها مثل استخدام تعليمة العودة.

تعمل الدالات المؤجلة " بترتيب عكسي تم تأجيلها ".

وإذا كان الأمر كذلك ، فسيقوم تلقائيًا بتعيين متغير باسم err داخل الإغلاق ، أو سيمرره كمعامل _ (لا أرى معاملًا؟) _.

الدلالات هي نفسها مثل استخدام تعليمة العودة.

إذا كنت بحاجة إلى الإشارة إلى معلمة نتيجة في هيئة دالة مؤجلة ، فيمكنك تسميتها. راجع المثال result في https://golang.org/ref/spec#Defer_statements.

وإذا لم يكن اسم خطأ تلقائي ، فكيف يمكنني تسميته؟ وهل يعني ذلك أنه لا يمكنني التصريح عن متغير err الخاص بي في وظيفتي ، لتجنب التعارضات؟

الدلالات هي نفسها مثل استخدام تعليمة العودة.

يتم تعيين عبارة الإرجاع دائمًا إلى نتائج الوظيفة الفعلية ، حتى إذا كانت النتيجة غير مسماة ، وحتى إذا تم تسمية النتيجة ولكنها مظللة.

وهل ستسمي كل defer s؟ بترتيب عكسي أم ترتيب عادي؟

الدلالات هي نفسها مثل استخدام تعليمة العودة.

تعمل الدالات المؤجلة " بترتيب عكسي تم تأجيلها ". (الترتيب العكسي هو أمر عادي.)

أم أنها ستعود من كل من الإغلاق و func حيث تم إرجاع الخطأ؟ _ (شيء لم أكن لأفكر فيه أبدًا إذا لم أقرأ هنا الكلمات التي تشير إلى ذلك.) _

لا أعرف ماذا يعني هذا ولكن ربما الإجابة هي لا. أود أن أشجع التركيز على نص الاقتراح والمواصفات وليس على التعليقات الأخرى هنا حول ما قد يعنيه هذا النص أو لا يعنيه.

بعد قراءة الاقتراح وجميع التعليقات حتى الآن ما زلت بصراحة لا أعرف إجابات الأسئلة أعلاه. هل هذا هو نوع الميزة التي نريد أن نضيفها إلى لغة يدافع عنها المدافعون عن أنها _ "كابتن واضح؟" _

بشكل عام ، نحن نهدف إلى لغة بسيطة وسهلة الفهم. أنا آسف لأنك كان لديك الكثير من الأسئلة. لكن هذا الاقتراح يعيد استخدام أكبر قدر ممكن من اللغة الحالية (على وجه الخصوص ، المؤجل) ، لذلك يجب أن يكون هناك القليل جدًا من التفاصيل الإضافية التي يجب تعلمها. بمجرد أن تعرف ذلك

x, y := try(f())

يعني

tmp1, tmp2, tmpE := f()
if tmpE != nil {
   return ..., tmpE
}
x, y := tmp1, tmp2

يجب أن يتبع كل شيء آخر تقريبًا الآثار المترتبة على هذا التعريف.

هذا ليس "تجاهل" الأخطاء. يتجاهل الخطأ عندما تكتب:

c, _ := net.Dial("tcp", "127.0.0.1:1234")
io.Copy(os.Stdout, c)

ويحدث الذعر في الكود بسبب فشل net.Dial وتجاهل الخطأ ، c is nil واستدعاء io.Copy لأخطاء c.Read. في المقابل ، يتحقق هذا الرمز ويعيد الخطأ:

 c := try(net.Dial("tcp", "127.0.0.1:1234"))
 io.Copy(os.Stdout, c)

للإجابة على سؤالك حول ما إذا كنا نريد تشجيع الأخير على السابق: نعم.

@ damienfamed75 يبدو بيانك المقترح emit بشكل أساسي مماثل لبيان handle الخاص بمسودة التصميم . السبب الرئيسي للتخلي عن إعلان handle هو تداخله مع defer . ليس من الواضح بالنسبة لي لماذا لا يمكن للمرء فقط استخدام defer للحصول على نفس التأثير الذي يحققه emit .

dominikh سأل :

سوف يبدأ acme تسليط الضوء على المحاولة؟

الكثير عن اقتراح المحاولة لم يحسم بعد ، في الهواء ، غير معروف.

لكن هذا السؤال يمكنني أن أجيب عليه بشكل قاطع: لا.

rsc

شكرا لردكم.

_ "مرتين الآن بخصوص هذه المسألة لقد وصفت استخدام المحاولة بأنه تجاهل للأخطاء." _

نعم ، كنت أعلق باستخدام وجهة نظري ولست صحيحًا من الناحية الفنية.

ما قصدته هو _ "السماح بمرور الأخطاء دون التزيين." _ بالنسبة لي ، هذا _ "تجاهل" _ - يشبه إلى حد كبير كيف يستخدم الأشخاص معالجة الاستثناءات _ "يتجاهل" _ الأخطاء - ولكن يمكنني بالتأكيد أن أرى كيف يمكن للآخرين أرى صياغتي على أنها ليست صحيحة من الناحية الفنية.

_ "عندما ترى try(f()) ، إذا أرجع f() خطأ ، فستوقف المحاولة تنفيذ الرمز وستعيد هذا الخطأ من الوظيفة التي تظهر المحاولة في جسمها." _

كان هذا إجابة على سؤال من تعليقي منذ فترة ، لكنني الآن اكتشفت ذلك.

وينتهي به الأمر بفعل شيئين يحزنني. الأسباب:

  1. ستجعل المسار الأقل مقاومة لتجنب أخطاء التزيين - مما يشجع الكثير من المطورين على فعل ذلك - وسوف ينشر الكثيرون هذا الرمز للآخرين لاستخدامه مما يؤدي إلى المزيد من التعليمات البرمجية منخفضة الجودة المتاحة للجمهور مع معالجة أخطاء / تقارير أخطاء أقل قوة .

  2. بالنسبة لأولئك مثلي الذين يستخدمون break و continue لمعالجة الأخطاء بدلاً من return - نمط أكثر مرونة مع المتطلبات المتغيرة - لن نتمكن حتى من استخدامه try() ، حتى في حالة عدم وجود سبب حقيقي للتعليق على الخطأ.

_ "أم أنه سيعود من كل من الإغلاق والوظيفة حيث تم إرجاع الخطأ؟ (شيء لم أكن لأفكر فيه أبدًا إذا لم أقرأ هنا الكلمات التي تشير إلى ذلك.)" _

_ "لا أعرف ما يعنيه هذا ولكن ربما تكون الإجابة لا. أود أن أشجع التركيز على نص الاقتراح والمواصفات وليس على التعليقات الأخرى هنا حول ما قد يعنيه هذا النص أو لا يعنيه." _

مرة أخرى ، كان هذا السؤال منذ أكثر من أسبوع ، لذا فقد فهمت بشكل أفضل الآن.

للتوضيح ، للأجيال القادمة ، defer لديه إغلاق ، أليس كذلك؟ إذا عدت من هذا الإغلاق - ما لم أسيء الفهم - فلن يعود فقط من الإغلاق ولكن أيضًا سيعود من func حيث حدث الخطأ ، أليس كذلك؟ _ (لا حاجة للرد إذا كانت الإجابة بنعم.) _

func example() {
    defer func(err) {
       return err // returns from both defer and example()
    }
    try(SomethingThatReturnsAnError)    
} 

راجع للشغل ، ما أفهمه هو السبب في try() لأن المطورين اشتكوا من النموذج المعياري. أجد ذلك محزنًا أيضًا لأنني أعتقد أن شرط قبول الأخطاء المرتجعة التي تؤدي إلى هذا النموذج المعياري هو ما يساعد في جعل تطبيقات Go أكثر قوة من العديد من اللغات الأخرى.

أنا شخصياً أفضل أن أراك تجعل من الصعب عدم تزيين الأخطاء بدلاً من تسهيل تجاهل تزيينها. لكني أقر بأنني على ما يبدو أقلية في هذا الشأن.


راجع للشغل ، اقترح بعض الأشخاص بناء جملة مثل ما يلي _ (لقد أضفت .Extend() افتراضيًا لإبقاء الأمثلة موجزة): _

f := try os.Open(filename) else err {
    err.Extend("Cannot open file %s",filename)
    //break, continue or return err   
}

أو

try f := os.Open(filename) else err {
    err.Extend("Cannot open file %s",filename)
    //break, continue or return err    
}

ثم يدعي الآخرون أنه لا يحفظ بالفعل أي أحرف فوق هذا:

f,err := os.Open(filename)
if err != nil {
    err.Extend("Cannot open file %s",filename)
    //break, continue or return err    
}

لكن الشيء الوحيد الذي يفتقده النقد هو أنه ينتقل من 5 خطوط إلى 4 خطوط ، مما يقلل من المساحة الرأسية وهذا يبدو مهمًا ، خاصة عندما تحتاج إلى العديد من هذه التركيبات في func .

والأفضل من ذلك أن يكون شيئًا من هذا القبيل والذي سيقضي على 40٪ من المساحة الرأسية _ (على الرغم من التعليقات حول الكلمات الرئيسية التي أشك في أن هذا سيؤخذ في الاعتبار): _

try f := os.Open(filename) 
    else err().Extend("Cannot open file %s",filename)
    end //break, continue or return err    

#fwiw


على أي حال ، كما قلت سابقًا ، أعتقد أن السفينة أبحرت لذا سأتعلم فقط قبولها.

الأهداف

تساءلت بعض التعليقات هنا عما نحاول فعله بالاقتراح. للتذكير ، فإن بيان مشكلة التعامل مع الخطأ الذي نشرناه في أغسطس الماضي ينص في قسم "الأهداف" :

"بالنسبة إلى Go 2 ، نود أن نجعل عمليات التحقق من الأخطاء أكثر خفة ، مما يقلل من مقدار نص برنامج Go المخصص لفحص الأخطاء. نريد أيضًا أن نجعل الأمر أكثر ملاءمة لكتابة معالجة الأخطاء ، مما يزيد من احتمالية أن يستغرق المبرمجون الوقت الكافي للقيام بذلك.

يجب أن تظل عمليات التحقق من الأخطاء ومعالجة الأخطاء صريحة ، مما يعني أنها مرئية في نص البرنامج. لا نريد تكرار أخطاء معالجة الاستثناءات.

يجب أن تستمر الشفرة الحالية في العمل وأن تظل صالحة كما هي اليوم. يجب أن تتفاعل أي تغييرات مع التعليمات البرمجية الموجودة ".

لمزيد من المعلومات حول "مخاطر معالجة الاستثناءات" ، راجع المناقشة في قسم "المشكلة" الأطول. على وجه الخصوص ، يجب إرفاق عمليات التحقق من الأخطاء بوضوح بما يتم التحقق منه.

mikeschinkel ،

للتوضيح ، للأجيال القادمة ، defer لديه إغلاق ، أليس كذلك؟ إذا عدت من هذا الإغلاق - ما لم أسيء الفهم - فلن يعود فقط من الإغلاق ولكن أيضًا سيعود من func حيث حدث الخطأ ، أليس كذلك؟ _ (لا حاجة للرد إذا كانت الإجابة بنعم.) _

لا. لا يتعلق الأمر بمعالجة الأخطاء ولكن حول الوظائف المؤجلة. هم ليسوا دائما مغلقة. على سبيل المثال ، النمط الشائع هو:

func (d *Data) Op() int {
    d.mu.Lock()
    defer d.mu.Unlock()

     ... code to implement Op ...
}

أي عائد من d.Op يقوم بتشغيل استدعاء إلغاء القفل المؤجل بعد بيان الإرجاع ولكن قبل نقل الكود إلى مستدعي d.Op. لا شيء يتم القيام به داخل d.mu.Unlock يؤثر على القيمة المرجعة لـ d.Op. بيان عودة في d.mu.Unlock يعود من فتح. وهي لا تعود من تلقاء نفسها من د. بالطبع ، مرة واحدة يعود d.mu.Unlock ، وكذلك يفعل d.Op ، ولكن ليس بشكل مباشر بسبب d.mu.Unlock. إنها نقطة دقيقة لكنها مهمة.

الوصول إلى مثالك:

func example() {
    defer func(err) {
       return err // returns from both defer and example()
    }
    try(SomethingThatReturnsAnError)    
} 

على الأقل كما هو مكتوب ، هذا برنامج غير صالح. أنا لا أحاول أن أكون متحذلقًا هنا - التفاصيل مهمة. هنا برنامج صالح:

func example() (err error) {
    defer func() {
        if err != nil {
            println("FAILED:", err.Error())
        }
    }()

    try(funcReturningError())
    return nil
}

يتم تجاهل أي نتيجة من استدعاء دالة مؤجل عند تنفيذ الاستدعاء ، لذلك في الحالة التي يكون فيها ما تم تأجيله هو استدعاء للإغلاق ، فليس من المنطقي على الإطلاق كتابة الإغلاق لإرجاع قيمة. لذلك إذا كنت ستكتب return err داخل نص الإغلاق ، سيخبرك المترجم "الكثير من الوسائط لإرجاعها" .

لذا ، لا ، كتابة return err لا يعود من كل من الدالة المؤجلة والدالة الخارجية بأي معنى حقيقي ، وفي الاستخدام التقليدي لا يمكن حتى كتابة كود يبدو أنه يفعل ذلك.

تم نشر العديد من المقترحات المضادة التي تم نشرها في هذه المشكلة والتي تقترح إنشاءات أخرى أكثر قدرة على معالجة الأخطاء وتكرار تراكيب اللغة الحالية ، مثل عبارة if. (أو أنها تتعارض مع الهدف المتمثل في "جعل عمليات التحقق من الأخطاء أكثر خفة ، وتقليل مقدار نص برنامج Go للتحقق من الأخطاء." أو كليهما.)

بشكل عام ، لدى Go بالفعل بنية معالجة الأخطاء قادرة تمامًا: اللغة بأكملها ، خاصةً إذا كانت العبارات. DavexPro كان محقًا في الرجوع إلى إدخال مدونة Go. الأخطاء هي قيم . لا نحتاج إلى تصميم لغة فرعية منفصلة بالكامل معنية بالأخطاء ، ولا ينبغي لنا ذلك. أعتقد أن الفكرة الرئيسية خلال نصف العام الماضي أو نحو ذلك كانت إزالة "التعامل" من اقتراح "الفحص / التعامل" لصالح إعادة استخدام اللغة التي لدينا بالفعل ، بما في ذلك الرجوع إلى العبارات إذا كان ذلك مناسبًا. هذه الملاحظة حول القيام بأقل قدر ممكن تلغي من الاعتبار معظم الأفكار المتعلقة بمزيد من المعلمات للبنية الجديدة.

بفضل brynbellomy على تعليقاته العديدة الجيدة ، سأستخدم تجربته الأخرى كمثال توضيحي. نعم ، قد نكتب:

func doSomething() (int, error) {
    // Inline error handler
    a, b := try SomeFunc() else err {
        return 0, errors.Wrap(err, "error in doSomething:")
    }

    // Named error handlers
    handler logAndContinue err {
        log.Errorf("non-critical error: %v", err)
    }
    handler annotateAndReturn err {
        return 0, errors.Wrap(err, "error in doSomething:")
    }

    c, d := try SomeFunc() else logAndContinue
    e, f := try OtherFunc() else annotateAndReturn

    // ...

    return 123, nil
}

لكن كل الأشياء التي تم اعتبارها قد لا تكون تحسنًا كبيرًا في استخدام تراكيب اللغة الحالية:

func doSomething() (int, error) {
    a, b, err := SomeFunc()
    if err != nil {
        return 0, errors.Wrap(err, "error in doSomething:")
    }

    // Named error handlers
    logAndContinue := func(err error) {
        log.Errorf("non-critical error: %v", err)
    }
    annotate:= func(err error) (int, error) {
        return 0, errors.Wrap(err, "error in doSomething:")
    }

    c, d, err := SomeFunc()
    if err != nil {
        logAndContinue(err)
    }
    e, f, err := SomeFunc()
    if err != nil {
        return annotate(err)
    }

    // ...

    return 123, nil
}

أي أن الاستمرار في الاعتماد على اللغة الحالية لكتابة منطق معالجة الأخطاء يبدو أفضل من إنشاء جملة جديدة ، سواء كانت try-else ، أو try-goto ، أو try-arrow ، أو أي شيء آخر.

هذا هو السبب في أن try مقصور على الدلالات البسيطة if err != nil { return ..., err } ولا شيء أكثر من ذلك: قم بتقصير النمط الشائع ولكن لا تحاول إعادة اختراع كل تدفق تحكم ممكن. عندما تكون عبارة if أو الوظيفة المساعدة مناسبة ، نتوقع تمامًا أن يستمر الناس في استخدامها.

rsc شكرا للتوضيح.

صحيح ، لم أفهم التفاصيل بشكل صحيح. أعتقد أنني لا أستخدم defer في كثير من الأحيان بما يكفي لتذكر تركيبها.

_ (FWIW أجد استخدام defer لأي شيء أكثر تعقيدًا من إغلاق مقبض الملف أقل وضوحًا بسبب القفز إلى الخلف في func قبل العودة. لذلك دائمًا ضع هذا الرمز في نهاية func بعد for range once{...} خطأ معالجة رمز break s خارج.) _

اقتراح gofmt كل محاولة استدعاء في عدة أسطر يتعارض بشكل مباشر مع الهدف المتمثل في "جعل عمليات التحقق من الأخطاء أكثر خفة ، وتقليل مقدار نص برنامج Go للتحقق من الأخطاء."

اقتراح إجراء اختبار خطأ إذا كانت العبارة في سطر واحد تتعارض أيضًا بشكل مباشر مع هذا الهدف. لا تصبح عمليات التحقق من الأخطاء أكثر خفة إلى حد كبير ولا يتم تقليل حجمها عن طريق إزالة أحرف السطر الجديد الداخلية. إذا كان هناك أي شيء ، فسيصبح من الصعب تقشيرهم.

تتمثل الفائدة الرئيسية للمحاولة في الحصول على اختصار واضح للحالة الأكثر شيوعًا ، مما يجعل الحالات غير المعتادة تبرز بشكل أكبر وتستحق القراءة بعناية.

النسخ الاحتياطي من الأدوات الحكومية إلى الأدوات العامة ، فإن اقتراح التركيز على الأدوات لكتابة عمليات التحقق من الأخطاء بدلاً من تغيير اللغة يمثل مشكلة أيضًا. كما قال أبيلسون وسوسمان ، "يجب كتابة البرامج ليقرأها الناس ، وفقط بالمصادفة لكي تنفذها الآلات." إذا كانت الأدوات الآلية _ مطلوبة_ للتعامل مع اللغة ، فإن اللغة لا تؤدي وظيفتها. يجب ألا تقتصر إمكانية القراءة على الأشخاص الذين يستخدمون أدوات معينة.

قام عدد قليل من الأشخاص بتشغيل المنطق في الاتجاه المعاكس: يمكن للأشخاص كتابة تعبيرات معقدة ، لذلك سيفعلون ذلك حتمًا ، لذلك ستحتاج إلى IDE أو دعم أداة أخرى للعثور على التعبيرات التجريبية ، لذا فإن المحاولة فكرة سيئة. هناك بعض القفزات غير المدعومة هنا ، على الرغم من ذلك. السبب الرئيسي هو الادعاء بأنه نظرًا لأنه _ من الممكن_ كتابة كود معقد وغير قابل للقراءة ، فإن هذا الرمز سيصبح في كل مكان. كما لاحظ josharian ، من الممكن بالفعل "كتابة رمز بغيض في Go ". هذا ليس شائعًا لأن المطورين لديهم معايير حول محاولة العثور على الطريقة الأكثر قابلية للقراءة لكتابة جزء معين من التعليمات البرمجية. لذلك من المؤكد _لا _ أن يكون دعم IDE مطلوبًا لقراءة البرامج التي تتضمن try. وفي الحالات القليلة التي يكتب فيها الناس إساءة استخدام رمز فظيع حقًا ، فمن غير المرجح أن يكون دعم IDE مفيدًا كثيرًا. هذا الاعتراض - يمكن للأشخاص كتابة رمز سيء للغاية باستخدام الميزة الجديدة - يتم طرحه في كل مناقشة حول كل ميزة لغة جديدة في كل لغة. انها ليست مفيدة بشكل رهيب. قد يكون الاعتراض الأكثر فائدة هو الشكل "سيكتب الأشخاص رمزًا يبدو جيدًا في البداية ولكن يتبين أنه أقل جودة لهذا السبب غير المتوقع" ، كما هو الحال في مناقشة تصحيح الأخطاء المطبوعات .

مرة أخرى: يجب ألا تقتصر إمكانية القراءة على الأشخاص الذين يستخدمون أدوات معينة.
(ما زلت أقوم بطباعة البرامج وقراءتها على الورق ، على الرغم من أن الناس غالبًا ما يعطونني مظهرًا غريبًا للقيام بذلك).

شكرًا rsc على تقديم أفكارك حول السماح بتحويل عبارات if إلى سطر واحد.

اقتراح إجراء اختبار خطأ إذا كانت العبارة في سطر واحد تتعارض أيضًا بشكل مباشر مع هذا الهدف. لا تصبح عمليات التحقق من الأخطاء أكثر خفة إلى حد كبير ولا يتم تقليل حجمها عن طريق إزالة أحرف السطر الجديد الداخلية. إذا كان هناك أي شيء ، فسيصبح من الصعب تقشيرهم.

أقدر هذه التأكيدات بشكل مختلف.

أجد تقليل عدد الخطوط من 3 إلى 1 ليكون خفيف الوزن بدرجة أكبر. ألن تتطلب gofmt عبارة if لتحتوي ، على سبيل المثال ، 9 (أو حتى 5) أسطر جديدة بدلاً من 3 على زيادة الوزن بشكل كبير؟ إنه نفس عامل (مقدار) التخفيض / التمدد. أود أن أزعم أن القيم الحرفية الهيكلية لها هذه المقايضة الدقيقة ، ومع إضافة try ، ستسمح بتدفق التحكم بقدر بيان if .

ثانيًا ، أجد الحجة التي مفادها أنه أصبح من الصعب تقشيرها لتطبيقها بشكل جيد على try ، إن لم يكن أكثر. يجب أن يكون بيان if على الأقل في السطر الخاص به. لكن ربما أسيء فهم المقصود بكلمة "skim" في هذا السياق. أنا أستخدمه ليعني "تخطي في الغالب ولكن كن على علم بذلك."

بعد كل ما قيل ، استند اقتراح الحكومة إلى اتخاذ خطوة أكثر تحفظًا من try ولم يكن له أي تأثير على try ما لم يكن ذلك كافياً. يبدو أنه ليس كذلك ، وبالتالي إذا كنت أرغب في مناقشته أكثر ، فسأفتح قضية / اقتراحًا جديدًا. : +1:

أجد تقليل عدد الخطوط من 3 إلى 1 ليكون خفيف الوزن بدرجة أكبر.

أعتقد أن الجميع متفقون على أنه من الممكن أن تكون التعليمات البرمجية كثيفة للغاية. على سبيل المثال ، إذا كانت الحزمة بأكملها عبارة عن سطر واحد ، أعتقد أننا نتفق جميعًا على أن هذه مشكلة. ربما نختلف جميعًا على الخط الدقيق. بالنسبة لي ، لقد أنشأنا

n, err := src.Read(buf)
if err == io.EOF {
    return nil
} else if err != nil {
    return err
}

كطريقة لتنسيق هذا الرمز ، وأعتقد أنه سيكون من الصعب للغاية محاولة التحول إلى مثالك

n, err := src.Read(buf)
if err == io.EOF { return nil }
else if err != nil { return err }

في حين أن. إذا بدأنا بهذه الطريقة ، فأنا متأكد من أنه سيكون على ما يرام. لكننا لم نفعل ذلك ، وليس ما نحن عليه الآن.

أنا شخصياً أجد الوزن الخفيف السابق على الصفحة بمعنى أنه من الأسهل تقشيره. يمكنك رؤية if-else في لمحة دون قراءة أي أحرف فعلية. في المقابل ، يصعب تمييز النسخة الأكثر كثافة بنظرة واحدة من سلسلة من ثلاث عبارات ، مما يعني أنه يجب عليك النظر بعناية أكبر قبل أن يصبح معناها واضحًا.

في النهاية ، لا بأس إذا رسمنا خط الكثافة مقابل سهولة القراءة في أماكن مختلفة بقدر عدد الأسطر الجديدة. يركز اقتراح المحاولة ليس فقط على إزالة الأسطر الجديدة ولكن إزالة التركيبات تمامًا ، وهذا ينتج عنه وجود صفحة أخف وزنًا منفصلًا عن سؤال gofmt.

قام عدد قليل من الأشخاص بتشغيل المنطق في الاتجاه المعاكس: يمكن للأشخاص كتابة تعبيرات معقدة ، لذلك سيفعلون ذلك حتمًا ، لذلك ستحتاج إلى IDE أو دعم أداة أخرى للعثور على التعبيرات التجريبية ، لذا فإن المحاولة فكرة سيئة. هناك بعض القفزات غير المدعومة هنا ، على الرغم من ذلك. السبب الرئيسي هو الادعاء بأنه نظرًا لأنه _ من الممكن_ كتابة كود معقد وغير قابل للقراءة ، فإن هذا الرمز سيصبح في كل مكان. كما لاحظ josharian ، من الممكن بالفعل "كتابة رمز بغيض في Go ". هذا ليس شائعًا لأن المطورين لديهم معايير حول محاولة العثور على الطريقة الأكثر قابلية للقراءة لكتابة جزء معين من التعليمات البرمجية. لذلك من المؤكد _لا _ أن يكون دعم IDE مطلوبًا لقراءة البرامج التي تتضمن try. وفي الحالات القليلة التي يكتب فيها الناس إساءة استخدام رمز فظيع حقًا ، فمن غير المرجح أن يكون دعم IDE مفيدًا كثيرًا. هذا الاعتراض - يمكن للأشخاص كتابة رمز سيء للغاية باستخدام الميزة الجديدة - يتم طرحه في كل مناقشة حول كل ميزة لغة جديدة في كل لغة. انها ليست مفيدة بشكل رهيب.

أليس هذا هو السبب الكامل لعدم وجود عامل تشغيل ثلاثي في ​​Go ؟

أليس هذا هو السبب الكامل لعدم وجود عامل تشغيل ثلاثي في ​​Go؟

لا ، يمكننا ويجب علينا التمييز بين "يمكن استخدام هذه الميزة لكتابة تعليمات برمجية مقروءة جدًا ، ولكن قد يتم إساءة استخدامها أيضًا لكتابة رمز غير قابل للقراءة" و "الاستخدام السائد لهذه الميزة هو كتابة رمز غير قابل للقراءة".

تجربة مع C تشير إلى ذلك؟ : يقع مباشرة في الفئة الثانية. (مع استثناء محتمل لـ min و max ، لست متأكدًا من أنني رأيت رمزًا يستخدم؟: لم يتم تحسين ذلك من خلال إعادة كتابته لاستخدام عبارة if بدلاً من ذلك. ولكن هذه الفقرة تخرج عن الموضوع.)

بناء الجملة

حددت هذه المناقشة ستة صيغ مختلفة لكتابة نفس الدلالات من الاقتراح:

(أعتذر إذا أخطأت في أصل القصص!)

كل هذه لها إيجابيات وسلبيات ، والشيء الجميل هو أنه نظرًا لأن لديهم جميعًا نفس الدلالات ، فليس من المهم جدًا الاختيار بين الصيغ المختلفة من أجل إجراء المزيد من التجارب.

لقد وجدت هذا المثال من خلالbrynbellomy مثير للتفكير:

headRef := try(r.Head())
parentObjOne := try(headRef.Peel(git.ObjectCommit))
parentObjTwo := try(remoteBranch.Reference.Peel(git.ObjectCommit))
parentCommitOne := try(parentObjOne.AsCommit())
parentCommitTwo := try(parentObjTwo.AsCommit())
treeOid := try(index.WriteTree())
tree := try(r.LookupTree(treeOid))

// vs

try headRef := r.Head()
try parentObjOne := headRef.Peel(git.ObjectCommit)
try parentObjTwo := remoteBranch.Reference.Peel(git.ObjectCommit)
try parentCommitOne := parentObjOne.AsCommit()
try parentCommitTwo := parentObjTwo.AsCommit()
try treeOid := index.WriteTree()
try tree := r.LookupTree(treeOid)

// vs

try (
    headRef := r.Head()
    parentObjOne := headRef.Peel(git.ObjectCommit)
    parentObjTwo := remoteBranch.Reference.Peel(git.ObjectCommit)
    parentCommitOne := parentObjOne.AsCommit()
    parentCommitTwo := parentObjTwo.AsCommit()
    treeOid := index.WriteTree()
    tree := r.LookupTree(treeOid)
)

لا يوجد فرق كبير بين هذه الأمثلة المحددة ، بالطبع. وإذا كانت المحاولة موجودة في جميع السطور ، فلماذا لا تصطفها أو تحل محلها؟ أليس هذا منظف؟ كنت أتساءل عن هذا أيضا.

ولكن كما لاحظ ianlancetaylor ، "المحاولة تدفن اللد. تصبح الشفرة سلسلة من عبارات try ، والتي تحجب ما تفعله الشفرة بالفعل ".

أعتقد أن هذه نقطة حاسمة: اصطفاف المحاولة بهذه الطريقة ، أو تحليلها كما في الكتلة ، يعني ضمناً توازياً خاطئاً. إنه يعني أن المهم في هذه العبارات هو أنها تحاول جميعًا. هذا ليس أهم شيء في الكود وليس ما يجب أن نركز عليه عند قراءته.

افترض من أجل الجدل أن AsCommit لا يفشل أبدًا وبالتالي لا يُرجع خطأ. الآن لدينا:

headRef := try(r.Head())
parentObjOne := try(headRef.Peel(git.ObjectCommit))
parentObjTwo := try(remoteBranch.Reference.Peel(git.ObjectCommit))
parentCommitOne := parentObjOne.AsCommit()
parentCommitTwo := parentObjTwo.AsCommit()
treeOid := try(index.WriteTree())
tree := try(r.LookupTree(treeOid))

// vs

try headRef := r.Head()
try parentObjOne := headRef.Peel(git.ObjectCommit)
try parentObjTwo := remoteBranch.Reference.Peel(git.ObjectCommit)
parentCommitOne := parentObjOne.AsCommit()
parentCommitTwo := parentObjTwo.AsCommit()
try treeOid := index.WriteTree()
try tree := r.LookupTree(treeOid)

// vs

try (
    headRef := r.Head()
    parentObjOne := headRef.Peel(git.ObjectCommit)
    parentObjTwo := remoteBranch.Reference.Peel(git.ObjectCommit)
)
parentCommitOne := parentObjOne.AsCommit()
parentCommitTwo := parentObjTwo.AsCommit()
try (
    treeOid := index.WriteTree()
    tree := r.LookupTree(treeOid)
)

ما تراه للوهلة الأولى هو أن الخطين الأوسطين يختلفان بوضوح عن الخطين الآخرين. لماذا ا؟ اتضح بسبب معالجة الخطأ. هل هذا هو أهم التفاصيل حول هذا الرمز ، الشيء الذي يجب أن تلاحظه للوهلة الأولى؟ جوابي هو لا. أعتقد أنه يجب أن تلاحظ المنطق الأساسي لما يفعله البرنامج أولاً ، ومعالجة الأخطاء لاحقًا. في هذا المثال ، تعيق تعليمة try و try block هذا العرض للمنطق الأساسي. بالنسبة لي ، هذا يشير إلى أنها ليست الصيغة الصحيحة لهذه الدلالات.

هذا يترك التركيبات الأربعة الأولى ، والتي هي أكثر تشابهًا مع بعضها البعض:

headRef := try(r.Head())
parentObjOne := try(headRef.Peel(git.ObjectCommit))
parentObjTwo := try(remoteBranch.Reference.Peel(git.ObjectCommit))
parentCommitOne := parentObjOne.AsCommit()
parentCommitTwo := parentObjTwo.AsCommit()
treeOid := try(index.WriteTree())
tree := try(r.LookupTree(treeOid))

// vs

headRef := try r.Head()
parentObjOne := try headRef.Peel(git.ObjectCommit)
parentObjTwo := try remoteBranch.Reference.Peel(git.ObjectCommit)
parentCommitOne := parentObjOne.AsCommit()
parentCommitTwo := parentObjTwo.AsCommit()
treeOid := try index.WriteTree()
tree := try r.LookupTree(treeOid)

// vs

headRef := r.Head()?
parentObjOne := headRef.Peel(git.ObjectCommit)?
parentObjTwo := remoteBranch.Reference.Peel(git.ObjectCommit)?
parentCommitOne := parentObjOne.AsCommit()
parentCommitTwo := parentObjTwo.AsCommit()
treeOid := index.WriteTree()?
tree := r.LookupTree(treeOid)?

// vs

headRef := r.Head?()
parentObjOne := headRef.Peel?(git.ObjectCommit)
parentObjTwo := remoteBranch.Reference.Peel?(git.ObjectCommit)
parentCommitOne := parentObjOne.AsCommit()
parentCommitTwo := parentObjTwo.AsCommit()
treeOid := index.WriteTree?()
tree := r.LookupTree?(treeOid)

من الصعب أن تنشغل كثيرًا بشأن اختيار واحد على الآخرين. كلهم لديهم نقاطهم الجيدة والسيئة. أهم مزايا النموذج المدمج هي:

(1) المعامل الدقيق واضح للغاية ، خاصة بالمقارنة مع عامل البادئة try x.y().z() .
(2) يمكن للأدوات التي لا تحتاج إلى معرفتها عن المحاولة التعامل معها على أنها مكالمة دالة عادية ، لذلك على سبيل المثال ، ستعمل goimports بشكل جيد دون أي تعديلات ، و
(3) هناك مجال للتوسع والتعديل المستقبلي إذا لزم الأمر.

من الممكن تمامًا بعد رؤية الكود الحقيقي باستخدام هذه التركيبات ، أن نطور إحساسًا أفضل لما إذا كانت مزايا أحد الصيغ الثلاثة الأخرى تفوق مزايا بناء جملة استدعاء الوظيفة. فقط التجارب والتجارب يمكن أن تخبرنا بذلك.

شكرا على كل التوضيح. كلما زاد اعتقادي ، زاد إعجابي بالاقتراح وشاهدت مدى ملاءمته للأهداف.

لماذا لا تستخدم دالة مثل recover() بدلاً من err التي لا نعرف من أين تأتي؟ سيكون أكثر اتساقًا وربما أسهل في التنفيذ.

func f() error {
 defer func() {
   if err:=error();err!=nil {
     ...
   }
 }()
}

تحرير: لا أستخدم الإرجاع المُسمى مطلقًا ، فسيكون من الغريب بالنسبة لي إضافة اسم إرجاع لهذا الغرض فقط

flibustenet ، راجع أيضًا https://swtch.com/try.html#Name للحصول على بعض الاقتراحات المماثلة.
(الإجابة على كل منهم: يمكننا القيام بذلك ، ولكن ليس من الضروري تمامًا في ضوء النتائج المسماة ، لذلك قد نحاول أيضًا استخدام المفهوم الحالي قبل أن نقرر أننا بحاجة إلى توفير طريقة ثانية.)

قد تكون النتيجة غير المقصودة لـ try() أن تتخلى المشاريع عن _go fmt_ من أجل الحصول على عمليات تحقق من الخطأ من سطر واحد. هذه تقريبًا جميع مزايا try() بدون أي تكاليف. لقد فعلت ذلك لبضع سنوات. أنه يعمل بشكل جيد.

لكنني أفضل أن أكون قادرًا على تحديد معالج أخطاء الملاذ الأخير للحزمة ، وإزالة جميع عمليات التحقق من الأخطاء التي تحتاجها. ما كنت أقوم بتعريفه ليس try() .

networkimprov ، يبدو أنك قادم من موقع مختلف عن مستخدمي Go الذين نستهدفهم ، وستساهم رسالتك بشكل أكبر في المحادثة إذا كانت تتضمن تفاصيل أو روابط إضافية حتى نتمكن من فهم وجهة نظرك بشكل أفضل.

ليس من الواضح ما هي "التكاليف" التي تعتقد أنها تنطوي على المحاولة. وبينما تقول إن التخلي عن gofmt "ليس له أي تكاليف" للمحاولة (مهما كانت) ، يبدو أنك تتجاهل أن تنسيق gofmt هو التنسيق الذي تستخدمه جميع البرامج التي تساعد في إعادة كتابة كود مصدر Go ، مثل goimports ، على سبيل المثال ، gorename ، وما إلى ذلك وهلم جرا. أنت تتخلى عن go fmt على حساب التخلي عن هؤلاء المساعدين ، أو على الأقل تحمل تعديلات عرضية كبيرة على التعليمات البرمجية الخاصة بك عند استدعائهم. ومع ذلك ، إذا كان من الأفضل لك القيام بذلك ، فهذا رائع: بكل الوسائل استمر في فعل ذلك.

كما أنه من غير الواضح ما يعنيه "تحديد معالج أخطاء الملاذ الأخير للحزمة" أو لماذا يكون من المناسب تطبيق سياسة معالجة الأخطاء على الحزمة بأكملها بدلاً من وظيفة واحدة في كل مرة. إذا كان الشيء الرئيسي الذي تريد القيام به في معالج الأخطاء هو إضافة سياق ، فلن يكون نفس السياق مناسبًا عبر الحزمة بأكملها.

rsc ، كما قد تكون قد رأيت ، بينما اقترحت بناء جملة كتلة المحاولة ، عدت لاحقًا إلى الجانب "لا" لهذه الميزة - جزئيًا لأنني أشعر بعدم الارتياح لإخفاء إرجاع خطأ شرطي واحد أو أكثر في بيان أو تطبيق وظيفي. لكن دعني أوضح نقطة واحدة. في اقتراح حظر المحاولة ، سمحت صراحةً بالتصريحات التي لا تحتاج إلى try . لذا فإن مثال كتلة المحاولة الأخيرة الخاص بك سيكون:

try (
    headRef := r.Head()
    parentObjOne := headRef.Peel(git.ObjectCommit)
    parentObjTwo := remoteBranch.Reference.Peel(git.ObjectCommit)
        parentCommitOne := parentObjOne.AsCommit()
        parentCommitTwo := parentObjTwo.AsCommit()
    treeOid := index.WriteTree()
    tree := r.LookupTree(treeOid)
)

هذا يعني ببساطة أن أي أخطاء يتم إرجاعها داخل كتلة try يتم إرجاعها إلى المتصل. إذا تجاوز عنصر التحكم كتلة المحاولة ، فلا توجد أخطاء في الكتلة.

أنت قلت

أعتقد أنه يجب أن تلاحظ المنطق الأساسي لما يفعله البرنامج أولاً ، ومعالجة الأخطاء لاحقًا.

هذا هو بالضبط السبب الذي جعلني أفكر في كتلة المحاولة! ما تم استبعاده ليس فقط الكلمة الأساسية ولكن معالجة الخطأ. لا أريد أن أفكر في N أماكن مختلفة قد تولد أخطاء (باستثناء عندما أحاول صراحة معالجة أخطاء معينة).

بعض النقاط الأخرى التي قد تستحق الذكر:

  1. لا يعرف المتصل من أين جاء الخطأ بالضبط من داخل المستدعي. هذا ينطبق أيضًا على الاقتراح البسيط الذي تفكر فيه بشكل عام. لقد توقعت أنه يمكن جعل المترجم يضيف التعليق التوضيحي الخاص به لإعادة نقطة إرجاع الخطأ. لكنني لم أفكر في الأمر كثيرًا.
  2. ليس من الواضح بالنسبة لي ما إذا كان مسموحًا باستخدام تعبيرات مثل try(try(foo(try(bar)).fum()) . قد يكون هذا الاستخدام مستاءً ولكن يجب تحديد دلالاته. في حالة كتلة المحاولة ، يجب على المترجم أن يعمل بجدية أكبر لاكتشاف مثل هذه الاستخدامات والضغط على معالجة جميع الأخطاء إلى مستوى كتلة المحاولة.
  3. أميل أكثر إلى الإعجاب بـ return-on-error بدلاً من try . هذا أسهل للابتلاع على مستوى الكتلة!
  4. على الجانب الآخر ، فإن أي كلمات رئيسية طويلة تجعل الأشياء أقل قابلية للقراءة.

FWIW ، ما زلت لا أعتقد أن هذا يستحق القيام به.

rsc

[...]
السبب الرئيسي هو الادعاء بأنه نظرًا لإمكانية كتابة كود معقد وغير قابل للقراءة ، فإن هذا الرمز سيصبح في كل مكان. كما لاحظ josharian ، من الممكن بالفعل "كتابة رمز بغيض في Go".
[...]

headRef := try(r.Head())
parentObjOne := try(headRef.Peel(git.ObjectCommit))
parentObjTwo := try(remoteBranch.Reference.Peel(git.ObjectCommit))
parentCommitOne := try(parentObjOne.AsCommit())
parentCommitTwo := try(parentObjTwo.AsCommit())

أفهم أن موقفك من "الشفرة السيئة" هو أنه يمكننا كتابة كود فظيع اليوم مثل الكتلة التالية.

parentCommitOne := try(try(try(r.Head()).Peel(git.ObjectCommit)).AsCommit())
parentCommitTwo := try(try(remoteBranch.Reference.Peel(git.ObjectCommit)).AsCommit())

ما هي أفكارك حول عدم السماح بالمكالمات المتداخلة try حتى لا نكتب عن طريق الخطأ رمزًا سيئًا؟

إذا لم تسمح بتداخل try في الإصدار الأول ، فستتمكن من إزالة هذا القيد لاحقًا إذا لزم الأمر ، فلن يكون ذلك ممكنًا بالعكس.

لقد ناقشت هذه النقطة بالفعل ولكن يبدو أنها ذات صلة - يجب أن يتم قياس تعقيد الكود عموديًا وليس أفقيًا.

يشجع try كتعبير تعقيد التعليمات البرمجية على التوسع أفقيًا عن طريق تشجيع المكالمات المتداخلة. يشجع try كبيان تعقيد الكود على التوسع عموديًا.

rsc ، على أسئلتك ،

معالج الملاذ الأخير على مستوى الحزمة الخاص بي - عندما لا يتوقع الخطأ:

func quit(err error) {
   fmt.Fprintf(os.Stderr, "quit after %s\n", err)
   debug.PrintStack()      // because panic(err) produces a pile of noise
   os.Exit(3)
}

السياق: استخدم ملف os. بشكل مكثف (حيث وجدت خطأين: # 26650 & # 32088)

سيحتاج المصمم على مستوى الحزمة الذي يضيف السياق الأساسي إلى وسيطة caller - بنية مُنشأة توفر نتائج وقت التشغيل. Caller ().

أتمنى أن يستخدم معيد الكتابة _go fmt_ التنسيق الحالي ، أو يتيح لك تحديد التنسيق لكل تحويل. أنا أفعل مع أدوات أخرى.

التكاليف (أي العيوب) try() موثقة جيدًا أعلاه.

لقد تأثرت بصدق لأن فريق Go قدم لنا أولًا check/handle (فكرة خيرية) ، ثم ternaryesque try() . لا أفهم سبب عدم إصدار معالجة خطأ RFP ، ثم جمع تعليقات المجتمع على بعض المقترحات الناتجة (انظر # 29860). هناك الكثير من الحكمة هنا يمكنك الاستفادة منها!

rsc

بناء الجملة

حددت هذه المناقشة ستة صيغ مختلفة لكتابة نفس الدلالات من الاقتراح:

try {error} {optional wrap func} {optional return args in brackets}

f, err := os.Open(file)
try err wrap { a, b }

... و IMO ، تحسين قابلية القراءة (من خلال الجناس) وكذلك الدقة الدلالية:

f, err := os.Open(file)
relay err

أو

f, err := os.Open(file)
relay err wrap

أو

f, err := os.Open(file)
relay err wrap { a, b }

أو

f, err := os.Open(file)
relay err { a, b }

أعلم أنه من السهل رفض الدعوة إلى الترحيل مقابل المحاولة باعتبارها خارج الموضوع ، لكن يمكنني فقط أن أتخيل محاولة شرح كيف أن المحاولة لا تحاول أي شيء ولا ترمي أي شيء. ليس واضحًا ولديه أمتعة. إن استخدام مصطلح relay سيتيح تفسيرًا واضحًا للعقلية ، والوصف له أساس في الدوائر (وهو ما يدور حوله كل هذا على أي حال).

تحرير للتوضيح:
المحاولة يمكن أن تعني - 1. تجربة شيء ما ثم الحكم عليه بشكل شخصي 2. للتحقق من شيء ما بموضوعية 3. محاولة القيام بشيء 4. إطلاق تدفقات تحكم متعددة يمكن مقاطعتها وإطلاق إخطار يمكن اعتراضه إذا كان الأمر كذلك

في هذا الاقتراح ، حاول ألا تفعل أيًا من هؤلاء. نحن في الواقع نقوم بتشغيل دالة. ثم يتم إعادة توصيل تدفق التحكم بناءً على قيمة الخطأ. هذا هو حرفيا تعريف مرحل الحماية. نحن نعيد وضع الدارة مباشرة (أي قصر دائرة نطاق الوظيفة الحالية) وفقًا لقيمة الخطأ الذي تم اختباره.

في اقتراح الحظر التجريبي ، سمحت صراحةً بالعبارات التي لا تحتاج إلى المحاولة

الميزة الرئيسية في معالجة أخطاء Go التي أراها عبر نظام try-catch للغات مثل Java و Python هي أنه من الواضح دائمًا ما هي استدعاءات الوظائف التي قد تؤدي إلى حدوث خطأ وأيها لا يمكن. إن جمال try كما هو موثق في الاقتراح الأصلي هو أنه يمكن أن يقلل من معالجة الأخطاء البسيطة مع الاستمرار في الحفاظ على هذه الميزة الهامة.

لنستعير من أمثلة Goodwine ، على الرغم من قبحها ، من منظور معالجة الخطأ حتى هذا:

parentCommitOne := try(try(try(r.Head()).Peel(git.ObjectCommit)).AsCommit())
parentCommitTwo := try(try(remoteBranch.Reference.Peel(git.ObjectCommit)).AsCommit())

... أفضل مما تراه غالبًا في لغات التجربة

parentCommitOne := r.Head().Peel(git.ObjectCommit).AsCommit()
parentCommitTwo := remoteBranch.Reference.Peel(git.ObjectCommit).AsCommit()

... لأنه لا يزال بإمكانك معرفة أجزاء الكود التي قد تحول تدفق التحكم بسبب خطأ وأيها لا يمكنه ذلك.

أعلم أن bakul لا يدافع عن اقتراح بناء الجملة هذا على أي حال ، لكنني أعتقد أنه يثير نقطة مثيرة للاهتمام حول معالجة أخطاء Go مقارنة بالآخرين. أعتقد أنه من المهم ألا يؤدي أي اقتراح لمعالجة الأخطاء يتبناه Go إلى تشويش أي أجزاء من الكود يمكن وما لا يمكن تجاوزها.

لقد كتبت أداة صغيرة: tryhard (التي لا تبذل جهدًا كبيرًا في الوقت الحالي) تعمل على أساس كل ملف على حدة وتستخدم مطابقة نمط AST البسيط للتعرف على المرشحين المحتملين مقابل try والإبلاغ عنها (وإعادة كتابتها). الأداة بدائية (لا يوجد فحص للنوع) وهناك فرصة جيدة للإيجابيات الخاطئة ، اعتمادًا على أسلوب الترميز السائد. اقرأ الوثائق للحصول على التفاصيل.

تطبيقه على $GOROOT/src عند تقارير الإكرامية> 5000 (!) فرصة مقابل try . قد يكون هناك الكثير من الإيجابيات الخاطئة ، لكن التحقق من عينة جيدة باليد يشير إلى أن معظم الفرص حقيقية.

يوضح استخدام ميزة إعادة الكتابة كيف سيبدو الرمز باستخدام try . مرة أخرى ، تظهر نظرة خاطفة على الناتج تحسنًا كبيرًا في ذهني.

( تنبيه: ستؤدي ميزة إعادة الكتابة إلى تدمير الملفات! استخدمها على مسؤوليتك الخاصة. )

نأمل أن يوفر هذا بعض الأفكار الملموسة حول الشكل الذي قد يبدو عليه الرمز باستخدام try ويتيح لنا تجاوز المضاربة الخاملة وغير المنتجة.

شكرا واستمتع.

أفهم أن موقفك من "الشفرة السيئة" هو أنه يمكننا كتابة كود فظيع اليوم مثل الكتلة التالية.

parentCommitOne := try(try(try(r.Head()).Peel(git.ObjectCommit)).AsCommit())
parentCommitTwo := try(try(remoteBranch.Reference.Peel(git.ObjectCommit)).AsCommit())

موقفي هو أن مطوري Go يقومون بعمل لائق في كتابة كود واضح ، ومن شبه المؤكد أن المترجم ليس هو الشيء الوحيد الذي يقف في طريقك أنت أو زملائك في العمل في كتابة التعليمات البرمجية التي تبدو هكذا.

ما هي أفكارك حول عدم السماح بالمكالمات المتداخلة try حتى لا نكتب عن طريق الخطأ رمزًا سيئًا؟

يُستمد جزء كبير من بساطة Go من اختيار الميزات المتعامدة التي يتم تكوينها بشكل مستقل. تؤدي إضافة القيود إلى كسر التعامد والتركيب والاستقلالية ، وبذلك يكسر البساطة.

اليوم ، من القواعد أنه إذا كان لديك:

x := expression
y := f(x)

مع عدم وجود استخدام آخر لـ x في أي مكان ، فإنه يعد تحويلًا صحيحًا للبرنامج لتبسيط ذلك إلى

y := f(expression)

إذا اعتمدنا قيدًا على تعبيرات التجربة ، فسيؤدي ذلك إلى كسر أي أداة افترضت أن هذا كان دائمًا تحويلًا صالحًا. أو إذا كان لديك منشئ رمز يعمل مع التعبيرات وقد يعالج التعبيرات التجريبية ، فسيتعين عليه أن يبذل قصارى جهده لتقديم الموقتات لتلبية القيود. وهلم جرا وهلم جرا.

باختصار ، تضيف القيود تعقيدًا كبيرًا. إنهم بحاجة إلى تبرير مهم ، وليس "دعونا نرى ما إذا كان أحد يصطدم بهذا الجدار ويطلب منا هدمه".

لقد كتبت تفسيرًا أطول منذ عامين على https://github.com/golang/go/issues/18130#issuecomment -264195616 (في سياق الأسماء المستعارة للنوع) ينطبق جيدًا هنا أيضًا.

@ باكول ،

لكن دعني أوضح نقطة واحدة. في اقتراح حظر المحاولة ، سمحت صراحةً بالتصريحات التي _لا تحتاج_ try .

القيام بذلك لا يرقى إلى الهدف الثاني : "يجب أن تظل عمليات التحقق من الأخطاء ومعالجة الأخطاء صريحة ، مما يعني أنه مرئي في نص البرنامج. لا نريد تكرار مخاطر معالجة الاستثناءات."

المأزق الرئيسي في معالجة الاستثناءات التقليدية هو عدم معرفة مكان الشيكات. انصح:

try {
    s = canThrowErrors()
    t = cannotThrowErrors()
    u = canThrowErrors() // a second call
} catch {
    // how many ways can you get here?
}

إذا لم يتم تسمية الوظائف بشكل مفيد ، فقد يكون من الصعب جدًا تحديد الوظائف التي قد تفشل وأيها مضمون للنجاح ، مما يعني أنه لا يمكنك بسهولة التفكير في أجزاء التعليمات البرمجية التي يمكن مقاطعة أي منها بواسطة استثناء وأيها لا يمكن.

قارن هذا مع نهج Swift ، حيث يتبنون بعضًا من بناء جملة معالجة الاستثناءات التقليدية لكنهم في الواقع يقومون بمعالجة الأخطاء ، مع علامة واضحة على كل وظيفة محددة ولا توجد طريقة للتخلص من إطار المكدس الحالي:

do {
    let s = try canThrowErrors()
    let t = cannotThrowErrors()
    let u = try canThrowErrors() // a second call
} catch {
    handle error from try above
}

سواء أكان الأمر Rust أو Swift أو هذا الاقتراح ، فإن مفتاح التحسين الحاسم على معالجة الاستثناء هو وضع علامة صريحة في النص - حتى مع وجود علامة خفيفة الوزن للغاية - في كل مكان يوجد فيه فحص.

لمزيد من المعلومات حول مشكلة عمليات التحقق الضمنية ، راجع قسم المشكلة في نظرة عامة على المشكلة من أغسطس الماضي ، ولا سيما الروابط الخاصة بمقالتي Raymond Chen.

تحرير: راجع أيضًا تعليق velovix 's three up ، والذي جاء أثناء عملي على هذا التعليق.

daved ، أنا سعيد لأن تشبيه "الترحيل الوقائي" يناسبك. انها لا تعمل بالنسبة لي. البرامج ليست دوائر.

يمكن أن يساء فهم أي كلمة:
"كسر" لا يكسر البرنامج الخاص بك.
"استمر" لا يستمر في التنفيذ في البيان التالي كالمعتاد.
"اذهب" ... حسنًا ، من المستحيل أن يساء فهمه في الواقع. :-)

https://www.google.com/search؟q=define+try يقول "قم بمحاولة أو جهد لفعل شيء ما" و "خاضع للتجربة". كلاهما ينطبق على "f: = try (os.Open (ملف))". يحاول إجراء OS.Open (أو يعرض نتيجة الخطأ للمحاكمة) ، وإذا فشلت المحاولة (أو نتيجة الخطأ) ، فإنه يعود من الوظيفة.

استخدمنا الشيك في أغسطس الماضي. كانت تلك كلمة طيبة أيضًا. لقد قمنا بالتبديل للمحاولة ، على الرغم من المتاع التاريخي لـ C ++ / Java / Python ، لأن المعنى الحالي للتجربة في هذا الاقتراح يطابق المعنى في محاولة Swift (بدون المحاولة المحيطة) وفي محاولة Rust الأصلية! . لن يكون الأمر فظيعًا إذا قررنا لاحقًا أن الاختيار هو الكلمة الصحيحة بعد كل شيء ولكن في الوقت الحالي يجب أن نركز على أشياء أخرى غير الاسم.

إليك قيمة سلبية كاذبة tryhard مثيرة للاهتمام ، من github.com/josharian/pct . أذكرها هنا للأسباب التالية:

  • إنه يوضح طريقة يكون فيها الكشف الآلي try أمرًا صعبًا
  • يوضح أن التكلفة المرئية لـ if err != nil تؤثر على كيفية بناء الناس (أنا على الأقل) لشفراتهم ، وأن try يمكن أن يساعد في ذلك

قبل:

var err error
switch {
case *flagCumulative:
    _, err = fmt.Fprintf(w, "% 6.2f%% % 6.2f%%% 6d %s\n", p, f*float64(runtot), line.n, line.s)
case *flagQuiet:
    _, err = fmt.Fprintln(w, line.s)
default:
    _, err = fmt.Fprintf(w, "% 6.2f%%% 6d %s\n", p, line.n, line.s)
}
if err != nil {
    return err
}

بعد (إعادة الكتابة اليدوية):

switch {
case *flagCumulative:
    try(fmt.Fprintf(w, "% 6.2f%% % 6.2f%%% 6d %s\n", p, f*float64(runtot), line.n, line.s))
case *flagQuiet:
    try(fmt.Fprintln(w, line.s))
default:
    try(fmt.Fprintf(w, "% 6.2f%%% 6d %s\n", p, line.n, line.s))
}

تغيير https://golang.org/cl/182717 يذكر هذه المشكلة: src: apply tryhard -r $GOROOT/src

للحصول على فكرة مرئية عن try في مكتبة الأمراض المنقولة جنسياً ، توجه إلى CL 182717 .

شكرا @ josharian على هذا . نعم ، قد يكون من المستحيل حتى بالنسبة لأداة جيدة الكشف عن جميع المرشحين للاستخدام المحتمل مقابل try . لكن لحسن الحظ ليس هذا هو الهدف الأساسي (لهذا الاقتراح). يعد امتلاك أداة أمرًا مفيدًا ، لكني أرى الفائدة الرئيسية من الرمز try الذي لم تتم كتابته بعد (لأنه سيكون هناك الكثير من ذلك أكثر من الكود الذي لدينا بالفعل).

"كسر" لا يكسر البرنامج الخاص بك.
"استمر" لا يستمر في التنفيذ في البيان التالي كالمعتاد.
"اذهب" ... حسنًا ، من المستحيل أن يساء فهمه في الواقع. :-)

break يكسر الحلقة. continue يواصل الحلقة ، و goto يذهب إلى الوجهة المشار إليها. في النهاية ، أسمعك ، ولكن يرجى مراعاة ما يحدث عندما تكتمل إحدى الوظائف في الغالب وتعيد خطأ ، ولكنها لا تتراجع. لم تكن محاولة / محاكمة. أعتقد أن check أفضل بكثير في هذا الصدد (من المؤكد أن "وقف تقدم" من خلال "الفحص" مناسب).

أكثر صلة بالموضوع ، لدي فضول حول شكل المحاولة / التحقق الذي عرضته على عكس الصيغ الأخرى.
try {error} {optional wrap func} {optional return args in brackets}

f, err := os.Open(file)
try err wrap { a, b }

ينتهي الأمر بالمكتبة القياسية إلى عدم تمثيل كود Go "الحقيقي" من حيث أنها لا تقضي الكثير من الوقت في تنسيق الحزم الأخرى أو توصيلها. لقد لاحظنا هذا في الماضي كسبب لوجود القليل جدًا من استخدام القناة في المكتبة القياسية مقارنة بالحزم البعيدة في السلسلة الغذائية التبعية. أظن أن معالجة الأخطاء وانتشارها ينتهي بها الأمر إلى أن تكون مشابهة للقنوات في هذا الصدد: ستجد المزيد كلما تقدمت في الأعلى.

لهذا السبب ، سيكون من المثير للاهتمام أن يقوم شخص ما بتشغيل tryhard على بعض قواعد أكواد التطبيق الأكبر حجمًا ومعرفة الأشياء الممتعة التي يمكن اكتشافها في هذا السياق. (المكتبة القياسية مثيرة للاهتمام أيضًا ، ولكن باعتبارها صورة مصغرة أكثر من كونها عينة دقيقة من العالم.)

لدي فضول بشأن شكل المحاولة / التحقق الذي عرضته على عكس الصيغ الأخرى.

أعتقد أن هذا النموذج ينتهي به الأمر إلى إعادة إنشاء هياكل التحكم الحالية .

networkimprov ، re https://github.com/golang/go/issues/32437#issuecomment -502879351

لقد تأثرت بصدق لأن فريق Go قدم لنا الاختيار / التعامل أولاً (بشكل خيري ، فكرة جديدة) ، ثم المحاولة الثلاثية (). لا أفهم سبب عدم إصدار معالجة خطأ RFP ، ثم جمع تعليقات المجتمع على بعض المقترحات الناتجة (انظر # 29860). هناك الكثير من الحكمة هنا يمكنك الاستفادة منها!

كما ناقشنا في # 29860 ، أنا بصراحة لا أرى فرقًا كبيرًا بين ما تقترح أنه كان يجب علينا القيام به فيما يتعلق بطلب آراء المجتمع وما فعلناه بالفعل. تقول صفحة مسودة التصاميم صراحةً إنها "نقاط انطلاق للمناقشة ، مع هدف نهائي هو إنتاج تصميمات جيدة بما يكفي لتحويلها إلى مقترحات فعلية". وقد كتب الأشخاص أشياء كثيرة تتراوح من التعليقات القصيرة إلى المقترحات البديلة الكاملة. وكان معظمها مفيدًا وأنا أقدر مساعدتك بشكل خاص في التنظيم والتلخيص. يبدو أنك تركز على تسميته اسمًا مختلفًا أو إدخال طبقات إضافية من البيروقراطية ، والتي كما ناقشناها بشأن هذه المسألة لا نرى حقًا حاجة إليها.

لكن من فضلك لا تدعي أننا بطريقة ما لم نلتمس مشورة المجتمع أو نتجاهلها. هذا ببساطة ليس صحيحًا.

لا أستطيع أيضًا أن أرى كيف تكون المحاولة بأي حال من الأحوال "ثلاثية الأبعاد" ، مهما كان معنى ذلك.

متفق عليه ، أعتقد أن هذا كان هدفي ؛ لا أعتقد أن الآليات الأكثر تعقيدًا تستحق العناء. إذا كنت في مكانك ، فإن أكثر ما أقدمه هو القليل من السكر النحوي لإسكات غالبية الشكاوى وليس أكثر.

rsc ، نعتذر عن الانحراف عن الموضوع!
لقد قمت بترقية معالجات مستوى الحزمة في https://github.com/golang/go/issues/32437#issuecomment -502840914
ورد على طلبك للتوضيح في https://github.com/golang/go/issues/32437#issuecomment -502879351

أرى معالجات مستوى الحزمة كميزة يمكن لأي شخص تقريبًا أن يتخلف عنها.

الرجاء استخدام بناء جملة {} catch {} ، لا تبني المزيد من العجلات

الرجاء استخدام بناء جملة {} catch {} ، لا تبني المزيد من العجلات

أعتقد أنه من المناسب بناء عجلات أفضل عندما تكون العجلات التي يستخدمها الآخرون على شكل مربعات

تضمين التغريدة

قد تكون معالجة الأخطاء القائمة على الاستثناء عبارة عن عجلة موجودة مسبقًا ولكنها تحتوي أيضًا على عدد غير قليل من المشكلات المعروفة. بيان المشكلة في مسودة التصميم الأصلي يقوم بعمل رائع في تحديد هذه القضايا.

لإضافة تعليقي غير مدروس جيدًا ، أعتقد أنه من المثير للاهتمام أن العديد من اللغات الحديثة الناجحة جدًا (مثل Swift و Rust و Go) لم تتبنى استثناءات. يخبرني هذا أن مجتمع البرمجيات الأوسع يعيد التفكير في الاستثناءات بعد سنوات عديدة كان علينا العمل معهم.

ردًا على https://github.com/golang/go/issues/32437#issuecomment -502837008 (تعليق rsc حول try كبيان)

قمت برفع نقطة جيدة. أنا آسف لأنني فاتني هذا التعليق بطريقة ما قبل إجراء هذا التعليق: https://github.com/golang/go/issues/32437#issuecomment -502871889

تبدو الأمثلة التي تستخدم try كتعبير أفضل بكثير من تلك التي تحتوي على try كتعبير. حقيقة أن العبارة تؤدي إلى try تجعل قراءتها أكثر صعوبة. ومع ذلك ، ما زلت قلقًا من أن الناس سيحاولون إجراء مكالمات معًا لعمل تعليمات برمجية سيئة ، مثل try كتعبير _ يشجع_ على هذا السلوك في عيني.

أعتقد أنني سأكون ممتنًا لهذا الاقتراح أكثر قليلاً إذا تم حظر المكالمات golint المتداخلة try المكالمات. أعتقد أن حظر جميع مكالمات try داخل التعبيرات الأخرى صارم للغاية بعض الشيء ، فوجود try كتعبير له مزاياه.

استعارة المثال الخاص بك ، حتى مجرد إجراء مكالمات متداخلة 2 معًا تبدو بشعة للغاية ، ويمكنني رؤية مبرمجي Go يقومون بذلك ، خاصةً إذا كانوا يعملون بدون مراجعي الكود.

parentCommitOne := try(remoteBranch.Reference.Peel(git.ObjectCommit)).AsCommit()
parentCommitTwo := try(try(r.Head()).Peel(git.ObjectCommit)).AsCommit()
tree := try(r.LookupTree(try(index.WriteTree())))

في الواقع ، بدا المثال الأصلي لطيفًا للغاية ، لكن هذا المثال يوضح أن تداخل تعبيرات المحاولة (حتى لو كانت فقط بعمق 2) يضر حقًا بقراءة الكود بشكل كبير. سيساعد أيضًا رفض المكالمات المتداخلة try في حل مشكلة "التصحيح" ، نظرًا لأنه من الأسهل بكثير توسيع try إلى if إذا كان موجودًا خارج التعبير.

مرة أخرى ، أود تقريبًا أن أقول أنه يجب وضع علامة على try داخل تعبير فرعي بواسطة golint ، لكنني أعتقد أن هذا قد يكون صارمًا بعض الشيء. من شأنه أيضًا وضع علامة على رمز مثل هذا ، وهو أمر جيد في نظري:

x := 5 + try(strconv.Atoi(input))

بهذه الطريقة ، نحصل على مزايا استخدام try كتعبير ، لكننا لا نشجع على إضافة الكثير من التعقيد إلى المحور الأفقي.

ربما يكون الحل الآخر هو أن golint يجب أن يسمح فقط بحد أقصى 1 try لكل بيان ، لكن الوقت متأخر ، لقد أصبت بالتعب ، وأحتاج إلى التفكير في الأمر بشكل أكثر عقلانية. في كلتا الحالتين ، كنت سلبيًا تمامًا تجاه هذا الاقتراح في بعض النقاط ، لكنني أعتقد أنه يمكنني في الواقع أن أتحول إلى الإعجاب به حقًا طالما أن هناك بعض المعايير golint المتعلقة به.

rsc

يمكننا ويجب علينا التمييز بين _ "يمكن استخدام هذه الميزة لكتابة تعليمات برمجية مقروءة جدًا ، ولكن يمكن أيضًا إساءة استخدامها لكتابة رمز غير قابل للقراءة" _ و "الاستخدام السائد لهذه الميزة هو كتابة رمز غير قابل للقراءة".
تجربة مع C تشير إلى ذلك؟ : يقع مباشرة في الفئة الثانية. (باستثناء احتمال min و max ،

ما أدهشني أولاً حول try() - مقابل try كبيان - هو مدى تشابهها في التداخل مع عامل التشغيل الثلاثي ، ومع ذلك ، كيف تتعارض مع وسيطات try() وضد ثلاثي تم _ (أعيدت صياغته): _

  • ثلاثي: _ "إذا سمحنا بذلك ، فسيقوم الأشخاص بتداخله وستكون النتيجة الكثير من الشفرات السيئة" _ متجاهلًا أن بعض الأشخاص يكتبون تعليمات برمجية أفضل معهم ، مقابل
  • try(): _ "يمكنك تداخله ، لكننا نشك في أن الكثيرين سيفعلون ذلك لأن معظم الناس يريدون كتابة رمز جيد" _،

مع الاحترام ، يبدو هذا الاختلاف المنطقي بين الاثنين أمرًا شخصيًا للغاية ، وأود أن أطلب بعض التأمل والتفكير على الأقل فيما إذا كنت قد تكون عقلانيًا للاختلاف في الميزة التي تفضلها مقابل ميزة لا تحبها؟ #please_dont_shoot_the_messenger

_ "لست متأكدًا من أنني رأيت رمزًا يستخدم؟: لم يتم تحسين ذلك بإعادة كتابته لاستخدام عبارة if بدلاً من ذلك. ولكن هذه الفقرة تخرج عن الموضوع.)" _

في لغات أخرى ، أقوم بتحسين العبارات بشكل متكرر عن طريق إعادة كتابتها من if إلى مشغل ثلاثي ، على سبيل المثال من الكود الذي كتبته اليوم بلغة PHP:

return isset( $_COOKIE[ CookieNames::CART_ID ] )
    ? intval( $_COOKIE[ CookieNames::CART_ID ] )
    : null;

قارن ب:

if ( isset( $_COOKIE[ CookieNames::CART_ID ] ) ) {
    return intval( $_COOKIE[ CookieNames::CART_ID ] );
} else { 
    return null;
}

وبقدر ما أشعر بالقلق ، فإن الأول تحسن كثيرًا عن الثاني.

fwiw

أعتقد أن الانتقاد الموجه لهذا الاقتراح يرجع إلى حد كبير إلى التوقعات العالية التي أثارها الاقتراح السابق ، والتي كان من الممكن أن تكون أكثر شمولاً. ومع ذلك ، أعتقد أن هذه التوقعات العالية كانت مبررة لأسباب تتعلق بالاتساق. أعتقد أن ما كان كثير من الناس يودون رؤيته هو بناء واحد شامل لمعالجة الأخطاء يكون مفيدًا في جميع حالات الاستخدام.

قارن هذه الميزة ، على سبيل المثال ، بالوظيفة المضمنة في append() . تم إنشاء الإلحاق لأن الإلحاق بالشريحة كان حالة استخدام شائعة جدًا ، وبينما كان من الممكن القيام بذلك يدويًا ، كان من السهل أيضًا القيام بذلك بشكل خاطئ. يسمح الآن append() بإلحاق ليس عنصرًا واحدًا فقط ، بل العديد من العناصر ، أو حتى شريحة كاملة ، كما يسمح بإلحاق سلسلة إلى [] شريحة بايت. إنه قوي بما يكفي لتغطية جميع حالات استخدام إلحاق شريحة. وبالتالي ، لم يعد أحد يلحق الشرائح يدويًا.

ومع ذلك ، فإن try() مختلف. إنها ليست قوية بما يكفي حتى نتمكن من استخدامها في جميع حالات معالجة الأخطاء. وأعتقد أن هذا هو أخطر عيب في هذا الاقتراح. تعد الوظيفة المضمنة try() مفيدة حقًا فقط ، بمعنى أنها تقلل من المستوى المعياري ، في أبسط الحالات ، أي مجرد تمرير خطأ إلى المتصل ، ومع بيان تأجيل ، إذا كانت جميع أخطاء يجب التعامل مع الوظيفة بنفس الطريقة.

لمعالجة الأخطاء الأكثر تعقيدًا ، سنظل بحاجة إلى استخدام if err != nil {} . يؤدي هذا بعد ذلك إلى نمطين متميزين لمعالجة الأخطاء ، حيث كان هناك نمط واحد فقط من قبل. إذا كان هذا الاقتراح هو كل ما نحصل عليه للمساعدة في معالجة الأخطاء في Go ، إذن ، أعتقد أنه سيكون من الأفضل عدم فعل أي شيء والاستمرار في التعامل مع الخطأ مع if كما هو الحال دائمًا ، لأنه على الأقل ، هذا متسق واستفاد من "هناك طريقة واحدة فقط للقيام بذلك".

rsc ، نعتذر عن الانحراف عن الموضوع!
لقد رفعت معالجات مستوى الحزمة في # 32437 (تعليق)
ورد على طلبك للتوضيح في # 32437 (تعليق)

أرى معالجات مستوى الحزمة كميزة يمكن لأي شخص تقريبًا أن يتخلف عنها.

لا أرى ما يربط بين مفهوم الحزمة ومعالجة الأخطاء المحددة. من الصعب تخيل أن يكون مفهوم معالج مستوى الحزمة مفيدًا ، على سبيل المثال ، net/http . في سياق مماثل ، على الرغم من كتابة حزم أصغر من net/http بشكل عام ، لا يمكنني التفكير في حالة استخدام واحدة حيث كنت أفضل بناء على مستوى الحزمة للقيام بمعالجة الأخطاء. بشكل عام ، وجدت أن الافتراض القائل بأن كل شخص يشارك الخبرات ، وحالات الاستخدام ، والآراء يعد أمرًا خطيرًا :)

beoran أعتقد أن هذا الاقتراح يجعل المزيد من التحسين ممكنًا. مثل مصمم الديكور في الوسيطة الأخيرة try(..., func(err) error) ، أو tryf(..., "context of my error: %w") ؟

flibustenet بينما يمكن أن تكون مثل هذه الامتدادات اللاحقة ممكنة ، يبدو أن الاقتراح كما هو الآن يثبط مثل هذه الامتدادات ، غالبًا لأن إضافة معالج خطأ سيكون زائداً عن الحاجة مع التأجيل.

أعتقد أن المشكلة الصعبة هي كيفية الحصول على معالجة شاملة للأخطاء دون تكرار وظيفة defe. ربما يمكن تحسين بيان التأجيل نفسه بطريقة ما للسماح بمعالجة الأخطاء بشكل أسهل في الحالات الأكثر تعقيدًا ... لكن هذه مشكلة مختلفة.

https://github.com/golang/go/issues/32437#issuecomment -502975437

يؤدي هذا بعد ذلك إلى نمطين متميزين لمعالجة الأخطاء ، حيث كان هناك نمط واحد فقط من قبل. إذا كان هذا الاقتراح هو كل ما نحصل عليه للمساعدة في معالجة الأخطاء في Go ، إذن ، أعتقد أنه سيكون من الأفضل عدم فعل أي شيء والاستمرار في التعامل مع الخطأ مع if كما هو الحال دائمًا ، لأنه على الأقل ، هذا متسق واستفاد من "هناك طريقة واحدة فقط للقيام بذلك".

Beoran متفق عليه. لهذا السبب اقترحت توحيد الغالبية العظمى من حالات الخطأ تحت الكلمة الرئيسية try ( try و try / else ). على الرغم من أن بناء الجملة try / else لا يمنحنا أي انخفاض كبير في طول الكود مقابل النمط الحالي if err != nil ، فإنه يمنحنا الاتساق مع try حالة else ). من المرجح أن تغطي هاتان الحالتان (حاول وتجربة أخرى) الغالبية العظمى من حالات معالجة الأخطاء. أضع ذلك على عكس الإصدار المدمج الذي لا يتضمن أي شيء آخر try والذي ينطبق فقط في الحالات التي لا يقوم فيها المبرمج فعليًا بأي شيء لمعالجة الخطأ إلى جانب الإرجاع (والذي ، كما ذكر آخرون في هذا الموضوع ، ليس بالضرورة شيئًا نريد حقًا تشجيعه في المقام الأول).

الاتساق مهم لسهولة القراءة.

append هي الطريقة المحددة لإضافة عناصر إلى شريحة. make هي الطريقة المحددة لإنشاء قناة أو خريطة أو شريحة جديدة (باستثناء العناصر الحرفية التي لا أشعر بالسعادة حيالها). لكن try() (كمدمج ، وبدون else ) سيتم نثرها في جميع أنحاء قواعد الكود ، اعتمادًا على كيفية حاجة المبرمج للتعامل مع خطأ معين ، بطريقة ربما تكون فوضوية بعض الشيء ومربكة القارئ. لا يبدو أنه يتماشى مع روح البنايات الأخرى (أي التعامل مع قضية إما صعبة للغاية أو مستحيلة تمامًا القيام بها بطريقة أخرى). إذا كان هذا هو إصدار try الذي نجح ، فإن الاتساق وسهولة القراءة سيجبرني على عدم استخدامه ، تمامًا كما أحاول تجنب القيم الحرفية للخريطة / الشرائح (وتجنب new مثل الطاعون).

إذا كانت الفكرة هي تغيير كيفية معالجة الأخطاء ، فيبدو من الحكمة محاولة توحيد النهج عبر أكبر عدد ممكن من الحالات ، بدلاً من إضافة شيء ، في أحسن الأحوال ، سيكون "تقبله أو اتركه". أخشى أن هذا الأخير سيضيف ضوضاء بالفعل بدلاً من تقليله.

كتب deanveloper :

أعتقد أنني سأكون ممتنًا لهذا الاقتراح أكثر قليلاً إذا حظرت golint مكالمات المحاولة المتداخلة.

أوافق على أنه من الصعب قراءة try المتداخلة بشدة. ولكن هذا صحيح أيضًا بالنسبة لاستدعاءات الوظائف القياسية ، وليس فقط الوظيفة المضمنة try . وبالتالي لا أفهم لماذا يجب أن يحظر golint هذا.

كتب brynbellomy :

على الرغم من أن بناء جملة try / else لا يمنحنا أي انخفاض كبير في طول الكود مقابل الموجود في حالة الخطأ!

الهدف الفريد للوظيفة المضمنة try هو تقليل الصيغة المعيارية ، لذلك من الصعب معرفة سبب اعتماد صيغة try / else التي تقترحها عندما تقر بأنه "لا يمنحنا أي تخفيض كبير في طول الكود ".

لقد ذكرت أيضًا أن الصيغة التي تقترحها تجعل حالة المحاولة متوافقة مع حالة try / else. ولكنه يخلق أيضًا طريقة غير متسقة للتفرع ، عندما يكون لدينا بالفعل if / else. تحصل على قدر من التناسق في حالة استخدام معينة ولكن تفقد الكثير من التناقض في حالة الاستخدام الأخرى.

أشعر بالحاجة إلى التعبير عن آرائي لما تستحقه. على الرغم من أن كل هذا ليس أكاديميًا وتقنيًا بطبيعته ، أعتقد أنه يجب أن يقال.

أعتقد أن هذا التغيير هو أحد هذه الحالات حيث تتم الهندسة من أجل الهندسة ويتم استخدام "التقدم" للتبرير. لم يتم كسر معالجة الأخطاء في Go وهذا الاقتراح ينتهك الكثير من فلسفة التصميم التي أحبها في Go.

اجعل الأمور سهلة الفهم ، وليس من السهل القيام بها
هذا الاقتراح يختار تحسين الكسل على الصواب. ينصب التركيز على تسهيل معالجة الأخطاء وفي المقابل يتم فقد قدر كبير من قابلية القراءة. الطبيعة المملة العرضية لمعالجة الأخطاء مقبولة بسبب مكاسب قابلية القراءة وقابلية التصحيح.

تجنب تسمية الحجج العودة
هناك عدد قليل من حالات الحافة مع عبارات defer حيث تكون تسمية وسيطة الإرجاع صحيحة. خارج هذه ، ينبغي تجنب ذلك. يشجع هذا الاقتراح على استخدام حجج عودة التسمية. لن يساعد هذا في جعل كود Go أكثر قابلية للقراءة.

يجب أن ينشئ التغليف دلالات جديدة حيث يكون المرء دقيقًا تمامًا
لا توجد دقة في بناء الجملة الجديد هذا. لا يساعد إخفاء متغير الخطأ والعائد في تسهيل فهم الأمور. في الواقع ، يبدو بناء الجملة غريبًا جدًا عن أي شيء نقوم به في Go اليوم. إذا كتب شخص ما وظيفة مماثلة ، أعتقد أن المجتمع سيوافق على أن التجريد يخفي التكلفة ولا يستحق البساطة التي يحاول توفيرها.

من نحاول مساعدته؟
أشعر بالقلق من تنفيذ هذا التغيير في محاولة لإغراء مطوري المؤسسات بعيدًا عن لغاتهم الحالية والالتحاق بـ Go. تطبيق التغييرات اللغوية ، فقط لزيادة الأعداد ، يشكل سابقة سيئة. أعتقد أنه من العدل طرح هذا السؤال والحصول على إجابة لمشكلة العمل التي تحاول حلها والمكاسب المتوقعة التي تحاول تحقيقها؟

لقد رأيت هذا من قبل عدة مرات الآن. يبدو واضحًا تمامًا ، مع كل الأنشطة الأخيرة من فريق اللغة ، فإن هذا الاقتراح ثابت بشكل أساسي. هناك المزيد من الدفاع عن التنفيذ ثم النقاش الفعلي حول التنفيذ نفسه. بدأ كل هذا قبل 13 يومًا. سنرى تأثير هذا التغيير على اللغة والمجتمع ومستقبل Go.

لم يتم كسر معالجة الأخطاء في Go وهذا الاقتراح ينتهك الكثير من فلسفة التصميم التي أحبها في Go.

يعبر بيل عن أفكاري تمامًا.

لا يمكنني إيقاف تقديم try ، لكن إذا كان الأمر كذلك ، فلن أستخدمه بنفسي ؛ لن أقوم بتدريسها ، ولن أقبلها في العلاقات العامة التي أراجعها. ستتم إضافته ببساطة إلى قائمة "الأشياء الأخرى في Go I never use" (انظر حديث Mat Ryer الممتع على YouTube للمزيد من هذه الأشياء).

@ ardan-bkennedy ، شكراً لتعليقاتكم.

لقد سألت عن "مشكلة العمل التي تحاول حلها". لا أعتقد أننا نستهدف مشاكل أي عمل معين باستثناء ربما "Go program". لكن بشكل عام أوضحنا المشكلة التي نحاول حلها في أغسطس الماضي في بدء مناقشة مسودة تصميم Gophercon (راجع نظرة عامة على المشكلة وخاصة قسم الأهداف). حقيقة أن هذه المحادثة مستمرة منذ أغسطس الماضي تتعارض بشكل قاطع مع ادعائك بأن "كل هذا بدأ منذ 13 يومًا".

أنت لست الشخص الوحيد الذي اقترح أن هذه ليست مشكلة أو ليست مشكلة تستحق الحل. راجع https://swtch.com/try.html#nonissue للحصول على تعليقات أخرى من هذا القبيل. لقد لاحظنا ذلك ونريد التأكد من أننا نحل مشكلة فعلية. جزء من طريقة معرفة ذلك هو تقييم الاقتراح على أسس التعليمات البرمجية الحقيقية. أدوات مثل Robert's tryhard تساعدنا على القيام بذلك. لقد طلبت سابقًا من الأشخاص إعلامنا بما يجدون في قواعد التعليمات البرمجية الخاصة بهم. ستكون هذه المعلومات مهمة للغاية لتقييم ما إذا كان التغيير يستحق العناء أم لا. لديك تخمين واحد ولدي تخمين مختلف ، ولا بأس بذلك. الجواب هو استبدال البيانات لتلك التخمينات.

سنفعل ما هو مطلوب للتأكد من أننا نحل مشكلة فعلية.

مرة أخرى ، المسار إلى الأمام عبارة عن بيانات تجريبية ، وليس تفاعلات القناة الهضمية. لسوء الحظ ، تتطلب البيانات مزيدًا من الجهد لجمعها. في هذه المرحلة ، أود أن أشجع الأشخاص الذين يرغبون في المساعدة على الخروج وجمع البيانات.

@ ardan-bkennedy ، آسف على المتابعة الثانية لكن بخصوص:

أشعر بالقلق من تنفيذ هذا التغيير في محاولة لإغراء مطوري المؤسسات بعيدًا عن لغاتهم الحالية والالتحاق بـ Go. تطبيق التغييرات اللغوية ، فقط لزيادة الأعداد ، يشكل سابقة سيئة.

هناك مشكلتان خطيرتان مع هذا الخط لا يمكنني تجاوزهما.

أولاً ، أرفض الادعاء الضمني بوجود فئات من المطورين - في هذه الحالة "مطورو المؤسسات" - لا يستحقون بطريقة ما استخدام Go أو النظر في مشاكلهم. في الحالة المحددة "للمؤسسة" ، نرى الكثير من الأمثلة للشركات الصغيرة والكبيرة التي تستخدم Go بشكل فعال للغاية.

ثانيًا ، منذ بداية مشروع Go ، قمنا - روبرت وروب وكين وإيان وأنا - بتقييم التغييرات والميزات اللغوية بناءً على خبرتنا الجماعية في بناء العديد من الأنظمة. نسأل "هل يعمل هذا بشكل جيد في البرامج التي نكتبها؟" لقد كانت هذه وصفة ناجحة مع إمكانية تطبيق واسعة وهي الطريقة التي نعتزم الاستمرار في استخدامها ، معززًا مرة أخرى بالبيانات التي طلبتها في التعليق السابق وتقارير الخبرة بشكل عام. لن نقترح أو ندعم تغييرًا للغة لا يمكننا أن نرى أنفسنا نستخدمه في برامجنا الخاصة أو لا نعتقد أنه يتناسب جيدًا مع Go. ونحن بالتأكيد لن نقترح أو ندعم تغييرًا سيئًا لمجرد أن يكون لدينا المزيد من مبرمجي Go. نحن نستخدم Go أيضًا بعد كل شيء.

rsc
لن يكون هناك نقص في المواقع حيث يمكن توفير هذه الراحة. ما هو المقياس المطلوب لإثبات جوهر الآلية بخلاف ذلك؟ هل توجد قائمة بحالات معالجة الأخطاء المصنفة؟ كيف ستُشتق القيمة من البيانات عندما تكون المشاعر مدفوعة بمعظم العملية العامة؟

الأدوات tryhard مفيدة للغاية!
استطعت أن أرى أنني أستخدم في كثير من الأحيان return ...,err ، ولكن فقط عندما أعرف أنني أستدعي دالة تقوم بالفعل بلف الخطأ (مع pkg/errors ) ، معظمها في معالجات http. فزت في سهولة القراءة بسطر أقل من التعليمات البرمجية.
ثم في معالج http الأطروحات ، أود إضافة defer fmt.HandleErrorf(&err, "handler xyz") وأخيرًا إضافة سياق أكثر من ذي قبل.

أرى أيضًا الكثير من الحالات التي لا أهتم فيها بالخطأ على الإطلاق fmt.Printf وسأفعل ذلك بـ try .
هل سيكون من الممكن على سبيل المثال القيام بـ defer try(f.Close()) ؟

لذلك ، ربما يساعد try أخيرًا في إضافة سياق ودفع أفضل الممارسات بدلاً من العكس.

أنا صبور جدا للاختبار في الواقع!

flibustenet لن يسمح الاقتراح كما هو defer try(f()) (انظر الأساس المنطقي ). هناك كل أنواع المشاكل مع ذلك.

عند استخدام هذه الأداة tryhard لمشاهدة التغييرات في قاعدة بيانات ، هل يمكننا أيضًا مقارنة النسبة if err != nil قبل وبعد لمعرفة ما إذا كان من الشائع إضافة سياق أو مجرد تمرير الخطأ مرة أخرى؟

تفكيري هو أنه ربما يمكن لمشروع ضخم افتراضي أن يرى 1000 مكان حيث تمت إضافة try() ولكن هناك 10000 if err != nil التي تضيف سياق لذلك على الرغم من أن 1000 تبدو ضخمة ، فهي فقط 10٪ من الشيء الكامل .

Goodwine نعم. ربما لن أتمكن من إجراء هذا التغيير هذا الأسبوع ، لكن الكود بسيط جدًا ومكتفي بذاته. لا تتردد في تجربته (لا يقصد التورية) واستنساخه وضبطه حسب الحاجة.

لن يكون defer try(f()) معادلاً لـ

defer func() error {
    if err:= f(); err != nil { return err }
    return nil
}()

هذا (إصدار if) غير مسموح به حاليًا ، أليس كذلك؟ يبدو لي أنه لا يجب إجراء استثناء هنا - هل يمكن إنشاء تحذير؟ وليس من الواضح ما إذا كان رمز التأجيل أعلاه خاطئًا بالضرورة. ماذا لو فشل close(file) في كشف حساب defer ؟ هل يجب الإبلاغ عن هذا الخطأ أم لا؟

قرأت الأساس المنطقي الذي يبدو أنه يتحدث عن defer try(f) وليس defer try(f()) . قد يكون خطأ مطبعي؟

يمكن عمل حجة مماثلة لـ go try(f()) ، والتي تُترجم إلى

go func() error {
    if err:= f(); err != nil { return err }
    return nil
}()

هنا try لا يفعل شيئًا مفيدًا ولكنه غير ضار.

@ ardan-bkennedy شكرا لأفكارك. مع كل الاحترام الواجب ، أعتقد أنك أساءت تمثيل الغرض من هذا الاقتراح وقدمت عدة ادعاءات لا أساس لها .

فيما يتعلق ببعض النقاط التي لم يتناولها rsc سابقًا:

  • لم نقول أبدًا أن معالجة الأخطاء معطلة. يعتمد التصميم على الملاحظة (من قبل مجتمع Go!) أن المعالجة الحالية جيدة ، لكنها مطولة في كثير من الحالات - وهذا بلا منازع. هذا هو الافتراض الرئيسي للاقتراح.

  • إن تسهيل الأمور يمكن أن يسهل فهمها أيضًا - هذان الاثنان لا يستبعدان بعضهما البعض ، أو حتى يعنيان بعضهما البعض. أحثك على إلقاء نظرة على هذا الرمز كمثال. يؤدي استخدام try إلى إزالة قدر كبير من النموذج المعياري ، ولا يضيف هذا النموذج المعياري شيئًا تقريبًا إلى قابلية فهم الكود. يعد استبعاد الكود التكراري من الممارسات الترميز القياسية والمقبولة على نطاق واسع لتحسين جودة الكود.

  • فيما يتعلق بـ "هذا الاقتراح ينتهك الكثير من فلسفة التصميم": المهم هو أننا لا نتشبث بفلسفة التصميم - وهذا غالبًا ما يكون سقوط الأفكار الجيدة (بالإضافة إلى ذلك ، أعتقد أننا نعرف شيئًا أو شيئين عن فلسفة تصميم Go). هناك الكثير من "الحماسة الدينية" (لعدم وجود مصطلح أفضل) حول معلمات النتائج المسماة مقابل غير المسماة. عبارات مثل "يجب ألا تستخدم معلمات نتيجة مسماة أبدًا" خارج السياق لا معنى لها. قد تكون بمثابة مبادئ توجيهية عامة ، لكنها ليست حقائق مطلقة. معلمات النتائج المسماة ليست "سيئة" بطبيعتها. يمكن أن تضيف معلمات النتائج ذات الأسماء الجيدة إلى وثائق API بطرق مفيدة. باختصار ، دعونا لا نستخدم الشعارات لاتخاذ قرارات تصميم اللغة.

  • من وجهة هذا الاقتراح عدم إدخال بناء جملة جديد. إنه يقترح فقط وظيفة جديدة. لا يمكننا كتابة هذه الوظيفة في اللغة ، لذا فإن العنصر المدمج هو المكان الطبيعي لها في Go. إنها ليست وظيفة بسيطة فحسب ، بل يتم تعريفها بدقة شديدة. نختار هذا النهج البسيط بدلاً من الحلول الأكثر شمولاً لأنه يقوم بشيء واحد بشكل جيد للغاية ولا يترك شيئًا تقريبًا لقرارات التصميم التعسفية. نحن أيضًا لسنا بعيدًا عن المسار المطروق نظرًا لأن اللغات الأخرى (مثل Rust) لها بنى متشابهة جدًا. إن اقتراح أن "المجتمع سيوافق على أن التجريد يخفي التكلفة ولا يستحق البساطة التي يحاول توفيرها" هو وضع الكلمات في فم الآخرين. بينما يمكننا أن نسمع بوضوح المعارضين الصريحين لهذا الاقتراح ، هناك نسبة كبيرة (تقدر بنحو 40٪) من الأشخاص الذين أعربوا عن موافقتهم على المضي قدمًا في التجربة. دعونا لا نحرمهم من حقوقهم بالمبالغة.

شكرا.

return isset( $_COOKIE[ CookieNames::CART_ID ] )
    ? intval( $_COOKIE[ CookieNames::CART_ID ] )
    : null;

متأكد تمامًا أن هذا يجب أن يكون return intval( $_COOKIE[ CookieNames::CART_ID ] ) ?? null; FWIW. 😁

bakul نظرًا لأنه يتم تقييم الحجج على الفور ، فهي في الواقع تعادل تقريبًا:

<result list> := f()
defer try(<result list>)

قد يكون هذا سلوكًا غير متوقع للبعض حيث لم يتم تأجيل f() لوقت لاحق ، يتم تنفيذه على الفور. نفس الشيء ينطبق على go try(f()) .

bakul يذكر المستند defer try(f) (بدلاً من defer try(f()) لأن try بشكل عام ينطبق على أي تعبير ، وليس مجرد استدعاء دالة (يمكنك أن تقول try(err) لـ على سبيل المثال ، إذا كان err من النوع error ). إذن ليس خطأ مطبعي ، ولكن ربما يكون محيرًا في البداية. f يشير ببساطة إلى تعبير ، والذي يحدث عادةً ليكون دالة يتصل.

deanveloper ، griesemer لا تهتم :-) شكرًا.

@ carl-mastrangelo

_ "متأكد أن هذا يجب أن يكون return intval( $_COOKIE[ CookieNames::CART_ID ] ) ?? null; _

أنت تفترض PHP 7.x. لم اكن. ولكن بعد ذلك مرة أخرى ، نظرًا لوجهك المتعثر ، فأنت تعلم أن هذا لم يكن هو الهدف. :غمزة:

أقوم بتحضير عرض توضيحي قصير لعرض هذه المناقشة خلال لقاء الذهاب الذي سيُعقد غدًا ، وسماع بعض الأفكار الجديدة ، حيث أعتقد أن معظم المشاركين في هذا الموضوع (المساهمون أو المراقبون) هم أولئك الذين يشاركون بشكل أعمق في اللغة ، و على الأرجح "ليس المطور العادي" (مجرد حدس).

أثناء القيام بذلك ، تذكرت أنه كان لدينا بالفعل لقاء حول الأخطاء ومناقشة حول نمطين:

  1. قم بتوسيع بنية الخطأ أثناء دعم واجهة الخطأ mystruct.Error ()
  2. قم بتضمين الخطأ إما كحقل أو حقل مجهول للبنية
type ExtErr struct{
  error
  someOtherField string
}  

يتم استخدام هذه في مجموعات قليلة أنشأتها فريقي بالفعل.

ينص الاقتراح سؤال وجواب
س: يجب أن تكون الوسيطة الأخيرة التي تم تمريرها للمحاولة من نوع الخطأ. لماذا لا يكفي أن تكون الوسيطة الواردة قابلة للتخصيص للخطأ؟
ج: "... يمكننا إعادة النظر في هذا القرار في المستقبل إذا لزم الأمر"

هل يمكن لأي شخص التعليق على حالات الاستخدام المماثلة حتى نتمكن من فهم ما إذا كانت هذه الحاجة شائعة لكلا خياري تمديد الخطأ أعلاه؟

mikeschinkel لست كارل الذي تبحث عنه.

daved ، إعادة:

لن يكون هناك نقص في المواقع حيث يمكن توفير هذه الراحة. ما هو المقياس المطلوب لإثبات جوهر الآلية بخلاف ذلك؟ هل توجد قائمة بحالات معالجة الأخطاء المصنفة؟ كيف ستُشتق القيمة من البيانات عندما تكون المشاعر مدفوعة بمعظم العملية العامة؟

يعتمد القرار على مدى نجاح هذا في البرامج الحقيقية. إذا أظهر لنا الأشخاص أن المحاولة غير فعالة في الجزء الأكبر من التعليمات البرمجية الخاصة بهم ، فهذه بيانات مهمة. العملية مدفوعة بهذا النوع من البيانات. إنه ليس مدفوعًا بالمشاعر.

سياق الخطأ

إن أهم المخاوف الدلالية التي أثيرت في هذه المشكلة هي ما إذا كانت المحاولة ستشجع التعليقات التوضيحية الأفضل أو الأسوأ للأخطاء في السياق.

نظرة عامة على المشكلة من أغسطس الماضي يعطي سلسلة من أمثلة تطبيقات CopyFile في أقسام المشكلة والأهداف. من الأهداف الصريحة ، في ذلك الوقت واليوم ، أن يجعل أي حل من المرجح أن يضيف المستخدمون سياقًا مناسبًا إلى الأخطاء. ونعتقد أن المحاولة يمكن أن تفعل ذلك ، أو لم نكن لنقترحها.

ولكن قبل أن نحاول ، يجدر بنا التأكد من أننا جميعًا على نفس الصفحة حول سياق الخطأ المناسب. المثال الأساسي هو os.Open. نقلاً عن منشور مدونة Go " معالجة الأخطاء والذهاب ":

تقع على عاتق تنفيذ الخطأ مسؤولية تلخيص السياق.
الخطأ المُرجع بواسطة تنسيقات os.Open كـ "open / etc / passwd: تم رفض الإذن" وليس فقط "تم رفض الإذن".

راجع أيضًا قسم Effective Go's حول الأخطاء .

لاحظ أن هذا الاصطلاح قد يختلف عن اللغات الأخرى المألوفة لديك ، كما أنه لا يتم اتباعه إلا بشكل غير متسق في كود Go. يتمثل الهدف الواضح لمحاولة تبسيط معالجة الأخطاء في تسهيل اتباع الأشخاص لهذه الاتفاقية وإضافة سياق مناسب ، وبالتالي جعلها متبعة بشكل أكثر اتساقًا.

يوجد الكثير من التعليمات البرمجية التي تتبع اصطلاح Go اليوم ، ولكن هناك أيضًا الكثير من التعليمات البرمجية التي تفترض الاصطلاح المعاكس. من الشائع جدًا رؤية رمز مثل:

f, err := os.Open(file)
if err != nil {
    log.Fatalf("opening %s: %v", file, err)
}

التي تطبع نفس الشيء مرتين بالطبع (العديد من الأمثلة في هذه المناقشة بالذات تبدو هكذا). جزء من هذا الجهد يجب أن يكون التأكد من أن الجميع يعرف ويتبع الاتفاقية.

في التعليمات البرمجية التي تتبع اصطلاح سياق الخطأ Go ، نتوقع أن تضيف معظم الوظائف نفس السياق بشكل صحيح إلى كل إرجاع خطأ ، بحيث يتم تطبيق زخرفة واحدة بشكل عام. على سبيل المثال ، في مثال CopyFile ، ما يجب إضافته في كل حالة هو تفاصيل حول ما تم نسخه. قد تضيف عمليات الإرجاع المحددة الأخرى مزيدًا من السياق ، ولكن عادةً ما يتم إضافتها بدلاً من استبدالها. إذا كنا مخطئين بشأن هذا التوقع ، فمن الجيد معرفة ذلك. قد يساعد الدليل الواضح من قواعد التعليمات البرمجية الحقيقية.

كان من الممكن أن يستخدم تصميم مسودة الفحص / المقبض Gophercon رمزًا مثل:

func CopyFile(src, dst string) error {
    handle err {
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }

    r := check os.Open(src)
    defer r.Close()

    w := check os.Create(dst)
    ...
}

لقد قام هذا الاقتراح بمراجعة ذلك ، لكن الفكرة هي نفسها:

func CopyFile(src, dst string) (err error) {
    defer func() {
        if err != nil {
            err = fmt.Errorf("copy %s %s: %v", src, dst, err)
        }
    }()

    r := try(os.Open(src))
    defer r.Close()

    w := try(os.Create(dst))
    ...
}

ونريد إضافة مساعد غير مسمى لهذا النمط الشائع:

func CopyFile(src, dst string) (err error) {
    defer HelperToBeNamedLater(&err, "copy %s %s", src, dst)

    r := try(os.Open(src))
    defer r.Close()

    w := try(os.Create(dst))
    ...
}

باختصار ، تعتمد معقولية هذا النهج ونجاحه على هذه الافتراضات والخطوات المنطقية:

  1. يجب على الأشخاص اتباع اصطلاح Go المذكور "يضيف المستدعي سياقًا ذا صلة يعرفه".
  2. لذلك ، تحتاج معظم الوظائف فقط إلى إضافة سياق على مستوى الوظيفة يصف الإجمالي
    العملية ، وليس القطعة الفرعية المحددة التي فشلت (تلك القطعة الفرعية التي تم الإبلاغ عنها ذاتيًا بالفعل).
  3. لا يضيف كود Much Go اليوم سياق مستوى الوظيفة لأنه متكرر للغاية.
  4. إن توفير طريقة لكتابة السياق على مستوى الوظيفة مرة واحدة سيزيد من احتمالية ذلك
    المطورين يفعلون ذلك.
  5. ستكون النتيجة النهائية هي المزيد من التعليمات البرمجية Go التي تتبع الاصطلاح وإضافة السياق المناسب.

إذا كان هناك افتراض أو خطوة منطقية تعتقد أنها خاطئة ، فنحن نريد أن نعرف. وأفضل طريقة لإخبارنا هي الإشارة إلى الدليل في قواعد التعليمات البرمجية الفعلية. أظهر لنا الأنماط الشائعة لديك حيث تكون المحاولة غير مناسبة أو تجعل الأمور أسوأ. اعرض لنا أمثلة على الأشياء التي كانت المحاولة فيها أكثر فعالية مما توقعت. حاول تحديد مقدار قاعدة الشفرة التي تقع على جانب أو آخر. وما إلى ذلك وهلم جرا. البيانات مهمة.

شكرا.

شكرًا rsc على المعلومات الإضافية حول أفضل الممارسات لسياق الخطأ. لقد ألمحتني هذه النقطة المتعلقة بأفضل الممارسات على وجه الخصوص ، ولكنها تحسن بشكل ملحوظ علاقة try s بسياق الخطأ.

لذلك ، تحتاج معظم الوظائف فقط إلى إضافة سياق على مستوى الوظيفة يصف الإجمالي
العملية ، وليس القطعة الفرعية المحددة التي فشلت (تلك القطعة الفرعية التي تم الإبلاغ عنها ذاتيًا بالفعل).

إذن ، المكان الذي لا يساعد فيه try هو عندما نحتاج إلى الرد على الأخطاء ، وليس فقط وضعها في سياقها.

لتكييف مثال من Cleaner ، أكثر أناقة ، وخاطئًا ، إليك مثالهم لوظيفة خاطئة بمهارة في معالجة الأخطاء. لقد قمت بتكييفه مع Go باستخدام غلاف الخطأ try و defer غرار التفاف:

func AddNewGuy(name string) (guy Guy, err error) {
    defer func() {
        if err != nil {
            err = fmt.Errorf("adding guy %v: %v", name, err)
        }
    }()

    guy = Guy{name: name}
    guy.Team = ChooseRandomTeam()
    try(guy.Team.Add(guy))
    try(AddToLeague(guy))
    return guy, nil
}

هذه الوظيفة غير صحيحة لأنه إذا نجح guy.Team.Add(guy) ولكن فشل AddToLeague(guy) ، فسيكون لدى الفريق كائن Guy غير صالح ليس في دوري. سيبدو الرمز الصحيح على هذا النحو ، حيث استرجعنا guy.Team.Add(guy) ولم يعد بإمكاننا استخدام try :

func AddNewGuy(name string) (guy Guy, err error) {
    defer func() {
        if err != nil {
            err = fmt.Errorf("adding guy %v: %v", name, err)
        }
    }()

    guy = Guy{name: name}
    guy.Team = ChooseRandomTeam()
    try(guy.Team.Add(guy))
    if err := AddToLeague(guy); err != nil {
        guy.Team.Remove(guy)
        return Guy{}, err
    }
    return guy, nil
}

أو ، إذا أردنا تجنب الاضطرار إلى تقديم قيم صفرية لقيم الإرجاع بدون أخطاء ، فيمكننا استبدال return Guy{}, err بـ try(err) . بغض النظر ، لا تزال الوظيفة defer -ed قيد التشغيل وتتم إضافة السياق ، وهو أمر رائع.

مرة أخرى ، هذا يعني أن try يدقق عند الرد على الأخطاء ، ولكن ليس عند إضافة سياق إليها. هذا هو التمييز الذي ألمح إلي وربما للآخرين. هذا منطقي لأن الطريقة التي تضيف بها الوظيفة سياقًا إلى الخطأ ليست ذات أهمية خاصة للقارئ ، ولكن الطريقة التي تتفاعل بها الوظيفة مع الأخطاء مهمة. يجب أن نجعل الأجزاء الأقل إثارة للاهتمام من التعليمات البرمجية أقل إسهابًا ، وهذا ما يفعله try .

أنت لست الشخص الوحيد الذي اقترح أن هذه ليست مشكلة أو ليست مشكلة تستحق الحل. راجع https://swtch.com/try.html#nonissue للحصول على تعليقات أخرى من هذا القبيل. لقد لاحظنا ذلك ونريد التأكد من أننا نحل مشكلة فعلية.

rsc أعتقد أيضًا أنه لا توجد مشكلة في رمز الخطأ الحالي. لذا ، من فضلك ، احسبني.

أدوات مثل Robert's tryhard تساعدنا على القيام بذلك. لقد طلبت سابقًا من الأشخاص إعلامنا بما يجدون في قواعد التعليمات البرمجية الخاصة بهم. ستكون هذه المعلومات مهمة للغاية لتقييم ما إذا كان التغيير يستحق العناء أم لا. لديك تخمين واحد ولدي تخمين مختلف ، ولا بأس بذلك. الجواب هو استبدال البيانات لتلك التخمينات.

نظرت إلى https://go-review.googlesource.com/c/go/+/182717/1/src/cmd/link/internal/ld/macho_combine_dwarf.go وأحب الكود القديم بشكل أفضل. من المدهش بالنسبة لي أن محاولة استدعاء الوظيفة قد تقاطع التنفيذ الحالي. هذه ليست الطريقة التي تعمل بها Go الحالية.

أظن أنك ستجد الآراء سوف تختلف. أعتقد أن هذا أمر شخصي للغاية.

وأظن أن غالبية المستخدمين لا يشاركون في هذا النقاش. إنهم لا يعرفون حتى أن هذا التغيير قادم. أنا منخرط جدًا في Go نفسي ، لكنني لا أشارك في هذا التغيير ، لأنه ليس لدي وقت فراغ.

أعتقد أننا سنحتاج إلى إعادة تثقيف جميع مستخدمي Go الحاليين ليفكروا بشكل مختلف الآن.

سنحتاج أيضًا إلى تحديد ما يجب فعله مع بعض المستخدمين / الشركات الذين سيرفضون المحاولة في التعليمات البرمجية الخاصة بهم. سيكون هناك البعض بالتأكيد.

ربما يتعين علينا تغيير gofmt لإعادة كتابة الكود الحالي تلقائيًا. لإجبار هؤلاء المستخدمين "المارقون" على استخدام وظيفة المحاولة الجديدة. هل من الممكن أن تجعل الحكومة تفعل ذلك؟

كيف سنتعامل مع أخطاء التحويل البرمجي عندما يستخدم الأشخاص go1.13 وقبل ذلك لإنشاء التعليمات البرمجية باستخدام try؟

ربما فاتني العديد من المشاكل الأخرى التي كان علينا التغلب عليها لتنفيذ هذا التغيير. هل يستحق عناء؟ لا أصدق ذلك.

اليكس

تضمين التغريدة
أثناء محاولة tryhard على ملف مع 97 خطأ لم يتم اكتشاف أي شيء ، وجدت أن النموذجين لم يتم ترجمتهما
1:

    if err := updateItem(tx, fields, entityView.DataBinding, entityInstance); err != nil {
        tx.Rollback()
        return nil, err
    }

لم يتم استبداله ، ربما لأن tx.Rollback () بين err: = وسطر العودة ،
وهو ما أفترض أنه لا يمكن التعامل معه إلا عن طريق التأجيل - وإذا كانت جميع مسارات الخطأ بحاجة إلى tx.
هل هذا صحيح ؟

  1. كما أنه لا يقترح:
if err := db.Error; err != nil {
        return nil, err
    } else if itemDb, err := GetItem(c, entity, entityView, ItemRequest{recNo}); err != nil {
        return nil, err
    } else {
        return itemDb, nil
    }

أو

    if err := db.Error; err != nil {
        return nil, err
    } else {
            if itemDb, err := GetItem(c, entity, entityView, ItemRequest{recNo}); err != nil {
                return nil, err
            } else {
                return itemDb, nil
            }
        return result, nil
    }

هل هذا بسبب التظليل أو محاولة التعشيش ستترجم إلى؟ المعنى - هل يجب محاولة هذا الاستخدام أم اقتراح تركه كما يخطئ: = ... إرجاع يخطئ؟

guybrand Re: النموذجان اللذان وجدتهما:

1) نعم ، tryhard لا يبذل جهدًا كبيرًا. فحص النوع ضروري للحالات الأكثر تعقيدًا. إذا كان يجب تنفيذ tx.Rollback() في جميع المسارات ، فقد يكون defer هو الأسلوب الصحيح. خلاف ذلك ، قد يكون الاحتفاظ بـ if الطريقة الصحيحة. ذلك يعتمد على الكود المحدد.

2) نفس الشيء هنا: tryhard لا يبحث عن هذا النمط الأكثر تعقيدًا. ربما يمكن.

مرة أخرى ، هذه أداة تجريبية للحصول على بعض الإجابات السريعة. القيام بذلك بشكل صحيح يتطلب المزيد من العمل.

تضمين التغريدة

كيف سنتعامل مع أخطاء التحويل البرمجي عندما يستخدم الأشخاص go1.13 وقبل ذلك لإنشاء التعليمات البرمجية باستخدام try؟

ما أفهمه هو أن إصدار اللغة نفسها سيتم التحكم فيه من خلال توجيه إصدار اللغة go في الملف go.mod لكل جزء من التعليمات البرمجية التي يتم تجميعها.

تصف وثائق go.mod أثناء الرحلة توجيه إصدار اللغة go كما يلي:

إصدار اللغة المتوقع ، الذي تم تعيينه بواسطة التوجيه go ، هو الذي يحدد
ما هي ميزات اللغة المتوفرة عند تجميع الوحدة.
ستكون ميزات اللغة المتوفرة في هذا الإصدار متاحة للاستخدام.
تمت إزالة ميزات اللغة في الإصدارات السابقة أو إضافتها في الإصدارات اللاحقة ،
لن تكون متاحة. لاحظ أن إصدار اللغة لا يؤثر
إنشاء العلامات ، والتي يتم تحديدها من خلال إصدار Go المستخدم.

إذا افترضنا أن شيئًا ما مثل try الجديد قد وصل إلى شيء مثل Go 1.15 ، فعند هذه النقطة لن يتمكن الشخص الذي يقرأ ملفه go.mod go 1.12 من الوصول إلى هذا try الجديد go.mod من go 1.12 بدلاً من ذلك لقراءة go 1.15 إذا كانوا يريدون استخدام Go الجديد ميزة اللغة 1.15 هي try .

من ناحية أخرى ، إذا كان لديك رمز يستخدم try وكان هذا الرمز موجودًا في وحدة نمطية يعلن ملفها go.mod أن إصدار لغة Go هو go 1.15 ، ولكن بعد ذلك يحاول شخص ما قم ببناء ذلك باستخدام سلسلة أدوات Go 1.12 ، عند هذه النقطة ستفشل سلسلة أدوات Go 1.12 مع حدوث خطأ في الترجمة. لا تعرف سلسلة أدوات Go 1.12 أي شيء عن try ، لكنها تعرف ما يكفي لطباعة رسالة إضافية مفادها أن الكود الذي فشل في التجميع ادعى أنه يتطلب Go 1.15 بناءً على ما هو موجود في الملف go.mod . يمكنك بالفعل تجربة هذه التجربة الآن باستخدام سلسلة أدوات Go 1.12 اليوم ، ومشاهدة رسالة الخطأ الناتجة:

.\hello.go:3:16: undefined: try
note: module requires Go 1.15

هناك نقاش أطول بكثير في وثيقة اقتراح الانتقال الثاني .

ومع ذلك ، قد تتم مناقشة التفاصيل الدقيقة لذلك بشكل أفضل في مكان آخر (على سبيل المثال ، ربما في # 30791 ، أو هذا الموضوع الأخير golang-nuts ).

griesemer ، آسف إذا فاتني طلب أكثر تحديدًا للتنسيق ، لكني أرغب في مشاركة بعض النتائج ، والحصول (إذن محتمل) على كود المصدر لبعض الشركات.
يوجد أدناه مثال حقيقي لمشروع صغير ، أعتقد أن النتائج المرفقة تعطي عينة جيدة ، إذا كان الأمر كذلك ، فيمكننا على الأرجح مشاركة بعض الجداول بنتائج مماثلة:

الإجمالي = عدد أسطر الكود
$find /path/to/repo -name '*.go' -exec cat {} \; | wc -l
Errs = عدد الأسطر التي بها أخطاء: = (ربما يخطئ هذا الخطأ = ، وماير: = ، لكنني أعتقد أنه يغطي في معظم الحالات)
$find /path/to/repo -name '*.go' -exec cat {} \; | grep "err :=" | wc -l
tryhard = عدد الأسطر التي تم العثور عليها في tryhard

عادت الحالة الأولى التي اختبرتها للدراسة:
المجموع = 5106
الخطأ = 111
tryhard = 16

قاعدة رمز أكبر
المجموع = 131777
الخطأ = 3289
tryhard = 265

إذا كان هذا التنسيق مقبولاً ، فأخبرنا كيف تريد الحصول على النتائج ، أفترض أن مجرد طرحها هنا لن يكون بالتنسيق الصحيح
أيضًا ، من المحتمل أن يكون الأمر سريعًا أن يكون لديك محاولة لعد الأسطر ، ومناسبات الخطأ: = (وربما يخطئ = ، 4 فقط على قاعدة الكود التي حاولت التعلم عليها)

شكرا.

من griesemer في https://github.com/golang/go/issues/32437#issuecomment -503276339

أحثك على إلقاء نظرة على هذا الرمز كمثال.

فيما يتعلق بهذا الرمز ، لاحظت أن الملف الخارجي الذي تم إنشاؤه هنا لا يبدو أنه مغلق. بالإضافة إلى ذلك ، من المهم التحقق من الأخطاء من إغلاق الملفات التي كتبت إليها ، لأن هذه قد تكون المرة الوحيدة التي يتم إبلاغك فيها بوجود مشكلة في الكتابة.

أنا لا أتحدث عن هذا كتقرير خطأ (على الرغم من أنه ربما يجب أن يكون كذلك؟) ، ولكن كفرصة لمعرفة ما إذا كان try له تأثير على كيفية إصلاحه. سأقوم بتعداد جميع الطرق التي يمكنني التفكير فيها لإصلاحها والنظر فيما إذا كانت إضافة try ستساعد أو تضر. فيما يلي بعض الطرق:

  1. أضف مكالمات صريحة إلى outf.Close() مباشرة قبل إرجاع أي خطأ.
  2. قم بتسمية قيمة الإرجاع وإضافة مؤجل لإغلاق الملف ، وتسجيل الخطأ إذا لم يكن أحدها موجودًا بالفعل. على سبيل المثال
func foo() (err error) {
    outf := try(os.Create())
    defer func() {
        cerr := outf.Close()
        if err == nil {
            err = cerr
        }
    }()

    ...
}
  1. نمط "الإغلاق المزدوج" حيث يقوم الشخص بعمل defer outf.Close() لضمان تنظيف المورد ، و try(outf.Close()) قبل العودة للتأكد من عدم وجود أخطاء.
  2. Refactor للحصول على وظيفة مساعد تأخذ الملف المفتوح بدلاً من المسار بحيث يمكن للمتصل التأكد من إغلاق الملف بشكل مناسب. على سبيل المثال
func foo() error {
    outf := try(os.Create())
    if err := helper(outf); err != nil {
        outf.Close()
        return err
    }
    try(outf.Close())
    return nil
}

أعتقد أنه في جميع الحالات باستثناء الحالة رقم 1 ، يكون try في أسوأ الأحوال محايدًا وعادة ما يكون إيجابيًا. وأنا أعتبر أن الرقم 1 هو الخيار الأقل قبولًا نظرًا لحجم وعدد احتمالات الخطأ في هذه الوظيفة ، لذا فإن إضافة try سيقلل من جاذبية الاختيار السلبي.

آمل أن يكون هذا التحليل مفيدًا.

إذا افترضنا أن شيئًا ما مثل try مدمج جديد هبط في شيء مثل Go 1.15 ، فعند هذه النقطة لن يتمكن شخص ما من ملفه go.mod يقرأ go 1.12

thepudds شكرا لك على التوضيح. لكني لا أستخدم الوحدات. لذا فإن شرحك بعيد عن رأسي.

اليكس

تضمين التغريدة

كيف سنتعامل مع أخطاء التحويل البرمجي عندما يستخدم الأشخاص go1.13 وقبل ذلك لإنشاء التعليمات البرمجية باستخدام try؟

إذا كان try هبط افتراضيًا في شيء مثل Go 1.15 ، فإن الإجابة المختصرة جدًا على سؤالك هي أن شخصًا ما يستخدم Go 1.13 لإنشاء كود باستخدام try سيشاهد خطأ تجميع مثل هذا:

.\hello.go:3:16: undefined: try
note: module requires Go 1.15

(على الأقل بقدر ما أفهم ما تم ذكره حول اقتراح النقل).

alexbrainman شكرا لملاحظاتك.

هناك عدد كبير من التعليقات على سلسلة المحادثات هذه على شكل "هذا لا يبدو مثل Go" ، أو "Go لا يعمل على هذا النحو" ، أو "لا أتوقع أن يحدث هذا هنا". هذا كل شيء صحيح ، _existing_ Go لا يعمل على هذا النحو.

ربما يكون هذا هو أول تغيير مقترح للغة يؤثر على الشعور باللغة بطرق أكثر جوهرية. نحن ندرك ذلك ، وهذا هو السبب في أننا أبقينا عليه في حده الأدنى. (أجد صعوبة في تخيل الضجة التي قد يسببها اقتراح الأدوية الجنيسة - الحديث عن تغيير اللغة).

لكن بالعودة إلى وجهة نظرك: يعتاد المبرمجون على كيفية عمل لغة البرمجة وشعورها. إذا تعلمت أي شيء على مدار حوالي 35 عامًا من البرمجة ، فهو أن المرء يعتاد على أي لغة تقريبًا ، ويحدث ذلك بسرعة كبيرة. بعد أن تعلمت لغة باسكال الأصلية كأول لغة عالية المستوى ، كان من غير المعقول أن لغة البرمجة لن تستغل كل كلماتها الرئيسية. لكن الأمر استغرق أسبوعًا فقط أو نحو ذلك للتعود على "بحر الكلمات" الذي كان C حيث "لا يمكن للمرء أن يرى بنية الشفرة لأنها كلها أحرف صغيرة". بعد تلك الأيام الأولى مع لغة C ، بدت شفرة باسكال عالية جدًا ، وبدا كل الشفرة الفعلية مدفونة في فوضى من الكلمات المفتاحية الصراخية. سريعًا إلى الأمام ، عندما قدمنا ​​الكتابة بالأحرف الكبيرة لتمييز المعرفات التي تم تصديرها ، فقد كان تغييرًا صادمًا عن النهج السابق المستند إلى الكلمات الرئيسية ، إذا كنت أتذكره بشكل صحيح (كان هذا قبل أن يكون Go عامًا). الآن نعتقد أنه أحد أفضل قرارات التصميم (حيث تأتي الفكرة الملموسة فعليًا من خارج فريق Go). أو ، ضع في اعتبارك التجربة الفكرية التالية: تخيل Go ليس لديه بيان defer والآن يقوم شخص ما بعمل حجة قوية لـ defer . لا يحتوي defer على دلالات مثل أي شيء آخر في اللغة ، فاللغة الجديدة لم تعد تبدو وكأنها ما قبل- defer Go بعد الآن. ومع ذلك ، بعد أن عاش معها لمدة عقد من الزمان ، يبدو تمامًا "Go-like".

النقطة المهمة هي أن رد الفعل الأولي تجاه تغيير اللغة يكاد يكون بلا معنى دون تجربة الآلية فعليًا في كود حقيقي وجمع ردود فعل ملموسة. بالطبع ، كود معالجة الخطأ الحالي جيد ويبدو أكثر وضوحًا من البديل باستخدام try - لقد تدربنا على التفكير في هذه if لمدة عقد الآن. وبالطبع يبدو رمز try غريبًا وله دلالات "غريبة" ، ولم نستخدمه من قبل ، ولا نتعرف عليه على الفور كجزء من اللغة.

ولهذا السبب نطلب من الأشخاص المشاركة فعليًا في التغيير من خلال تجربته في التعليمات البرمجية الخاصة بك ؛ على سبيل المثال ، كتابته بالفعل ، أو اجعل tryhard يتخطى الكود الموجود ، واعتبر النتيجة. أوصي بتركه يجلس لفترة ، ربما أسبوع أو نحو ذلك. انظر إليها مرة أخرى ، وأبلغ مرة أخرى.

أخيرًا ، أتفق مع تقييمك بأن غالبية الناس لا يعرفون شيئًا عن هذا الاقتراح ، أو لم يتفاعلوا معه. من الواضح تمامًا أن هذه المناقشة يهيمن عليها ربما عشرات الأشخاص أو نحو ذلك. لكن لا يزال الوقت مبكرًا ، فقد تم طرح هذا الاقتراح لمدة أسبوعين فقط ، ولم يتم اتخاذ أي قرار. هناك متسع من الوقت لمزيد من الأشخاص المختلفين للتعامل مع هذا الأمر.

https://github.com/golang/go/issues/32437#issuecomment -503297387 يشير إلى حد كبير إذا كنت تقوم بتغليف الأخطاء بأكثر من طريقة في وظيفة واحدة ، فمن الواضح أنك تفعل ذلك بشكل خاطئ. في غضون ذلك ، لدي الكثير من الأكواد التي تبدو كالتالي:

        if err := gen.Execute(tmp, s); err != nil {
                return fmt.Errorf("template error: %v", err)
        }

        if err := tmp.Close(); err != nil {
                return fmt.Errorf("cannot write temp file: %v", err)
        }
        closed = true

        if err := os.Rename(tmp.Name(), *genOutput); err != nil {
                return fmt.Errorf("cannot finalize file: %v", err)
        }
        removed = true

(يتم استخدام closed و removed بواسطة أدوات التأجيل للتنظيف ، حسب الاقتضاء)

لا أعتقد حقًا أنه يجب إعطاء كل هؤلاء نفس السياق الذي يصف مهمة المستوى الأعلى لهذه الوظيفة. أنا حقا لا أعتقد أن المستخدم يجب أن يرى فقط

processing path/to/dir: template: gen:42:17: executing "gen" at <.Broken>: can't evaluate field Broken in type main.state

عندما يكون القالب مشدودًا ، أعتقد أنه من مسؤولية معالج الأخطاء الخاص بي لاستدعاء تنفيذ القالب لإضافة "نموذج تنفيذي" أو بعض الشيء الإضافي القليل. (هذا ليس الجزء الأكبر من السياق ، لكنني أردت نسخ رمز حقيقي ولصقه بدلاً من مثال مصطنع.)

لا أعتقد أن المستخدم يجب أن يرى

processing path/to/dir: rename /tmp/blahDs3x42aD commands.gen.go: No such file or directory

بدون أدنى فكرة عن _لماذا_ يحاول برنامجي إجراء إعادة التسمية هذه ، ما هي الدلالات ، وما هي النية. أعتقد أن إضافة القليل من "لا يمكن إنهاء الملف:" يساعد حقًا.

إذا لم تقنعك هذه الأمثلة بما يكفي ، فتخيل إخراج هذا الخطأ من تطبيق سطر أوامر:

processing path/to/dir: open /some/path/here: No such file or directory

ماذا يعني ذلك؟ أريد أن أضيف سببًا وراء محاولة التطبيق إنشاء ملف هناك (لم تكن تعلم حتى أنه كان إنشاءًا ، وليس مجرد نظام تشغيل مفتوح! إنه متاح نظرًا لعدم وجود مسار وسيط.). هذا ليس شيئًا يجب إضافته إلى _ كل خطأ يتم إرجاعه من هذه الوظيفة.

إذن ، ما الذي أفتقده. هل أنا "أخطئ"؟ هل من المفترض أن أدفع كل من هذه الأشياء إلى وظيفة صغيرة منفصلة يستخدمها جميعًا مؤجلًا لالتفاف جميع أخطائهم؟

guybrand شكرا لهذه الأرقام . سيكون من الجيد الحصول على بعض الأفكار حول سبب ظهور الأرقام tryhard على ما هي عليه. ربما هناك الكثير من زخرفة الخطأ المحددة تحدث؟ إذا كان الأمر كذلك ، فهذا رائع وبيانات if هي الخيار الصحيح.

سأقوم بتحسين الأداة عندما أصل إليها.

شكرًا ، zeebo على تحليلك . لا أعلم عن هذا الرمز تحديدًا ، ولكن يبدو أن outf جزء من loadCmdReader (السطر 173) والذي يتم تمريره بعد ذلك في السطر 204. ربما هذا هو السبب outf غير مغلق. (عذرا ، لم أكتب هذا الرمز).

@ tv42 من الأمثلة الموجودة في https://github.com/golang/go/issues/32437#issuecomment -503340426 ، بافتراض أنك لا تفعل ذلك "خطأ" ، يبدو أنك تستخدم كشف حساب if هي طريقة التعامل مع هذه الحالات إذا كانت جميعها تتطلب استجابات مختلفة. try لن يساعد ، و defer سيجعل الأمر أكثر صعوبة (أي اقتراح آخر لتغيير اللغة في هذا الموضوع الذي يحاول جعل هذا الرمز أسهل في الكتابة هو قريب جدًا من if بيان

griesemer إذن كل ما يمكنني التفكير فيه هو أنك و @ rsc لا توافقان. أو أنني ، في الواقع ، "أفعل ذلك بشكل خاطئ" ، وأود إجراء محادثة حول ذلك.

من الأهداف الصريحة ، في ذلك الوقت واليوم ، أن يؤدي أي حل إلى زيادة احتمال قيام المستخدمين بإضافة سياق مناسب إلى الأخطاء. ونعتقد أن المحاولة يمكن أن تفعل ذلك ، أو لم نكن لنقترحها.

@ tv42rsc post يدور حول بنية معالجة الأخطاء الشاملة للرمز الجيد ، وهو ما أتفق معه. إذا كان لديك جزء من الكود لا يناسب هذا النمط تمامًا وكنت سعيدًا بالشفرة ، فاتركه بمفرده.

يرجئ

كان التغيير الأساسي من مسودة فحص / مقبض Gophercon إلى هذا الاقتراح هو إسقاط handle لصالح إعادة استخدام defer . الآن سيتم إضافة سياق الخطأ عن طريق رمز مثل هذا الاستدعاء المؤجل (انظر تعليقي السابق حول سياق الخطأ):

func CopyFile(src, dst string) (err error) {
    defer HelperToBeNamedLater(&err, "copy %s %s", src, dst)

    r := check os.Open(src)
    defer r.Close()

    w := check os.Create(dst)
    ...
}

تعتمد صلاحية التأجيل لأن آلية التعليق التوضيحي للخطأ في هذا المثال على بعض الأشياء.

  1. _ نتائج خطأ مسمى. كان هناك الكثير من القلق حول إضافة نتائج خطأ مسماة. صحيح أننا أحبطنا ذلك في الماضي حيث لم تكن هناك حاجة لأغراض التوثيق ، ولكن هذا هو العرف الذي اخترناه في غياب أي عامل حاسم أقوى. وحتى في الماضي ، كان هناك عامل حاسم أقوى مثل الإشارة إلى نتائج محددة في الوثائق يفوق العرف العام للنتائج غير المسماة. الآن هناك عامل حاسم ثاني أقوى ، وهو الرغبة في الإشارة إلى الخطأ في إرجاء. يبدو أنه لا ينبغي أن يكون أكثر اعتراضًا من تسمية النتائج لاستخدامها في التوثيق. كان رد فعل عدد من الأشخاص سلبيًا جدًا على هذا ، وأنا بصراحة لا أفهم السبب. يبدو تقريبًا أن الأشخاص يخلطون بين عمليات الإرجاع بدون قوائم تعبير (ما يسمى "الإرجاع العاري") مع وجود نتائج مسماة. صحيح أن الإرجاع بدون قوائم تعبير يمكن أن يؤدي إلى ارتباك في الدالات الأكبر. غالبًا ما يكون تجنب هذا الالتباس عن طريق تجنب تلك العوائد في الوظائف الطويلة أمرًا منطقيًا. نتائج الطلاء المسماة بنفس الفرشاة لا تفعل ذلك.

  2. _تعبيرات العنوان. _ أثار عدد قليل من الأشخاص مخاوف من أن استخدام هذا النمط سيتطلب من مطوري Go فهم تعبيرات عناوين. يتطلب تخزين أي قيمة باستخدام طرق المؤشر في واجهة ذلك بالفعل ، لذلك لا يبدو هذا عيبًا كبيرًا.

  3. _ دافع عن نفسه. أثار عدد قليل من الأشخاص مخاوف بشأن استخدام "التأجيل" كمفهوم لغوي على الإطلاق ، مرة أخرى لأن المستخدمين الجدد قد لا يكونون على دراية به. كما هو الحال مع تعبيرات العنوان ، يعد التأجيل مفهومًا أساسيًا للغة يجب تعلمه في النهاية. المصطلحات القياسية حول أشياء مثل defer f.Close() و defer l.mu.Unlock() شائعة جدًا لدرجة أنه من الصعب تبرير تجنب التأجيل باعتباره ركنًا غامضًا من اللغة.

  4. _الأداء. _ لقد ناقشنا لسنوات العمل على جعل أنماط التأجيل الشائعة مثل المؤجل في الجزء العلوي من الوظيفة ليس لها أي حمل زائد مقارنة بإدخال تلك المكالمة يدويًا في كل عودة. نعتقد أننا نعرف كيفية القيام بذلك وسنستكشفه في إصدار Go القادم. حتى لو لم يكن الأمر كذلك ، يجب ألا يكون الحمل الحالي البالغ 50 نانوثانية تقريبًا مانعًا لمعظم المكالمات التي تحتاج إلى إضافة سياق خطأ. ويمكن للمكالمات القليلة الحساسة للأداء الاستمرار في استخدام عبارات if حتى يصبح الإرجاء أسرع.

تتعلق الثلاثة الأولى جميعها بالاعتراضات على إعادة استخدام ميزات اللغة الحالية. لكن إعادة استخدام ميزات اللغة الحالية هو بالضبط تقدم هذا الاقتراح على التحقق / التعامل: هناك القليل لإضافته إلى اللغة الأساسية ، وعدد أقل من القطع الجديدة للتعلم ، وتفاعلات أقل إثارة للدهشة.

ومع ذلك ، فإننا نقدر أن استخدام "التأجيل" بهذه الطريقة أمر جديد وأننا بحاجة إلى منح الأشخاص الوقت لتقييم ما إذا كان "التأجيل" يعمل جيدًا بما يكفي في الممارسة العملية للخطأ في التعامل مع المصطلحات التي يحتاجون إليها.

منذ أن بدأنا هذه المناقشة في أغسطس الماضي ، كنت أقوم بالتمرين الذهني "كيف سيبدو هذا الرمز مع التحقق / التعامل؟" ومؤخرا "مع المحاولة / التأجيل؟" في كل مرة أكتب فيها رمزًا جديدًا. عادةً ما تعني الإجابة أنني أكتب رمزًا مختلفًا وأفضل ، مع إضافة السياق في مكان واحد (المؤجل) بدلاً من كل إرجاع أو حذفه تمامًا.

بالنظر إلى فكرة استخدام معالج مؤجل لاتخاذ إجراء بشأن الأخطاء ، هناك مجموعة متنوعة من الأنماط التي يمكننا تمكينها باستخدام حزمة مكتبة بسيطة. لقد قدمت # 32676 للتفكير في ذلك أكثر ، ولكن استخدام حزمة API في هذه المشكلة سيبدو رمزنا كما يلي:

func CopyFile(src, dst string) (err error) {
    defer errd.Add(&err, "copy %s %s", src, dst)

    r := check os.Open(src)
    defer r.Close()

    w := check os.Create(dst)
    ...
}

إذا كنا نصحح أخطاء CopyFile وأردنا رؤية أي خطأ تم إرجاعه وتتبع المكدس (على غرار الرغبة في إدراج طباعة تصحيح الأخطاء) ، فيمكننا استخدام:

func CopyFile(src, dst string) (err error) {
    defer errd.Trace(&err)
    defer errd.Add(&err, "copy %s %s", src, dst)

    r := check os.Open(src)
    defer r.Close()

    w := check os.Create(dst)
    ...
}

وما إلى ذلك وهلم جرا.

يؤدي استخدام التأجيل بهذه الطريقة إلى كونه قويًا إلى حد ما ، ويحتفظ بميزة الاختيار / المقبض التي يمكنك كتابتها "افعل هذا في أي خطأ على الإطلاق" مرة واحدة في الجزء العلوي من الوظيفة ثم لا تقلق بشأنه لبقية الجسم. يعمل هذا على تحسين إمكانية القراءة بنفس طريقة عمليات الخروج السريع المبكرة .

هل سيعمل هذا في الممارسة؟ نريد أن نعرف.

بعد إجراء التجربة الذهنية لما سيبدو عليه الإرجاء في الكود الخاص بي لبضعة أشهر ، أعتقد أنه من المرجح أن ينجح. لكن بالطبع ، لا يكون الحصول على استخدامه في الكود الحقيقي هو نفسه دائمًا. سنحتاج إلى التجربة لمعرفة ذلك.

يمكن للناس تجربة هذا النهج اليوم من خلال الاستمرار في كتابة بيانات if err != nil ولكن نسخ المساعدين المؤجلين والاستفادة منهم حسب الاقتضاء. إذا كنت تميل إلى القيام بذلك ، فالرجاء إخبارنا بما تتعلمه.

@ tv42 ، أتفق معgriesemer. إذا وجدت أن هناك حاجة إلى سياق إضافي للتجانس عبر اتصال مثل إعادة التسمية كونها خطوة "إنهاء" ، فلا حرج في استخدام عبارات if لإضافة سياق إضافي. ومع ذلك ، في العديد من الوظائف ، هناك حاجة قليلة لمثل هذا السياق الإضافي.

guybrand ، تعد أرقام tryhard رائعة ، ولكن من الأفضل أن تكون وصفًا لسبب عدم تحويل أمثلة معينة ، علاوة على أنه لم يكن من المناسب إعادة كتابتها حتى يمكن التحويل. مثال وشرح @ tv42 هو مثال على ذلك.

griesemer حول قلقك بشأن التأجيل. كنت ذاهبًا لذلك emit أو في الاقتراح الأولي handle . سيتم استدعاء emit/handle إذا لم يكن err صفريًا. وسوف تبدأ في تلك اللحظة بدلاً من نهاية الدالة. يتم استدعاء المؤجل في النهاية. emit/handle سينهي الوظيفة بناءً على ما إذا كان err لا شيء أم لا. لهذا السبب لن يعمل التأجيل.

بعض البيانات:

من بين 70 ألفًا من مشروع LOC الذي قمت بالتشجيع عليه للتخلص من "مرتجعات الأخطاء العارية" دينياً ، لا يزال لدينا 612 خطأً عارياً. غالبًا ما يتعامل مع حالة يتم فيها تسجيل خطأ ، ولكن الرسالة مهمة داخليًا فقط (الرسالة إلى المستخدم محددة مسبقًا). try () سيوفر مدخرًا أكبر من سطرين فقط لكل عودة عارية ، لأنه مع وجود أخطاء محددة مسبقًا يمكننا تأجيل معالج واستخدام المحاولة في أماكن أكثر.

الأكثر إثارة للاهتمام ، في دليل البائع ، من بين ~ 620k + LOC ، لدينا 1600 خطأ عارٍ فقط. تميل المكتبات التي نختارها إلى تزيين الأخطاء على نحو أكثر دينًا مما نفعل.

rsc ، إذا تمت إضافة المعالجات لاحقًا إلى try ، فهل ستكون هناك حزمة أخطاء / errc بوظائف مثل func Wrap(msg string) func(error) error حتى تتمكن من تنفيذ try(f(), errc.Wrap("f failed")) ؟

@ damienfamed75 شكرا لتفسيراتكم . لذلك سيتم استدعاء $ emit عندما يعثر try على خطأ ، ويتم استدعاؤه بهذا الخطأ. هذا يبدو واضحا بما فيه الكفاية.

أنت تقول أيضًا أن emit سينهي الوظيفة إذا كان هناك خطأ ، وليس إذا تم التعامل مع الخطأ بطريقة ما. إذا لم تنهِ الوظيفة ، فأين يستمر الرمز؟ من المفترض مع العودة من try (وإلا فأنا لا أفهم emit الذي لا ينهي الوظيفة). ألن يكون من الأسهل والأكثر وضوحًا في هذه الحالة استخدام if بدلاً من try ؟ قد يؤدي استخدام emit أو handle إلى إخفاء تدفق التحكم بشكل كبير في تلك الحالات ، خاصةً لأن عبارة emit يمكن أن تكون في جزء مختلف تمامًا (من المفترض أن يكون ذلك سابقًا) في الوظيفة. (في هذه الملاحظة ، هل يمكن للمرء أن يمتلك أكثر من emit ؟ إذا لم يكن كذلك ، فلماذا؟ ماذا يحدث إذا لم يكن هناك emit ؟ الكثير من الأسئلة نفسها التي ابتليت بـ check الأصلي مشروع تصميم handle .)

فقط إذا أراد المرء العودة من دالة بدون عمل إضافي كبير إلى جانب زخرفة الخطأ ، أو مع نفس العمل دائمًا ، فهل من المنطقي استخدام try ، ونوع من المعالج. وآلية المعالج هذه ، والتي يتم تشغيلها قبل إرجاع الدالة ، موجودة بالفعل في defer .

guybrand (وgriesemer) فيما يتعلق بنمطك الثاني غير المعترف به ، راجع https://github.com/griesemer/tryhard/issues/2

تضمين التغريدة

كيف ستُشتق القيمة من البيانات عندما تكون المشاعر مدفوعة بمعظم العملية العامة؟

ربما قد يكون لدى الآخرين تجربة مثل تجربتي المذكورة هنا . كنت أتوقع أن أقلب بعض الأمثلة من try تم إدراجها بواسطة tryhard ، واكتشف أنها تشبه إلى حد ما ما هو موجود بالفعل في هذا الموضوع ، والمضي قدمًا. بدلاً من ذلك ، فوجئت بإيجاد حالة أدى فيها try إلى كود أفضل بشكل واضح ، بطريقة لم تتم مناقشتها من قبل.

لذلك هناك أمل على الأقل. :)

بالنسبة للأشخاص الذين يحاولون تجربة tryhard ، إذا لم تكن قد قمت بذلك بالفعل ، فأنا أشجعك ليس فقط على النظر في التغييرات التي أجرتها الأداة ، ولكن أيضًا على grep للحالات المتبقية من err != nil وإلقاء نظرة على ماذا تركت وحدها ولماذا.

(ولاحظ أيضًا أن هناك بعض المشكلات والعلاقات العامة على https://github.com/griesemer/tryhard/.)

rsc هنا بصيرتي حول سبب عدم إعجابي شخصيًا بالنمط defer HandleFunc(&err, ...) . ليس لأنني ربطها بالعائدات العارية أو أي شيء آخر ، إنها فقط تشعر بأنها "ذكية" للغاية.

كان هناك خطأ في التعامل مع الاقتراح قبل بضعة أشهر (ربما عام؟) ، لكنني فقدت المسار الآن. لقد نسيت ما كان يطلبه ، ولكن أجاب أحدهم بشيء على غرار:

func myFunction() (i int, err error) {
    defer func() {
        if err != nil {
            err = fmt.Errorf("wrapping the error: %s", err)
        }
    }()

    // ...
    return 0, err

    // ...
    return someInt, nil
}

كان من المثير للاهتمام أن أرى أقل ما يقال. كانت المرة الأولى التي أرى فيها defer مستخدمًا لمعالجة الأخطاء ، والآن يتم عرضه هنا. أراه على أنه "ذكي" و "مبتكر" ، وعلى الأقل في المثال الذي أذكره ، فإنه لا يبدو وكأنه Go. ومع ذلك ، فإن تغليفها في مكالمة وظيفية مناسبة بشيء مثل fmt.HandleErrorf يساعدها على الشعور بلطف أكبر. ما زلت أشعر بالسلبية تجاهها ، رغم ذلك.

سبب آخر يجعلني أرى الناس لا يحبونها هو أنه عندما يكتب المرء return ..., err ، يبدو أنه يجب إرجاع err . لكن لا يتم إرجاعها ، بدلاً من ذلك يتم تعديل القيمة قبل الإرسال. لقد قلت من قبل أن return بدا دائمًا وكأنه عملية "مقدسة" في Go ، والتشجيع على الكود الذي يعدل القيمة التي تم إرجاعها قبل العودة في الواقع يبدو خاطئًا.

حسنًا ، الأرقام والبيانات هي إذن. :)

قمت بتشغيل tryhard على المصادر عدة خدمات لمنصة الخدمات المصغرة الخاصة بنا ، وقارنتها بنتائج loccount و grep "إذا أخطأت". حصلت على النتائج التالية بالترتيب loccount / grep 'إذا أخطأت' | مرحاض / تريارد:

1382/64/14
108554/66/5
58401/22/5
2052/247/39
12024/1655/1

تقوم بعض خدماتنا المصغرة بالكثير من معالجة الأخطاء والبعض الآخر لا يقوم إلا بالقليل ، ولكن لسوء الحظ ، كان tryhard قادرًا فقط على تحسين الشفرة تلقائيًا ، في أحسن الأحوال ، 22٪ من الحالات ، بأقل من 1٪. الآن ، لن نعيد كتابة معالجة الأخطاء يدويًا ، لذا ستكون أداة مثل tryhard ضرورية لتقديم try() في قاعدة الكود لدينا. أقدر أن هذه أداة أولية بسيطة ، لكنني فوجئت بمدى ندرة تمكنها من المساعدة.

لكنني أعتقد أنه الآن ، مع وجود الرقم في متناول اليد ، يمكنني القول أنه من أجل استخدامنا ، فإن المحاولة () لا تحل حقًا أي مشكلة ، أو على الأقل ليس حتى يصبح tryhard أفضل بكثير.

لقد وجدت أيضًا في قواعد الكود لدينا أن حالة استخدام if err != nil { return err } لـ try() نادرة جدًا ، على عكس برنامج التحويل البرمجي go ، حيث يكون شائعًا. مع كل الاحترام الواجب ، لكنني أعتقد أن مصممي Go ، الذين يبحثون في كود مصدر مترجم Go في كثير من الأحيان أكثر بكثير من قواعد الكود الأخرى ، يبالغون في تقدير فائدة try() بسبب هذا.

beoran tryhard بدائي للغاية في الوقت الحالي. هل لديك فكرة عن الأسباب الأكثر شيوعًا لندرة try في قاعدة الكود الخاصة بك؟ على سبيل المثال لأنك تزين الأخطاء؟ لأنك تقوم بأعمال إضافية أخرى قبل العودة؟ شيء آخر؟

rsc ، griesemer

بالنسبة للأمثلة ، فقد أعطيت عينتين متكررتين هنا وقد فاتتهما tryHard ، فمن المحتمل أن تظل إحداها "إذا كان الخطأ: =" ، فقد يتم حل الأخرى

بالنسبة إلى زخرفة الخطأ ، هناك نمطين متكررين أراهما في الكود هما (أضع الاثنين في مقتطف رمز واحد):

if v, err := someFunction(vars...) ; err != nil {
        return fmt.Errorf("extra data to help with where did error occur and params are %s , %d , err : %v",
            strParam, intParam, err)
    } else if v2, err := passToAnotherFunc(v,vars ...);err != nil {
        extraData := DoSomethingAccordingTo(v2,err)
        return formatError(err,extraData)
    } else {

    }

وفي كثير من الأحيان يكون التنسيق خطأ معياريًا للتطبيق أو من أي وقت مضى عبر إعادة الشراء ، والأكثر تكرارًا هو تنسيق DbError (وظيفة واحدة في جميع التطبيقات / التطبيقات ، تُستخدم في عشرات المواقع) ، في بعض الحالات (بدون الدخول إلى "هل هذا تصحيح النمط ") حفظ بعض البيانات للتسجيل (إذا فشل استعلام sql فلن ترغب في تفويت المكدس) وبعض النصوص الأخرى إلى الخطأ.

بمعنى آخر ، إذا أردت "القيام بأي شيء ذكي باستخدام بيانات إضافية مثل خطأ التسجيل A ورفع الخطأ B ، علاوة على ذكري لهذين الخيارين لتوسيع معالجة الأخطاء
هذا خيار آخر لـ "أكثر من مجرد إرجاع الخطأ والسماح لـ" شخص آخر "أو" بعض الوظائف الأخرى "بالتعامل معه"

مما يعني أنه من المحتمل أن يكون هناك استخدام أكبر لـ try () في "المكتبات" منه في "البرامج القابلة للتنفيذ" ، فربما سأحاول تشغيل مقارنة Total / Errs / tryHard التي تميز libs عن برامج التشغيل ("التطبيقات").

لقد وجدت نفسي بالضبط في الموقف الموضح في https://github.com/golang/go/issues/32437#issuecomment -503297387
في بعض المستويات التي ألتف فيها الأخطاء بشكل فردي ، لن أغير هذا بـ try ، فلا بأس مع if err!=nil .
على المستوى الآخر ، أنا فقط return err إنه أمر صعب لإضافة نفس السياق لجميع المرتجعات ، ثم سأستخدم try و defer .
حتى أنني أفعل ذلك بالفعل باستخدام مسجل محدد أستخدمه في بداية الوظيفة فقط في حالة حدوث خطأ. بالنسبة لي try والزخرفة حسب الوظيفة هي بالفعل goish.

تضمين التغريدة

إذا كان من المفترض أن يهبط try في شيء مثل Go 1.15 ، فإن الإجابة المختصرة جدًا على سؤالك هي أن شخصًا ما يستخدم Go 1.13

لم يتم إصدار Go 1.13 حتى الآن ، لذا لا يمكنني استخدامه. ونظرًا لأن مشروعي لا يستخدم وحدات Go ، فلن أتمكن من الترقية إلى Go 1.13. (أعتقد أن Go 1.13 سيتطلب من الجميع استخدام وحدات Go)

لإنشاء رمز باستخدام try سيظهر خطأ تجميع مثل هذا:

.\hello.go:3:16: undefined: try
note: module requires Go 1.15

(على الأقل بقدر ما أفهم ما تم ذكره حول اقتراح النقل).

هذا كله افتراض. من الصعب عليّ التعليق على الأشياء الخيالية. وربما يعجبك هذا الخطأ ، لكني أجده محيرًا وغير مفيد.

إذا كانت المحاولة غير محددة ، فإنني سأفعل ذلك. ولن أجد شيئًا. ماذا علي أن أفعل إذا؟

و note: module requires Go 1.15 هو أسوأ مساعدة في هذا الموقف. لماذا module ؟ لماذا Go 1.15 ؟

تضمين التغريدة

ربما يكون هذا هو أول تغيير مقترح للغة يؤثر على الشعور باللغة بطرق أكثر جوهرية. نحن ندرك ذلك ، وهذا هو السبب في أننا أبقينا عليه في حده الأدنى. (أجد صعوبة في تخيل الضجة التي قد يسببها اقتراح الأدوية الجنيسة - الحديث عن تغيير اللغة).

أفضل قضاء الوقت في الأدوية الجنيسة ، بدلاً من المحاولة. ربما هناك فائدة من استخدام الأدوية الجنيسة في Go.

لكن بالعودة إلى وجهة نظرك: يعتاد المبرمجون على كيفية عمل لغة البرمجة وشعورها. ...

أتفق مع كل آرائك. لكننا نتحدث عن استبدال صيغة معينة من عبارة if باستدعاء دالة try. هذا في اللغة التي تفتخر بالبساطة والتعامد. يمكنني التعود على كل شيء ، ولكن ما هو الهدف؟ لحفظ سطرين من التعليمات البرمجية؟

أو ، ضع في اعتبارك التجربة الفكرية التالية: تخيل Go ليس لديه بيان defer والآن يقوم شخص ما بعمل حجة قوية لـ defer . لا يحتوي defer على دلالات مثل أي شيء آخر في اللغة ، فاللغة الجديدة لم تعد تبدو وكأنها ما قبل- defer Go بعد الآن. ومع ذلك ، بعد أن عاش معها لمدة عقد من الزمان ، يبدو تمامًا "Go-like".

بعد سنوات عديدة ، ما زلت يتم خداعي من قبل defer للجسم وإغلاقه بسبب المتغيرات. لكن defer يدفع سعره على شكل بستوني عندما يتعلق الأمر بإدارة الموارد. لا أستطيع أن أتخيل الذهاب بدون defer . لكنني لست مستعدًا لدفع سعر مماثل مقابل try ، لأنني لا أرى أي فوائد هنا.

ولهذا السبب نطلب من الأشخاص المشاركة فعليًا في التغيير من خلال تجربته في التعليمات البرمجية الخاصة بك ؛ على سبيل المثال ، كتابته بالفعل ، أو اجعل tryhard يتخطى الكود الموجود ، واعتبر النتيجة. أوصي بتركه يجلس لفترة ، ربما أسبوع أو نحو ذلك. انظر إليها مرة أخرى ، وأبلغ مرة أخرى.

حاولت تغيير مشروع صغير خاص بي (حوالي 1200 سطر من الكود). ويبدو مشابهًا للتغيير الذي أجريته على https://go-review.googlesource.com/c/go/+/182717/1/src/cmd/link/internal/ld/macho_combine_dwarf.go لا أرى رأيي تغيير عن هذا بعد أسبوع. ذهني دائمًا مشغول بشيء ، وسوف أنساه.

... لكن لا يزال الوقت مبكرًا ، لقد تم طرح هذا الاقتراح لمدة أسبوعين فقط ، ...

ويمكنني أن أرى أن هناك 504 رسائل حول هذا الاقتراح فقط في هذا الموضوع بالفعل. إذا كنت مهتمًا بدفع هذا التغيير ، فسوف يستغرق مني أيامًا إن لم يكن أسابيع لمجرد قراءة وفهم كل هذا. أنا لا أحسد عملك.

شكرا لك على الوقت الذي استغرقته في الرد على رسالتي. عذرًا ، إذا لم أرد على سلسلة الرسائل هذه - فهي كبيرة جدًا بحيث لا يمكنني مراقبتها ، إذا كانت الرسالة موجهة إلي أم لا.

اليكس

griesemer شكرًا على الاقتراح الرائع ويبدو أن tryhard أكثر فائدة أتوقعه. أنا أيضا أريد أن أقدر.

rsc شكرًا على الاستجابة والأداة المفصليتين جيدًا.

لقد كنت أتابع هذا الموضوع لفترة من الوقت والتعليقات التالية من beoran أعطني قشعريرة

لا يساعد إخفاء متغير الخطأ والعائد في تسهيل فهم الأمور

لقد تمكنت من إدارة العديد من bad written code من قبل ويمكنني أن أشهد على أنها أسوأ كابوس لكل مطور.

حقيقة أن الوثائق تشير إلى استخدام A لا تعني أنه سيتم اتباعها ، تظل الحقيقة أنه إذا كان من الممكن استخدام AA ، AB ، فلا يوجد حد للكيفية يمكن استخدامه.

To my surprise, people already think the code below is cool ... أعتقد أن it's an abomination مع كل الاحترام الواجب للاعتذار لأي شخص أساء.

parentCommitOne := try(try(try(r.Head()).Peel(git.ObjectCommit)).AsCommit())
parentCommitTwo := try(try(remoteBranch.Reference.Peel(git.ObjectCommit)).AsCommit())

انتظر حتى تحقق من AsCommit وسترى

func AsCommit() error(){
    return try(try(try(tail()).find()).auth())
}

يستمر الجنون وبصراحة لا أريد أن أصدق أن هذا هو تعريف robpike simplicity is complicated (فكاهة)

بناءً على مثال rsc

// Example 1
headRef := try(r.Head())
parentObjOne := try(headRef.Peel(git.ObjectCommit))
parentObjTwo := try(remoteBranch.Reference.Peel(git.ObjectCommit))
parentCommitOne := parentObjOne.AsCommit()
parentCommitTwo := parentObjTwo.AsCommit()
treeOid := try(index.WriteTree())
tree := try(r.LookupTree(treeOid))

// Example 2 
try headRef := r.Head()
try parentObjOne := headRef.Peel(git.ObjectCommit)
try parentObjTwo := remoteBranch.Reference.Peel(git.ObjectCommit)
parentCommitOne := parentObjOne.AsCommit()
parentCommitTwo := parentObjTwo.AsCommit()
try treeOid := index.WriteTree()
try tree := r.LookupTree(treeOid)

// Example 3 
try (
    headRef := r.Head()
    parentObjOne := headRef.Peel(git.ObjectCommit)
    parentObjTwo := remoteBranch.Reference.Peel(git.ObjectCommit)
)
parentCommitOne := parentObjOne.AsCommit()
parentCommitTwo := parentObjTwo.AsCommit()
try (
    treeOid := index.WriteTree()
    tree := r.LookupTree(treeOid)
)

أنا أؤيد Example 2 مع القليل من else ، يرجى ملاحظة أن هذا قد لا يكون أفضل نهج مع ذلك

  • من السهل رؤية الخطأ بوضوح
  • أقل ما يمكن أن يتحول إلى abomination يمكن للآخرين أن يلدوا
  • try لا يتصرف مثل الوظيفة العادية. لإعطائها بناء جملة يشبه الوظيفة قليل من. يستخدم $ go if وإذا كان بإمكاني تغييره إلى try tree := r.LookupTree(treeOid) else { فسيبدو الأمر أكثر طبيعية
  • يمكن أن تكون الأخطاء باهظة الثمن للغاية ، فهي تحتاج إلى أكبر قدر ممكن من الرؤية ، وأعتقد أن هذا هو السبب وراء عدم دعم الأخطاء التقليدية try & catch
try headRef := r.Head()
try parentObjOne := headRef.Peel(git.ObjectCommit)
try parentObjTwo := remoteBranch.Reference.Peel(git.ObjectCommit)

parentCommitOne := parentObjOne.AsCommit()
parentCommitTwo := parentObjTwo.AsCommit()

try treeOid := index.WriteTree()
try tree := r.LookupTree(treeOid) else { 
    // Heal the world 
   // I may return with return keyword 
   // I may not return but set some values to 0 
   // I may remember I need to log only this 
   // I may send a mail to let the cute monkeys know the server is on fire 
}

مرة أخرى أريد أن أعتذر لكوني أناني قليلاً.

josharian لا يمكنني الإفصاح عن الكثير هنا ، ومع ذلك ، فإن الأسباب متنوعة تمامًا. كما تقول ، نحن نقوم بتزيين الأخطاء ، أو نقوم أيضًا بمعالجة مختلفة ، وأيضًا ، هناك حالة استخدام مهمة وهي أننا نقوم بتسجيلها ، حيث تختلف رسالة السجل لكل خطأ يمكن أن تعيده الوظيفة ، أو لأننا نستخدم if err := foo() ; err != nil { /* various handling*/ ; return err } شكل ، أو أسباب أخرى.

ما أريد التأكيد عليه هو هذا: حالة الاستخدام البسيطة التي تم تصميم try() من أجلها تحدث نادرًا جدًا في قاعدة الشفرة الخاصة بنا. لذلك ، ليس هناك الكثير الذي يمكن اكتسابه لإضافة "try ()" إلى اللغة.

تحرير: إذا كان سيتم تنفيذ try () ، فأعتقد أن الخطوة التالية يجب أن تكون جعل tryhard أفضل بكثير ، بحيث يمكن استخدامه على نطاق واسع لترقية قواعد التعليمات البرمجية الحالية.

griesemer سأحاول معالجة كل مخاوفك واحدة تلو الأخرى من إجابتك الأخيرة .
سألت أولاً عما إذا كان المعالج لا يعود أو يخرج من الوظيفة بطريقة ما ، فماذا سيحدث. نعم ، يمكن أن تكون هناك حالات لا تُرجع فيها عبارة emit / handle وظيفة أو تخرج منها ، بل تبدأ من حيث توقفت. على سبيل المثال ، في حالة أننا نحاول العثور على محدد أو شيء بسيط باستخدام قارئ ووصلنا إلى EOF ، فقد لا نرغب في إرجاع خطأ عندما نصل إلى ذلك. لذلك قمت ببناء هذا المثال السريع لما يمكن أن يبدو عليه:

func findDelimiter(r io.Reader) ([]byte, error) {
    emit err {
        // if this doesn't return then continue from where we left off
        // at the try function that was called last.
        if err != io.EOF {
            return nil, err
        }
    }

    bufReader := bufio.NewReader(r)

    token := try(bufReader.ReadSlice('|'))

    return token, nil
}

أو حتى يمكن تبسيطها لهذا:

func findDelimiter(r io.Reader) ([]byte, error) {
    emit err != io.EOF {
        return nil, err
    }

    bufReader := bufio.NewReader(r)

    token := try(bufReader.ReadSlice('|'))

    return token, nil
}

القلق الثاني كان حول تعطيل تدفق التحكم. ونعم ، قد يؤدي ذلك إلى تعطيل التدفق ، ولكن لكي نكون منصفين ، فإن معظم المقترحات تعمل إلى حد ما على تعطيل التدفق للحصول على وظيفة مركزية واحدة لمعالجة الخطأ وما شابه. هذا لا يختلف في اعتقادي.
بعد ذلك ، سألت عما إذا كنا قد استخدمنا emit / handle أكثر من مرة أقول فيها أنه أعيد تعريفه.
إذا كنت تستخدم emit أكثر من مرة ، فسيتم استبدال آخرها وهكذا. إذا لم يكن لديك أي منها ، فسيكون لدى try معالج افتراضي يقوم فقط بإرجاع القيم الصفرية والخطأ. هذا يعني أن هذا المثال هنا:

func writeStuff(filename string) (io.ReadCloser, error) {
    emit err {
        return nil, err
    }

    f := try(os.Open(filename))

    try(fmt.Fprintf(f, "stuff\n"))

    return f, nil
}

سيفعل نفس الشيء مثل هذا المثال:

func writeStuff(filename string) (io.ReadCloser, error) {
    // when not defining a handler then try's default handler kicks in to
    // return nil valued then error as usual.

    f := try(os.Open(filename))

    try(fmt.Fprintf(f, "stuff\n"))

    return f, nil
}

كان سؤالك الأخير يتعلق بالتصريح عن دالة معالج يتم استدعاؤها في defer مع افتراض إشارة إلى error . لا يعمل هذا التصميم بالطريقة نفسها التي يعمل بها هذا الاقتراح على أساس أن defer لا يمكنه إيقاف وظيفة فورًا عند وجود شرط بحد ذاته.

أعتقد أنني تناولت كل شيء في ردك وآمل أن يوضح هذا اقتراحي أكثر قليلاً. إذا كان هناك المزيد من المخاوف ، فأعلمني بذلك لأنني أعتقد أن هذا النقاش برمته مع الجميع ممتع للغاية للتفكير في أفكار جديدة. حافظوا على العمل جيد جميعا!

velovix ، re https://github.com/golang/go/issues/32437#issuecomment -503314834:

مرة أخرى ، هذا يعني أن try يقوم بالردود على الأخطاء ، ولكن ليس عند إضافة سياق إليها. هذا هو التمييز الذي ألمح إلي وربما للآخرين. هذا منطقي لأن الطريقة التي تضيف بها الوظيفة سياقًا إلى الخطأ ليست ذات أهمية خاصة للقارئ ، ولكن الطريقة التي تتفاعل بها الوظيفة مع الأخطاء مهمة. يجب أن نجعل الأجزاء الأقل إثارة للاهتمام من التعليمات البرمجية أقل إسهابًا ، وهذا ما يفعله try .

هذه حقا طريقة لطيفة لوضعها. شكرا.

olekukonko ، re https://github.com/golang/go/issues/32437#issuecomment -503508478:

To my surprise, people already think the code below is cool ... أعتقد أن it's an abomination مع كل الاحترام الواجب لأي شخص أساء.

parentCommitOne := try(try(try(r.Head()).Peel(git.ObjectCommit)).AsCommit())
parentCommitTwo := try(try(remoteBranch.Reference.Peel(git.ObjectCommit)).AsCommit())

Grepping https://swtch.com/try.html ، فقد حدث هذا التعبير ثلاث مرات في هذا الموضوع.
goodwine طرحها على أنها رمز سيء ، فوافقت ، وقال velovix "على الرغم من قبحها ... أفضل مما تراه غالبًا في لغات try-catch ... لأنه لا يزال بإمكانك تحديد أجزاء الكود التي قد تؤدي إلى تحويلها التحكم في التدفق بسبب خطأ والذي لا يمكن. "

لم يقل أحد أنه "رائع" أو شيء يطرحه على أنه رمز رائع. مرة أخرى ، من الممكن دائمًا كتابة تعليمات برمجية سيئة .

أود أن أقول فقط إعادة

يمكن أن تكون الأخطاء باهظة الثمن للغاية ، فهي تحتاج إلى أكبر قدر ممكن من الرؤية

من المفترض ألا تكون الأخطاء في Go باهظة الثمن. إنها أحداث يومية عادية ويقصد بها أن تكون خفيفة الوزن. (هذا على النقيض من بعض تطبيقات الاستثناءات على وجه الخصوص. كان لدينا خادمًا قضى وقتًا طويلاً جدًا من وقت وحدة المعالجة المركزية في التحضير والتخلص من كائنات الاستثناء التي تحتوي على تتبعات المكدس لاستدعاءات "فتح الملف" الفاشلة في حلقة للتحقق من قائمة معروفة مواقع لملف معين.)

alexbrainman ، أنا آسف للارتباك بشأن ما سيحدث إذا كانت الإصدارات القديمة من كود Go تحتوي على المحاولة. الإجابة المختصرة هي أنها مثل أي وقت آخر نقوم فيه بتغيير اللغة: سوف يرفض المترجم القديم الكود الجديد برسالة غير مفيدة في الغالب (في هذه الحالة "undefined: try"). الرسالة غير مفيدة لأن المترجم القديم لا يعرف البنية الجديدة ولا يمكن أن يكون أكثر فائدة حقًا. قد يقوم الأشخاص في هذه المرحلة بالبحث على الويب عن "تجربة غير محددة" ومعرفة الميزة الجديدة.

في مثالthepudds ، يحتوي الكود الذي يستخدم try على go.mod يحتوي على السطر "go 1.15" ، مما يعني أن مؤلف الوحدة يقول إن الكود مكتوب مقابل إصدار لغة Go. يعمل هذا كإشارة لأوامر go الأقدم للإشارة بعد خطأ في التجميع إلى أن الرسالة غير المفيدة ربما تكون بسبب وجود إصدار قديم جدًا من Go. هذه محاولة صريحة لجعل الرسالة أكثر إفادة دون إجبار المستخدمين على اللجوء إلى عمليات البحث على الويب. إذا كان يساعد ، جيد ؛ إذا لم يكن الأمر كذلك ، فستبدو عمليات البحث على الويب فعالة جدًا على أي حال.

guybrand ، أعد https://github.com/golang/go/issues/32437#issuecomment -503287670 ومع الاعتذار لأن الأوان قد فات على لقائك:

إحدى المشكلات بشكل عام مع الوظائف التي تُرجع أنواعًا ليست أخطاءً تمامًا هي أنه بالنسبة للواجهات غير البينية ، فإن التحويل إلى خطأ لا يحافظ على عدم وجود شيء. لذلك على سبيل المثال ، إذا كان لديك نوع ملموس خاص بك * MyError (على سبيل المثال ، مؤشر إلى بنية) واستخدم Err == nil كإشارة للنجاح ، فهذا رائع حتى تحصل على

func f() (int, *MyError)
func g() (int, error) { x, err := f(); return x, err }

إذا كانت f تُرجع nil * MyError ، فإن g تُرجع نفس القيمة كخطأ غير معدوم ، وهو على الأرجح ليس المقصود منه. إذا كانت * MyError عبارة عن واجهة بدلاً من مؤشر بنية ، فإن التحويل يحافظ على عدم الهدوء ، ولكنه مع ذلك يكون دقيقًا.

للمحاولة ، قد تعتقد أنه نظرًا لأن المحاولة لن تؤدي إلا إلى القيم غير الصفرية ، فلا مشكلة. على سبيل المثال ، يعد هذا أمرًا مقبولاً في الواقع فيما يتعلق بإرجاع خطأ غير صفري عند الفشل ، كما أنه لا بأس به فيما يتعلق بإرجاع خطأ لا شيء عند نجاح f:

func g() (int, error) {
    return try(f()), nil
}

هذا جيد حقًا ، ولكن بعد ذلك قد ترى هذا وتفكر في إعادة كتابته

func g() (int, error) {
    return f()
}

الذي يبدو أنه يجب أن يكون هو نفسه ولكنه ليس كذلك.

هناك ما يكفي من التفاصيل الأخرى لمقترح المحاولة التي تحتاج إلى فحص وتقييم دقيقين في تجربة حقيقية بحيث يبدو أن اتخاذ قرار بشأن هذه الدقة بالذات سيكون من الأفضل تأجيله.

شكرا للجميع على كل ردود الفعل حتى الآن . في هذه المرحلة ، يبدو أننا حددنا الفوائد والمخاوف الرئيسية والآثار المحتملة الجيدة والسيئة لـ try . لإحراز تقدم ، يحتاج هؤلاء إلى مزيد من التقييم من خلال النظر في ما يعنيه try لقواعد الكود الفعلي. يدور النقاش في هذه المرحلة حول هذه النقاط نفسها ويكررها.

الخبرة الآن أكثر قيمة من المناقشة المستمرة. نريد أن نشجع الأشخاص على قضاء بعض الوقت في تجربة الشكل الذي سيبدو عليه try في قواعد التعليمات البرمجية الخاصة بهم وكتابة تقارير التجربة وربطها على صفحة التعليقات .

لمنح الجميع بعض الوقت للتنفس والتجربة ، سنقوم بإيقاف هذه المحادثة مؤقتًا وإغلاق المشكلة للأسبوع ونصف القادم.

سيبدأ القفل في حوالي الساعة 1p PDT / 4p بتوقيت شرق الولايات المتحدة (في حوالي 3 ساعات من الآن) لمنح الأشخاص فرصة لإرسال منشور معلق. سنعيد فتح القضية لمزيد من المناقشة في 1 يوليو.

يرجى التأكد من أنه ليس لدينا أي نية للتسرع في أي ميزات لغوية جديدة دون أخذ الوقت الكافي لفهمها جيدًا والتأكد من أنها تحل مشكلات حقيقية في التعليمات البرمجية الحقيقية. سنأخذ الوقت اللازم لتصحيح هذا الأمر ، تمامًا كما فعلنا في الماضي.

صفحة الويكي هذه مزدحمة بالردود لفحصها / التعامل معها. أقترح أن تبدأ صفحة جديدة.

على أي حال ، لن يكون لدي الوقت لمواصلة البستنة في الويكي.

networkimprov ، شكرًا على مساعدتك في البستنة. لقد أنشأت قسمًا علويًا جديدًا في https://github.com/golang/go/wiki/Go2ErrorHandlingFeedback. أعتقد أن هذا يجب أن يكون أفضل من صفحة جديدة كاملة.

فاتني أيضًا ملاحظة روبرت 1p PDT / 4p EDT للقفل ، لذلك أغلقتها لفترة وجيزة مبكرًا جدًا. إنه مفتوح مرة أخرى ، لفترة أطول قليلاً.

كنت أخطط لكتابة هذا ، وأردت فقط إكماله قبل إغلاقه.

آمل ألا يرى فريق go الانتقادات ويشعر أنه مؤشر على شعور الأغلبية. هناك دائمًا ميل للأقلية الصوتية لإرباك المحادثة ، وأشعر أن هذا ربما حدث هنا. عندما يتعامل الجميع مع الأمر ، فإنه لا يشجع الآخرين الذين يريدون فقط التحدث عن الاقتراح كما هو.

لذلك - أود أن أوضح موقفي الإيجابي لما يستحق.

لديّ رمز يستخدم بالفعل مؤجلًا لتزيين / إضافة تعليقات توضيحية للأخطاء ، حتى بالنسبة لإخراج آثار المكدس ، وهذا هو السبب بالضبط.

يرى:
https://github.com/ugorji/go-ndb/blob/master/ndb/ndb.go#L331
https://github.com/ugorji/go-serverapp/blob/master/app/baseapp.go#L129
https://github.com/ugorji/go-serverapp/blob/master/app/webrouter.go#L180

التي تستدعي جميعها errorutil.OnError (* خطأ)

https://github.com/ugorji/go-common/blob/master/errorutil/errors.go#L193

هذا على غرار المساعدين المؤجلين الذين ذكرهم روس / روبرت سابقًا.

إنه نمط أستخدمه بالفعل ، FWIW. إنه ليس سحر. انها تماما مثل الذهاب IMHO.

أنا أستخدمه أيضًا مع معلمات مسماة ، وهو يعمل بشكل ممتاز.

أقول هذا لمعارضة الفكرة القائلة بأن أي شيء موصى به هنا هو سحر.

ثانيًا ، أردت إضافة بعض التعليقات على try (...) كوظيفة.
لها ميزة واحدة واضحة على الكلمة الرئيسية ، حيث يمكن توسيعها لتشمل المعلمات.

هناك وضعان للتمديد تمت مناقشتهما هنا:

  • تمديد حاول أن تأخذ تسمية للقفز إليها
  • تمديد حاول أن تأخذ معالج النموذج func (خطأ) خطأ

لكل منها ، من الضروري أن تأخذ المحاولة كدالة معلمة واحدة ، ويمكن تمديدها لاحقًا لتأخذ معلمة ثانية إذا لزم الأمر.

لم يتم اتخاذ القرار بشأن ما إذا كان تمديد المحاولة ضروريًا ، وإذا كان الأمر كذلك ، فما هو الاتجاه الذي يجب اتخاذه. وبالتالي ، فإن الاتجاه الأول هو محاولة التخلص من معظم التلعثم "إذا أخطأ! = لا شيء {إرجاع خطأ}" الذي كرهته إلى الأبد ولكنني اعتبرته تكلفة ممارسة الأعمال التجارية.

أنا شخصياً سعيد لأن المحاولة هي وظيفة ، يمكنني الاتصال بها ، على سبيل المثال يمكنني الكتابة

var u User = db.loadUser(try(strconv.Atoi(stringId)))

في مقابل:

var id int // i have to define this on its own if err is already defined in an enclosing block
id, err = strconv.Atoi(stringId)
if err != nil {
  return
}
var u User = db.loadUser(id)

كما ترون ، لقد أخذت 6 سطور وصولاً إلى 1. وخمسة من هذه السطور عبارة عن أسطر معيارية حقًا.
هذا شيء تعاملت معه عدة مرات ، وقد كتبت الكثير من أكواد وحزم go - يمكنك التحقق من github الخاص بي لرؤية بعض تلك التي نشرتها عبر الإنترنت ، أو مكتبة go-codec الخاصة بي.

أخيرًا ، لم تُظهر الكثير من التعليقات هنا مشاكل مع الاقتراح ، بقدر ما طرحوا طريقتهم المفضلة لحل المشكلة.

أنا شخصياً أشعر بسعادة غامرة لأن المحاولة (...) قادمة. وأنا أقدر الأسباب التي تجعل المحاولة كوظيفة هي الحل المفضل. من الواضح أنني أحب استخدام هذا التأجيل هنا ، لأنه منطقي فقط.

لنتذكر أحد مبادئ go الأساسية - المفاهيم المتعامدة التي يمكن دمجها جيدًا. يستفيد هذا الاقتراح من مجموعة من مفاهيم go المتعامدة (تأجيل ، ومعلمات إرجاع مسماة ، ووظائف مضمنة للقيام بما هو غير ممكن عبر رمز المستخدم ، وما إلى ذلك) لتوفير الفائدة الرئيسية التي
go التي طلبها المستخدمون عالميًا لسنوات ، أي تقليل / إلغاء إذا كان يخطئ! تُظهر استبيانات Go User أن هذه مشكلة حقيقية. يدرك فريق Go أنها مشكلة حقيقية. أنا سعيد لأن الأصوات الصاخبة للقلة لا تحرف موقف فريق Go كثيرًا.

كان لدي سؤال واحد حول المحاولة باعتباره الانتقال الضمني إذا أخطأ! = لا شيء.

إذا قررنا أن هذا هو الاتجاه ، فهل سيكون من الصعب تعديل "المحاولة من أجل العودة" إلى "try do a goto" ،
بالنظر إلى أن الانتقال قد حدد دلالات لا يمكنك تجاوز المتغيرات غير المخصصة؟

شكرا لملاحظتكugorji.

كان لدي سؤال واحد حول المحاولة باعتباره الانتقال الضمني إذا أخطأ! = لا شيء.

إذا قررنا أن هذا هو الاتجاه ، فهل سيكون من الصعب تعديل "المحاولة من أجل العودة" إلى "try do a goto" ،
بالنظر إلى أن الانتقال قد حدد دلالات لا يمكنك تجاوز المتغيرات غير المخصصة؟

نعم ، صحيح تمامًا. هناك بعض النقاش حول # 26058.
أعتقد أن "try-goto" لديه ثلاث ضربات على الأقل ضده:
(1) عليك الإجابة على المتغيرات غير المخصصة ،
(2) تفقد معلومات المكدس حول المحاولة التي فشلت ، والتي على النقيض من ذلك لا يزال بإمكانك التقاطها في حالة الإرجاع + التأجيل ، و
(3) يحب الجميع أن يكرهوا عند الانتقال.

نعم ، try هو السبيل للذهاب.
لقد حاولت إضافة try مرة واحدة ، وقد أعجبني ذلك.
التصحيح - https://github.com/ascheglov/go/pull/1
موضوع على Reddit - https://www.reddit.com/r/golang/comments/6vt3el/the_try_keyword_proofofconcept/

تضمين التغريدة

استمرار من https://github.com/golang/go/issues/32825#issuecomment -507120860 ...

بالتوافق مع الفرضية القائلة بأن إساءة استخدام try سيتم تخفيفها عن طريق مراجعة الكود ، والتدقيق ، و / أو معايير المجتمع ، يمكنني رؤية الحكمة في تجنب تغيير اللغة من أجل تقييد مرونة try . لا أرى الحكمة في توفير تسهيلات إضافية تشجع بشدة المظاهر الأكثر صعوبة / غير سارة في استهلاكها.

عند تفكيك هذا البعض ، يبدو أن هناك شكلين من تدفق التحكم في مسار الخطأ يتم التعبير عنه: يدوي ، وتلقائي. فيما يتعلق بتغليف الخطأ ، يبدو أن هناك ثلاثة أشكال يتم التعبير عنها: مباشر ، وغير مباشر ، وتمريري. ينتج عن هذا إجمالي ستة "أوضاع" من معالجة الخطأ.

يبدو الوضعان اليدوي المباشر والوضع المباشر التلقائي مقبولين:

wrap := func(err error) error {
  return fmt.Errorf("failed to process %s: %v", filename, err)
}

f, err := os.Open(filename)
if err != nil {
    return nil, wrap(err)
}
defer f.Close()

info, err := f.Stat()
if err != nil {
    return nil, wrap(err)
}
// in errors, named better, and optimized
WrapfFunc := func(format string, args ...interface{}) func(error) error {
  return func(err error) error {
    if err == nil {
      return nil
    }
    s := fmt.Sprintf(format, args...)
    return errors.Errorf(s+": %w", err)
  }
}

"اذهب
التفاف: = errors.WrapfFunc ("فشل معالجة٪ s" ، اسم الملف)

f ، يخطئ: = os.Open (اسم الملف)
محاولة (التفاف (يخطئ))
تأجيل و إغلاق ()

معلومات ، يخطئ: = f.Stat ()
محاولة (التفاف (يخطئ))

Manual Pass-through, and Automatic Pass-through modes are also simple enough to be agreeable (despite often being a code smell):
```go
f, err := os.Open(filename)
if err != nil {
    return nil, err
}
defer f.Close()

info, err := f.Stat()
if err != nil {
    return nil, err
}
f := try(os.Open(filename))
defer f.Close()

info := try(f.Stat())

ومع ذلك ، فإن الوضعين اليدوي غير المباشر والتلقائي غير المباشر كلاهما مرفوض تمامًا بسبب الاحتمال الكبير لوقوع أخطاء طفيفة:

defer errd.Wrap(&err, "failed to do X for %s", filename)

var f *os.File
f, err = os.Open(filename)
if err != nil {
    return
}
defer f.Close()

var info os.FileInfo
info, err = f.Stat()
if err != nil {
    return
}
defer errd.Wrap(&err, "failed to do X for %s", filename)

f := try(os.Open(filename))
defer f.Close()

info := try(f.Stat())

مرة أخرى ، لا أستطيع أن أفهم عدم منعهم ، لكن تسهيل / مباركة الأوضاع غير المباشرة هو المكان الذي لا يزال فيه هذا يرفع إشارات حمراء واضحة بالنسبة لي. يكفي ، في هذا الوقت ، أن أبقى متشككًا بشكل قاطع في الفرضية بأكملها.

المحاولة يجب ألا تكون وظيفة لتجنب ذلك اللعين

info := try(try(os.Open(filename)).Stat())

تسريب الملف.

أعني أن عبارة try لن تسمح بالتسلسل. ومن الأفضل أن تبدو كمكافأة. على الرغم من وجود مشاكل التوافق.

sirkon نظرًا لأن try خاص ، فقد لا تسمح اللغة بتداخل try 's إذا كان ذلك مهمًا - حتى لو كان try يبدو وكأنه دالة. مرة أخرى ، إذا كان هذا هو الحاجز الوحيد مقابل try ، فيمكن معالجته بسهولة بطرق مختلفة ( go vet ، أو تقييد اللغة). دعنا ننتقل من هذا - لقد سمعنا ذلك عدة مرات الآن. شكرا.

دعنا ننتقل من هذا - لقد سمعنا ذلك مرات عديدة من قبل

"هذا ممل جدًا ، دعنا ننتقل من هذا"

هناك نظير جيد آخر:

- نظريتك تناقض الحقائق!
- الأسوأ بالنسبة للحقائق!

بواسطة هيجل

أعني أنك تحل مشكلة غير موجودة في الواقع. والطريقة القبيحة في ذلك.

دعنا نلقي نظرة على المكان الذي تظهر فيه هذه المشكلة بالفعل: التعامل مع الآثار الجانبية من العالم الخارجي ، هذا كل شيء. وهذا في الواقع أحد أسهل الأجزاء منطقيًا في هندسة البرمجيات. والأهم في ذلك. لا أستطيع أن أفهم لماذا نحتاج إلى تبسيط لأسهل شيء سيكلفنا قدرًا أقل من الموثوقية.

IMO هو أصعب مشكلة من هذا النوع هو الحفاظ على اتساق البيانات في الأنظمة الموزعة (وليس توزيعها في الواقع). ولم يكن التعامل مع الأخطاء مشكلة كنت أقاتل معها في Go عند حل هذه المشكلات. الافتقار إلى فهم الشرائح والخرائط ، وعدم وجود المجموع / الجبر / التباين / أيًا كانت الأنواع كانت أكثر إزعاجًا بدرجة كبيرة.

بما أن المناقشة هنا تتواصل بلا هوادة ، اسمحوا لي أن أكرر مرة أخرى:

الخبرة الآن أكثر قيمة من المناقشة المستمرة. نريد أن نشجع الأشخاص على قضاء بعض الوقت في تجربة الشكل الذي سيبدو عليه try في قواعد التعليمات البرمجية الخاصة بهم وكتابة تقارير التجربة وربطها على صفحة التعليقات.

إذا قدمت التجربة الملموسة دليلاً هامًا مع أو ضد هذا الاقتراح ، نود أن نسمع ذلك هنا. مضايقات الحيوانات الأليفة الشخصية ، والسيناريوهات الافتراضية ، والتصميمات البديلة ، وما إلى ذلك ، يمكننا الاعتراف بها ولكنها أقل قابلية للتنفيذ.

شكرا.

لا أريد أن أكون وقحًا هنا ، وأنا أقدر كل اعتدالك ، لكن المجتمع تحدث بشدة عن تغيير معالجة الأخطاء. تغيير الأشياء ، أو إضافة رمز جديد ، سوف يزعج _جميع الأشخاص الذين يفضلون النظام الحالي. لا يمكنك أن تجعل الجميع سعداء ، لذلك دعونا نركز على 88٪ يمكننا أن نجعلهم سعداء (الرقم مشتق من نسبة التصويت أدناه).

في وقت كتابة هذا التقرير ، كان مؤشر "اتركه وشأنه" عند 1322 صوتًا مقابل 158 صوتًا ضد. هذا الخيط هو 158 لأعلى و 255 لأسفل. إذا لم تكن هذه نهاية مباشرة لهذا الموضوع عند معالجة الخطأ ، فيجب أن يكون لدينا سبب وجيه للغاية لمواصلة دفع المشكلة.

من الممكن دائمًا أن تفعل ما يصرخ مجتمعك من أجله وأن تدمر منتجك في نفس الوقت بالضبط.

على الأقل ، أعتقد أن هذا الاقتراح المحدد يجب اعتباره فاشلاً.

لحسن الحظ ، لم يتم تصميم go من قبل اللجنة. نحن بحاجة إلى الوثوق في أن الأوصياء على اللغة التي نحبها جميعًا سيستمرون في اتخاذ القرار الأفضل في ضوء جميع البيانات المتاحة لهم ، ولن يتخذوا قرارًا بناءً على الرأي العام للجماهير. تذكر - يستخدمون go أيضًا ، مثلنا تمامًا. إنهم يشعرون بنقاط الألم ، مثلنا تمامًا.

إذا كان لديك منصب ، خذ الوقت الكافي للدفاع عنه بالطريقة التي يدافع بها فريق Go عن مقترحاتهم. وإلا فأنت تغرق فقط في المحادثة بمشاعر تطير ليلاً غير قابلة للتنفيذ ولا تدفع المحادثات إلى الأمام. وهذا يجعل الأمر أكثر صعوبة بالنسبة للأشخاص الذين يرغبون في المشاركة ، كما قد يرغب الأشخاص المذكورون في الانتظار حتى تختفي الضوضاء.

عندما بدأت عملية الاقتراح ، بذل روس قدرًا كبيرًا من التبشير بالحاجة إلى تقارير الخبرة كوسيلة للتأثير على اقتراح أو جعل طلبك مسموعًا. دعونا على الأقل نحاول احترام ذلك.

أخذ فريق go جميع التعليقات القابلة للتنفيذ في الاعتبار. لم يخذلونا بعد. اطلع على المستندات التفصيلية التي تم إنتاجها للاسم المستعار ، وللحصول على وحدات ، وما إلى ذلك. دعنا على الأقل نوليهم نفس الاهتمام ونقضي الوقت في التفكير في اعتراضاتنا ، والرد على موقفهم من اعتراضاتك ، وجعل تجاهل اعتراضك أكثر صعوبة.

لطالما كانت فائدة Go هي أنها لغة صغيرة وبسيطة ذات تركيبات متعامدة مصممة من قبل مجموعة صغيرة من الأشخاص الذين يفكرون في الفضاء بشكل نقدي قبل الالتزام بالمنصب. دعنا نساعدهم حيث نستطيع ، بدلاً من مجرد قول "انظر ، التصويت الشعبي يقول لا" - حيث قد لا يكون لدى العديد من الأشخاص الذين يصوتون خبرة كبيرة في الذهاب أو فهم الذهاب بشكل كامل. لقد قرأت ملصقات متسلسلة اعترفت بأنهم لا يعرفون بعض المفاهيم الأساسية لهذه اللغة الصغيرة والبسيطة المعترف بها. هذا يجعل من الصعب أخذ ملاحظاتك على محمل الجد.

على أي حال ، تمتص أنني أفعل هذا هنا - لا تتردد في إزالة هذا التعليق. لن أكون مستاء. لكن على أحد أن يقول هذا بصراحة!

يبدو هذا الاقتراح الثاني مشابهًا جدًا للمؤثرين الرقميين الذين ينظمون مسيرة لي. مسابقات الشعبية لا تقيم المزايا الفنية.

قد يكون الناس صامتين لكنهم ما زالوا يتوقعون Go 2. أنا شخصياً أتطلع إلى هذا وبقية Go 2. Go 1 هي لغة رائعة ومناسبة تمامًا لأنواع مختلفة من البرامج. آمل أن يقوم Go 2 بتوسيع ذلك.

أخيرًا ، سأقوم أيضًا بعكس تفضيلي لوجود try كبيان. الآن أنا أؤيد الاقتراح كما هو. بعد سنوات عديدة بموجب وعد المواطن "Go 1" ، يعتقد الناس أن Go قد نحت في الحجر. بسبب هذا الافتراض الإشكالي ، فإن عدم تغيير بناء الجملة في هذه الحالة يبدو وكأنه حل وسط أفضل بكثير في نظري الآن. تحرير: أتطلع أيضًا إلى رؤية تقارير الخبرة للتحقق من صحة الحقائق.

ملاحظة: أتساءل ما هو نوع المعارضة الذي سيحدث عندما يتم اقتراح الأدوية الجنيسة.

لدينا حوالي عشرة أدوات مكتوبة أثناء التنقل في شركتنا. قمت بتشغيل أداة tryhard مقابل قاعدة الكود الخاصة بنا ووجدت 933 مرشحًا محتملاً للتجربة. أنا شخصياً أعتقد أن وظيفة try () فكرة رائعة لأنها تحل أكثر من مجرد مشكلة معيارية للتعليمات البرمجية.

يفرض كلاً من المتصل والوظيفة / الطريقة لإرجاع الخطأ كمعامل أخير. لن يتم السماح بذلك:

var file= try(parse())

func parse()(err, result) {
}

إنه يفرض طريقة واحدة للتعامل مع الأخطاء بدلاً من الإعلان عن متغير الخطأ والسماح بشكل فضفاض للخطأ!

func Foo() (err error) {
    var file, ferr = os.Open("file1.txt")
    if ferr == nil {
               defer file.Close()
        var parsed, perr = parseFile(file)
        if perr != nil {
            return
        }
        fmt.Printf("%s", parsed)
    }
    return nil
}

باستخدام try () ، يكون الكود أكثر قابلية للقراءة واتساقًا وأمانًا في رأيي:

func Foo() (err error) {
    var file = try(os.Open("file.txt"))
        defer file.Close()
    var parsed = try(parseFile(file))
    fmt.Printf(parsed)
    return
}

أجريت بعض التجارب المشابهة لما فعلته lpar على جميع مستودعات Go غير المؤرشفة في Heroku (العامة والخاصة).

النتائج في هذا المحتوى: https://gist.github.com/freeformz/55abbe5da61a28ab94dbb662bfc7f763

cc davecheney

ubikenobi وظيفتك الأكثر أمانًا ~ كانت تتسرب.

أيضًا ، لم أر مطلقًا قيمة تم إرجاعها بعد حدوث خطأ. على الرغم من ذلك ، يمكنني أن أتخيل أنه من المنطقي عندما تكون الوظيفة تدور حول الخطأ والقيم الأخرى التي يتم إرجاعها لا تتوقف على الخطأ نفسه (ربما يؤدي إلى إرجاع خطأين مع "حماية" القيم السابقة الثانية).

أخيرًا ، على الرغم من أنه ليس شائعًا ، يوفر err == nil اختبارًا شرعيًا لبعض العوائد المبكرة.

تضمين التغريدة

شكرًا للإشارة إلى التسرب ، لقد نسيت إضافة defer.Close () في كلا المثالين. (محدث الآن).

نادرًا ما أرى عائدًا خاطئًا في هذا الترتيب أيضًا ، لكن لا يزال من الجيد أن تكون قادرًا على الإمساك بها في وقت الترجمة إذا كانت أخطاء وليس حسب التصميم.

أرى أن الأمر يخطئ == لا شيء كاستثناء من القاعدة في معظم الحالات. يمكن أن يكون مفيدًا في بعض الحالات كما ذكرت ولكن ما لا يعجبني هو اختيار المطورين بشكل غير متسق دون سبب وجيه. لحسن الحظ ، فإن الغالبية العظمى من العبارات في قاعدة بياناتنا خاطئة! = لا شيء ، والتي يمكن أن تستفيد بسهولة من وظيفة try ().

  • قمت بتشغيل tryhard مقابل واجهة برمجة تطبيقات Go كبيرة والتي أحافظ عليها مع فريق من أربعة مهندسين آخرين بدوام كامل. في 45580 سطرًا من كود Go ، حدد tryhard أخطاء 301 لإعادة الكتابة (لذلك ، سيكون تغيير + 301 / -903) ، أو إعادة كتابة حوالي 2٪ من الكود بافتراض أن كل خطأ يستغرق 3 أسطر تقريبًا. مع الأخذ في الاعتبار التعليقات والمسافات البيضاء والواردات وما إلى ذلك التي تبدو كبيرة بالنسبة لي.
  • لقد كنت أستخدم أداة خط tryhard لاستكشاف كيف سيغير try عملي ، ويتدفق بشكل شخصي بشكل جيد للغاية بالنسبة لي! يبدو لي أن محاولة الفعل أوضح لي أن شيئًا ما يمكن أن يحدث بشكل خاطئ في وظيفة الاستدعاء ، وينجز ذلك بشكل مضغوط. أنا معتاد جدًا على كتابة if err != nil ، ولا أمانع حقًا ، لكنني لا أمانع في التغيير أيضًا. كتابة وإعادة هيكلة المتغير الفارغ الذي يسبق الخطأ (أي جعل الشريحة / الخريطة / المتغير الفارغ للعودة) بشكل متكرر ربما يكون أكثر تعقيدًا من المتغير err نفسه.
  • من الصعب قليلاً متابعة جميع خيوط المناقشة ، لكنني أشعر بالفضول لمعرفة ما يعنيه هذا بالنسبة لتغليف الأخطاء. سيكون من الرائع أن يكون try متغيرًا إذا كنت تريد إضافة سياق اختياريًا مثل try(json.Unmarshal(b, &accountBalance), "failed to decode bank account info for user %s", user) . تحرير: ربما تكون هذه النقطة خارج الموضوع ؛ من النظر إلى إعادة الكتابة بدون محاولة ، هذا هو المكان الذي يحدث فيه هذا ، على الرغم من ذلك.
  • إنني أقدر حقًا الفكرة والرعاية التي يتم وضعها في هذا! يعد التوافق مع الإصدارات السابقة والاستقرار أمرًا مهمًا حقًا بالنسبة لنا وقد كان جهد Go 2 حتى الآن سلسًا حقًا للحفاظ على المشاريع. شكرا!

ألا يجب أن يتم ذلك على مصدر تم فحصه بواسطة Gophers ذوي الخبرة للتأكد من أن البدائل عقلانية؟ ما مقدار إعادة الكتابة "2٪" التي يجب إعادة كتابتها بمعالجة واضحة؟ إذا لم نكن نعرف ذلك ، فسيظل LOC مقياسًا عديم الفائدة نسبيًا.

* وهذا هو بالضبط سبب تركيز رسالتي في وقت سابق من هذا الصباح على "أوضاع" معالجة الأخطاء. من الأسهل والأكثر موضوعية مناقشة أنماط معالجة الخطأ try يسهل ومن ثم تصارع مع المخاطر المحتملة للكود الذي من المحتمل أن نكتبه بدلاً من تشغيل عداد خط تعسفي.

kingishb كم عدد المواقع _try_ التي تم العثور عليها في الوظائف العامة من الحزم غير الرئيسية؟ يجب أن تُرجع الوظائف العامة عادةً أخطاء الحزمة الأصلية (أي مغلفة أو مزينة) ....

Networkimprov هذه صيغة مفرطة في التبسيط لأحاسيسي. حيث يكون هذا صحيحًا من حيث إرجاع أسطح واجهة برمجة التطبيقات لأخطاء قابلة للفحص. من المناسب عادةً إضافة سياق إلى رسالة خطأ بناءً على ملاءمة السياق ، وليس موضعه في مكدس الاستدعاءات.

من المحتمل أن العديد من الإيجابيات الكاذبة تتقدم في المقاييس الحالية. وماذا عن الأخطاء التي تحدث بسبب اتباع الممارسات المقترحة (https://blog.golang.org/errors-are-values)؟ من المحتمل أن يقلل try من استخدام مثل هذه الممارسات ، وبهذا المعنى ، فهي أهداف رئيسية للاستبدال (ربما تكون واحدة من حالات الاستخدام الوحيدة التي تثير فضولني حقًا). لذا ، مرة أخرى ، يبدو أن هذا لا طائل من ورائه في التخلص من المصدر الحالي دون بذل المزيد من العناية الواجبة.

شكرًا ubikenobi و freeformz و kingishb على جمع بياناتك ، أقدر ذلك كثيرًا! جانبا ، إذا قمت بتشغيل tryhard مع الخيار -err="" إذا ستحاول أيضًا العمل مع الكود حيث يُسمى متغير الخطأ شيئًا آخر غير err (مثل e ). قد ينتج عن هذا المزيد من الحالات ، اعتمادًا على قاعدة الشفرة (ولكن من المحتمل أيضًا زيادة فرصة الإيجابيات الخاطئة).

griesemer في حال كنت تبحث عن المزيد من نقاط البيانات. لقد قمت بتشغيل tryhard مقابل اثنتين من خدماتنا الصغيرة ، مع النتائج التالية:

cloc v 1.82 / تريهارد
13280 سطور كود Go / 148 تم تحديدها للتجربة (1٪)

خدمة أخرى:
9768 سطور كود Go / 50 تم تحديدها للتجربة (0.5٪)

بعد ذلك ، قام tryhard بفحص مجموعة أوسع من الخدمات الصغيرة المتنوعة:

314343 خطوط كود Go / 1563 المحددة للتجربة (0.5٪)

القيام بفحص سريع. عادةً ما تكون أنواع الحزم التي يمكن لـ try تحسينها هي محولات / أغلفة خدمة تقوم بإرجاع خطأ (GRPC) بشكل شفاف من الخدمة المغلفة.

أتمنى أن يساعدك هذا.

إنها فكرة سيئة للغاية.

  • متى يظهر err var لـ defer ؟ ماذا عن "صريح أفضل من ضمني"؟
  • نحن نستخدم قاعدة بسيطة: يجب أن تجد بسرعة مكانًا واحدًا بالضبط حيث تم إرجاع الخطأ. يتم تغليف كل خطأ بالسياق لفهم الخطأ وأين يحدث. سيخلق defer الكثير من الأكواد القبيحة التي يصعب فهمها.
  • كتب davecheney منشورًا رائعًا عن الأخطاء والاقتراح يتعارض تمامًا مع كل شيء في هذا المنشور.
  • أخيرًا ، إذا كنت تستخدم os.Exit ، فلن يتم التحقق من أخطائك.

لقد قمت للتو بتشغيل tryhard على حزمة (مع البائع) وأبلغت عن 2478 مع انخفاض عدد الكود من 873934 إلى 851178 لكنني لست متأكدًا كيفية تفسير ذلك لأنني لا أعرف مقدار ذلك بسبب الإفراط في الالتفاف (مع افتقار stdlib إلى دعم التفاف خطأ تتبع المكدس) أو مقدار هذا الرمز الذي يتعلق بمعالجة الأخطاء.

ما أعرفه ، مع ذلك ، هو أنني أهدرت هذا الأسبوع وحده قدرًا محرجًا من الوقت بسبب نسخ المعكرونة مثل if err != nil { return nil } والأخطاء التي تبدو مثل error: cannot process ....file: cannot parse ...file: cannot open ...file .

\ لن أضع وزنيًا كبيرًا على عدد الأصوات إلا إذا كنت تعتقد أن هناك فقط 3000 مطور Go هناك. يرجع عدد الأصوات المرتفعة على غير المقترحات الأخرى ببساطة إلى حقيقة أن المشكلة وصلت إلى قمة HN و Reddit - مجتمع Go ليس معروفًا تمامًا بافتقاره إلى العقيدة و / أو عدم قول ذلك. - يجب أن يتفاجأ أحد بشأن عدد الأصوات.

كما أنني لن آخذ محاولات الاستئناف إلى السلطة على محمل الجد أيضًا ، لأن هذه السلطات نفسها معروفة برفضها للأفكار والمقترحات الجديدة حتى بعد الإشارة إلى جهلها و / أو سوء فهمها.
\

قمنا بتشغيل tryhard -err="" على أكبر خدمة (± 163 ألف سطر من التعليمات البرمجية بما في ذلك الاختبارات) - لقد وجدنا 566 تكرارًا. أظن أنه سيكون أكثر من الناحية العملية ، حيث تم كتابة بعض الكود مع وضع if err != nil في الاعتبار ، لذلك تم تصميمه حوله (يتبادر إلى الذهن مقالة Rob Pike "الأخطاء هي القيم" حول تجنب التكرار).

griesemer لقد أضفت ملفًا جديدًا إلى المحتوى. تم إنشاؤه مع -err = "". لقد قمت بالتحقق من ذلك وهناك بعض التغييرات. لقد قمت أيضًا بتحديث tryhard هذا الصباح أيضًا ، لذلك تم استخدام الإصدار الأحدث أيضًا.

griesemer أعتقد أن tryhard سيكون أكثر فائدة إذا كان بإمكانه حساب:

أ) عدد مواقع الاتصال التي تسفر عن خطأ
ب) عدد معالجات كشف الحساب if err != nil [&& ...] (المرشحون لـ on err # 32611)
ج) عدد تلك التي تعيد أي شيء (المرشحين لـ defer # 32676)
د) عدد تلك التي ترجع err (المرشحين لـ try() )
هـ) عدد تلك التي تعمل في وظائف مُصدرة للحزم غير الرئيسية (من المحتمل أن تكون إيجابية كاذبة)

مقارنة إجمالي LoC بحالات return err Sorta تفتقر إلى السياق ، IMO.

networkimprov متفق عليه - تم طرح اقتراحات مماثلة من قبل. سأحاول أن أجد بعض الوقت خلال الأيام القادمة لتحسين هذا.

فيما يلي إحصائيات تشغيل tryhard على قاعدة الشفرة الداخلية الخاصة بنا (فقط الكود الخاص بنا ، وليس التبعيات):

قبل:

  • 882 ملفات .go
  • 352434 loc
  • 329909 loc

بعد المحاولة:

  • استبدال 2701 (متوسط ​​الاستبدال 3.1 / ملف)
  • 345364 لوك (-2.0٪)
  • 322838 موقع غير فارغ (-2.1٪)

تحرير: الآن بعد أن قامgriesemer بتحديث tryhard لتضمين إحصاءات موجزة ، إليك زوجين آخرين:

  • 39.2٪ من كشوف الحساب if هي if <err> != nil
  • 69.6٪ من هؤلاء مرشحين try

بالنظر إلى البدائل التي وجدها tryhard ، هناك بالتأكيد أنواع من التعليمات البرمجية حيث يكون استخدام try سائدًا للغاية ، وأنواع أخرى نادرًا ما يتم استخدامها.

لقد لاحظت أيضًا بعض الأماكن التي لا يمكن أن تتحول فيها tryhard ، ولكنها ستستفيد كثيرًا من المحاولة. على سبيل المثال ، إليك بعض الأكواد التي لدينا لفك تشفير الرسائل وفقًا لبروتوكول سلك بسيط (تم تعديله من أجل البساطة / الوضوح):

func (req *Request) Decode(r Reader) error {
    typ, err := readByte(r)
    if err != nil {
        return err
    }
    req.Type = typ
    req.Body, err = readString(r)
    if err != nil {
        return unexpected(err)
    }

    req.ID, err = readID(r)
    if err != nil {
        return unexpected(err)
    }
    n, err := binary.ReadUvarint(r)
    if err != nil {
        return unexpected(err)
    }
    req.SubIDs = make([]ID, n)
    for i := range req.SubIDs {
        req.SubIDs[i], err = readID(r)
        if err != nil {
            return unexpected(err)
        }
    }
    return nil
}

// unexpected turns any io.EOF into an io.ErrUnexpectedEOF.
func unexpected(err error) error {
    if err == io.EOF {
        return io.ErrUnexpectedEOF
    }
    return err
}

بدون try ، كتبنا للتو unexpected عند نقاط الإرجاع حيث تكون هناك حاجة لأنه لا يوجد تحسين كبير من خلال التعامل معه في مكان واحد. ومع ذلك ، باستخدام try ، يمكننا تطبيق تحويل الخطأ unexpected مع تأجيل ثم تقصير الكود بشكل كبير ، مما يجعله أكثر وضوحًا وأسهل في التقليل:

func (req *Request) Decode(r Reader) (err error) {
    defer func() { err = unexpected(err) }()

    req.Type = try(readByte(r))
    req.Body = try(readString(r))
    req.ID = try(readID(r))

    n := try(binary.ReadUvarint(r))
    req.SubIDs = make([]ID, n)
    for i := range req.SubIDs {
        req.SubIDs[i] = try(readID(r))
    }
    return nil
}

cespare تقرير رائع!

المقتطف المصغر بالكامل أفضل بشكل عام ، لكن الأقواس أسوأ مما توقعت ، و try داخل الحلقة سيء كما توقعت.

الكلمة الرئيسية أكثر قابلية للقراءة ومن السريالية بعض الشيء أن هذه نقطة يختلف عنها الكثير من الآخرين. ما يلي قابل للقراءة ولا يجعلني قلقًا بشأن التفاصيل الدقيقة بسبب إرجاع قيمة واحدة فقط (على الرغم من أنه لا يزال من الممكن ظهورها في وظائف أطول و / أو تلك التي تحتوي على الكثير من التداخل):

func (req *Request) Decode(r Reader) (err error) {
    defer func() { err = wrapEOF(err) }()

    req.Type = try readByte(r)
    req.Body = try readString(r)
    req.ID = try readID(r)

    n := try binary.ReadUvarint(r)
    req.SubIDs = make([]ID, n)
    for i := range req.SubIDs {
        req.SubIDs[i], err = readID(r)
        try err
    }
    return nil
}

* لكي نكون منصفين بشأن ذلك ، فإن تسليط الضوء على الكود سيساعد كثيرًا ، لكن هذا يبدو وكأنه أحمر شفاه رخيص.

هل تفهم أن أكبر ميزة تحصل عليها في حالة وجود كود سيء حقًا؟

إذا كنت تستخدم unexpected() أو قمت بإرجاع الخطأ كما هو ، فأنت لا تعرف شيئًا عن الكود والتطبيق الخاص بك.

لا يمكن أن يساعدك try في كتابة كود أفضل ، ولكن يمكن أن ينتج المزيد من الأكواد السيئة.

cespare يمكن أيضًا أن يكون مفكك التشفير عبارة عن هيكل به نوع خطأ بداخله ، مع فحص الطرق لـ err == nil قبل كل عملية وإرجاع قيمة منطقية موافق.

نظرًا لأن هذه هي العملية التي نستخدمها لبرامج الترميز ، فإن try عديم الفائدة تمامًا لأنه يمكن للمرء بسهولة إنشاء لغة غير سحرية وأقصر وأكثر إيجازًا للتعامل مع الأخطاء لهذه الحالة المحددة.

makhov بعبارة "رمز سيء حقًا" ، أفترض أنك تقصد رمزًا لا يلف الأخطاء.

إذا كان الأمر كذلك ، فيمكنك أن تأخذ رمزًا يشبه هذا:

a, b, c, err := someFn()
if err != nil {
  return ..., errors.Wrap(err, ...)
}

وقم بتحويلها إلى كود [1] متطابق لغويًا يبدو كالتالي:

a, b, c, err := someFn()
try(errors.Wrap(err, ...))

لا يشير الاقتراح إلى أنه يجب عليك استخدام "تأجيل" لتغليف الأخطاء ، فقط تشرح سبب عدم ضرورة الكلمة الأساسية handle للتكرار السابق للاقتراح ، حيث يمكن تنفيذها من حيث التأجيل دون أي تغييرات في اللغة.

(يبدو أن تعليقك الآخر يستند أيضًا إلى أمثلة أو رمز زائف في الاقتراح ، بدلاً من جوهر ما يتم اقتراحه)

قمت بتشغيل tryhard على قاعدة الشفرة الخاصة بي باستخدام 54K LOC ، وتم العثور على 1116 حالة.
لقد رأيت الفرق ، ويجب أن أقول إن لدي القليل جدًا من البنية التي يمكن أن تستفيد بشكل كبير من المحاولة ، لأن استخدامي الكامل تقريبًا لنوع if err != nil من البناء هو كتلة بسيطة من مستوى واحد تقوم فقط بإرجاع خطأ مع السياق المضاف. أعتقد أنني وجدت حالتين فقط حيث يقوم try بتغيير تكوين الكود.

بمعنى آخر ، ما أراه هو أن try في شكله الحالي يعطيني:

  • كتابة أقل (تقليل ضخم ~ 30 حرفًا لكل مرة ، يُشار إليها بعلامة "**" أدناه)
-       **if err := **json.NewEncoder(&buf).Encode(in)**; err != nil {**
-               **return err**
-       **}**
+       try(json.NewEncoder(&buf).Encode(in))

بينما يقدم لي هذه المشاكل:

  • طريقة أخرى للتعامل مع الأخطاء
  • إشارة بصرية مفقودة لتقسيم مسار التنفيذ

كما كتبت سابقًا في هذا الموضوع ، يمكنني العيش مع try ، لكن بعد تجربته على الكود الخاص بي ، أعتقد أنني أفضل شخصيًا عدم تقديم هذا إلى اللغة. بلدي .02 دولار

ميزة عديمة الفائدة , توفر الكتابة ، ولكنها ليست مشكلة كبيرة.
أنا أفضل اختيار الطريقة القديمة.
كتابة المزيد من معالج الأخطاء لجعل البرمجة سهلة لحل مشاكل التصوير.

فقط بعض الأفكار ...

هذا المصطلح مفيد في الذهاب ولكنه مجرد: مصطلح يجب عليك
تعليم للقادمين الجدد. يجب أن يتعلم مبرمج go الجديد ذلك ، وإلا فهم
قد يتم إغراء إعادة بناء معالجة الأخطاء "المخفية". أيضا ،
الكود ليس أقصر باستخدام هذا المصطلح (على العكس تمامًا) إلا إذا نسيت
لعد الأساليب.

الآن دعونا نتخيل أنه تم تنفيذ المحاولة ، ما مدى فائدة هذا المصطلح
أن حالة الاستخدام؟ مع مراعاة:

  • حاول أن تجعل التنفيذ أقرب بدلاً من نشره عبر الطرق.
  • المبرمجون سوف يقرأون ويكتبون الكود بالمحاولة أكثر من ذلك بكثير
    مصطلح محدد (نادرًا ما يستخدم باستثناء كل مهام محددة). أ
    يصبح المصطلح الأكثر استخدامًا أكثر طبيعية وقابلية للقراءة ما لم يكن هناك واضح
    العيب ، والذي من الواضح أنه ليس هو الحال هنا إذا قارنا كلاهما مع
    عقل متفتح.

لذلك ربما سيتم اعتبار هذا المصطلح محله المحاولة.

Em ter، 2 de jul de 2019 18:06، as [email protected] escreveu:

cespare https://github.com/cespare يمكن أن يكون جهاز فك التشفير أيضًا منظمًا مع
نوع خطأ بداخله ، مع فحص الأساليب لـ err == nil من قبل
كل عملية وإرجاع موافق منطقي.

نظرًا لأن هذه هي العملية التي نستخدمها لبرامج الترميز ، فإن المحاولة عديمة الفائدة تمامًا
لأنه يمكن للمرء بسهولة أن يصنع لغة غير سحرية وأقصر وأكثر إيجازًا
لمعالجة الأخطاء لهذه الحالة المحددة.

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/golang/go/issues/32437؟email_source=notifications&email_token=AAT5WM3YDDRZXVXOLDQXKH3P5O7L5A5CNFSM4HTGCZ72YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/AAT5WMYXLLO74CIM6H4Y2RLP5O7L5ANCNFSM4HTGCZ7Q
.

الإسهاب في معالجة الخطأ أمر جيد في رأيي. بعبارة أخرى ، لا أرى سببًا قويًا لاستخدام المحاولة.

أنا منفتح على هذه الفكرة ولكني أشعر أنه يجب أن تتضمن بعض الآليات لتحديد مكان حدوث انقسام التنفيذ. سيكون Xerror / Is جيدًا في بعض الحالات (على سبيل المثال ، إذا كان الخطأ ErrNotExists ، فيمكنك الاستدلال على حدوثه في Open) ، ولكن بالنسبة للآخرين - بما في ذلك الأخطاء القديمة في المكتبات - لا يوجد بديل.

هل يمكن تضمين عنصر مدمج مشابه للاسترداد لتوفير معلومات السياق حول المكان الذي تغير فيه تدفق التحكم؟ ربما ، لإبقائها رخيصة ، تستخدم وظيفة منفصلة بدلاً من try ().

أو ربما تصحيح أخطاء. حاول باستخدام نفس بناء الجملة مثل try () ولكن مع معلومات تصحيح الأخطاء المضافة؟ بهذه الطريقة يمكن أن تكون try () مفيدة بنفس القدر مع التعليمات البرمجية باستخدام الأخطاء القديمة ، دون إجبارك على اللجوء إلى معالجة الأخطاء القديمة.

قد يكون البديل هو محاولة () الالتفاف وإضافة سياق ولكن في معظم الحالات قد يؤدي ذلك إلى تقليل الأداء دون أي غرض ، ومن ثم اقتراح وظائف إضافية.

تحرير: بعد كتابة هذا ، خطر لي أن المترجم يمكنه تحديد أي متغير من try () لاستخدامه بناءً على ما إذا كانت أي عبارات تأجيل تستخدم وظيفة توفير السياق هذه المشابهة لـ "الاسترداد". لست متأكدا من مدى تعقيد هذا بالرغم من ذلك

lestrrat لن أقول رأيي في هذا التعليق ، لكن إذا كانت هناك فرصة لشرح كيف يمكن أن تؤثر كلمة "try" في صالحنا ، فسيكون من الممكن كتابة رمزين مميزين أو أكثر في عبارة if. لذلك إذا كتبت 200 شرط في عبارة if ، فستتمكن من تقليل العديد من الأسطر.

if try(foo()) == 1 && try(bar()) == 2 {
  // err
}
n1, err := foo()
if err != nil {
  // err
}
n2, err := bar()
if err != nil {
  // err
}
if n1 == 1 && n2 == 2 {
  // err
}

mattn هذا هو الشيء رغم ذلك ، _نظريًا_ أنت محق تمامًا. أنا متأكد من أنه يمكننا التوصل إلى حالات يكون فيها try مناسبًا بشكل رائع.

لقد قدمت للتو بيانات في الحياة الواقعية ، على الأقل _I_ لم تعثر تقريبًا على أي تواتر لمثل هذه التركيبات التي قد تستفيد من الترجمة لتجربتها في _ my code_.

من المحتمل أنني أكتب رمزًا مختلفًا عن بقية العالم ، لكنني اعتقدت أن الأمر يستحق أن يتناغم شخص ما في ذلك ، استنادًا إلى ترجمة PoC ، أن البعض منا لا يكسب كثيرًا من تقديم try في اللغة.

جانبا ، ما زلت لا أستخدم أسلوبك في الكود الخاص بي. سأكتبها كـ

n1 := try(foo())
n2 := try(bar())
if n1 == 1 && n2 == 2 {
   return errors.New(`boo`)
}

لذلك ما زلت سأوفر نفس المقدار من الكتابة لكل مثيل من هؤلاء n1 / n2 / .... n (n) s

لماذا لديك كلمة رئيسية (أو وظيفة) على الإطلاق؟

إذا كان سياق الاستدعاء يتوقع قيم n + 1 ، فسيكون كل شيء كما كان من قبل.

إذا كان سياق الاستدعاء يتوقع قيم n ، يبدأ سلوك المحاولة.

(هذا مفيد بشكل خاص في الحالة n = 1 حيث تأتي كل الفوضى الفظيعة.)

يسلط ID الخاص بي بالفعل الضوء على قيم العودة التي تم تجاهلها ؛ سيكون من التافه تقديم إشارات مرئية لهذا إذا لزم الأمر.

balasanjay نعم ، أخطاء التغليف هي القضية. ولكن لدينا أيضًا تسجيلات وردود فعل مختلفة على أخطاء مختلفة (ما الذي يجب أن نفعله بمتغيرات الخطأ ، على سبيل المثال sql.NoRows ؟) ، رمز قابل للقراءة وما إلى ذلك. نكتب defer f.Close() فور فتح الملف لتوضيح الأمر للقراء. نتحقق من الأخطاء على الفور لنفس السبب.

الأهم من ذلك ، أن هذا الاقتراح ينتهك قاعدة " الأخطاء قيم ". هذه هي الطريقة التي تم تصميم Go. وهذا الاقتراح يتعارض بشكل مباشر مع القاعدة.

try(errors.Wrap(err, ...)) هو جزء آخر من الشفرة الرهيبة لأنه يتعارض مع كل من هذا الاقتراح وتصميم Go الحالي.

أميل إلى الاتفاق معlestrrat
عادةً ما تكون foo () و bar () في الواقع:
SomeFunctionWithGoodName (Parm1، Parms2)

إذن فإن بناء جملة mattn المقترح سيكون في الواقع:

if  try(SomeFunctionWithGoodName(Parm1, Parms2)) == 1 && try(package.SomeOtherFunction(Parm1, Parms2,Parm3))) == 2 {


} 

المقروئية ستكون عادة في حالة من الفوضى.

ضع في اعتبارك قيمة الإرجاع:
someRetVal, err := SomeFunctionWithGoodName(Parm1, Parms2)
قيد الاستخدام أكثر من مجرد المقارنة بعنصر ثابت مثل 1 أو 2 ولا يزداد سوءًا ولكنه يتطلب ميزة التعيين المزدوجة:

if  a := try(SomeFunctionWithGoodName(Parm1, Parms2)) && b:= try(package.SomeOtherFunction(Parm1, Parms2,Parm3))) {


} 

بالنسبة لجميع حالات الاستخدام ("كم ساعدني tryhard"):

  1. أعتقد أنك سترى فرقًا كبيرًا بين المكتبات والملفات القابلة للتنفيذ ، سيكون من المثير للاهتمام أن نرى من الآخرين إذا حصلوا على هذا الاختلاف أيضًا
  2. اقتراحي هو عدم مقارنة٪ حفظ في سطور في التعليمات البرمجية ولكن بالأحرى عدد الأخطاء في التعليمات البرمجية مقابل الرقم المعاد بناءه.
    (كان رأيي في هذا
    $find /path/to/repo -name '*.go' -exec cat {} \; | grep "err :=" | wc -l
    )

تضمين التغريدة

هذا الاقتراح يخالف قاعدة "الأخطاء هي القيم"

ليس صحيحا. الأخطاء لا تزال قيم في هذا الاقتراح. يقوم try() بتبسيط تدفق التحكم من خلال كونه اختصارًا لـ if err != nil { return ...,err } . النوع error هو نوع "خاص" إلى حد ما من خلال كونه نوع واجهة مضمن. هذا الاقتراح هو مجرد إضافة وظيفة مضمنة تكمل النوع error . لا يوجد شيء غير عادي هنا.

ngrilly تبسيط؟ كيف؟

func (req *Request) Decode(r Reader) error {
    defer func() { err = unexpected(err) }()

    req.Type = try(readByte(r))
    req.Body = try(readString(r))
    req.ID = try(readID(r))

    n := try(binary.ReadUvarint(r))
    req.SubIDs = make([]ID, n)
    for i := range req.SubIDs {
        req.SubIDs[i] = try(readID(r))
    }
    return nil
}

كيف أفهم أنه تم إرجاع الخطأ داخل الحلقة؟ لماذا تم تعيينه إلى err var وليس foo ؟
هل من الأسهل وضعها في الاعتبار وعدم الاحتفاظ بها في التعليمات البرمجية؟

تضمين التغريدة

الأقواس أسوأ مما كنت أتوقع [...] الكلمة الرئيسية أكثر قابلية للقراءة ومن السريالية بعض الشيء أن هذه نقطة يختلف عليها الكثير من الآخرين.

يعد الاختيار بين كلمة رئيسية ووظيفة مضمنة في الغالب مسألة جمالية ونحوية. أنا بصراحة لا أفهم لماذا هذا مهم جدا لعينيك.

ملاحظة: تتميز الوظيفة المضمنة بأنها متوافقة مع الإصدارات السابقة ، وقابلة للتوسيع مع معلمات أخرى في المستقبل ، وتجنب المشكلات المتعلقة بأسبقية المشغل. الكلمة الرئيسية لها ميزة ... كونها كلمة رئيسية ، والإشارة try هي كلمة "خاصة".

تضمين التغريدة

تبسيط؟

نعم. الكلمة الصحيحة هي "تقصير".

try() يختصر الكود عن طريق استبدال النمط if err != nil { return ..., err } باستدعاء الوظيفة المضمنة try() .

إنه تمامًا مثل تحديد نمط متكرر في التعليمات البرمجية الخاصة بك ، وتقوم باستخراجه في وظيفة جديدة.

لدينا بالفعل وظائف مضمنة مثل append () ، والتي يمكننا استبدالها بكتابة الشفرة "in extenso" بأنفسنا في كل مرة نحتاج فيها إلى إلحاق شيء ما بشريحة. ولكن نظرًا لأننا نفعل ذلك طوال الوقت ، فقد تم دمجه في اللغة. لا يختلف try() .

كيف أفهم أنه تم إرجاع الخطأ داخل الحلقة؟

يعمل try() في الحلقة تمامًا مثل try() في باقي الوظيفة ، خارج الحلقة. إذا readID() خطأ ، فإن الدالة ترجع الخطأ (بعد تزيين if).

لماذا تم تعيينه إلى err var وليس foo؟

لا أرى متغير foo في مثال الكود الخاص بك ...

makhov أعتقد أن المقتطف غير مكتمل لأن الخطأ الذي تم إرجاعه لم يتم تسميته (أعدت قراءة الاقتراح بسرعة ولكن لم أستطع معرفة ما إذا كان اسم المتغير err هو الاسم الافتراضي إذا لم يتم تعيين أي شيء).

تعد الحاجة إلى إعادة تسمية المعلمات التي تم إرجاعها إحدى النقاط التي لا يحبها الأشخاص الذين يرفضون هذا الاقتراح.

func (req *Request) Decode(r Reader) (err error) {
    defer func() { err = unexpected(err) }()

    req.Type = try(readByte(r))
    req.Body = try(readString(r))
    req.ID = try(readID(r))

    n := try(binary.ReadUvarint(r))
    req.SubIDs = make([]ID, n)
    for i := range req.SubIDs {
        req.SubIDs[i] = try(readID(r))
    }
    return nil
}

pierrec ربما لدينا وظيفة مثل recover() لاسترداد الخطأ إذا لم يكن في المعلمة المسماة؟
defer func() {err = unexpected(tryError())}

makhov يمكنك جعلها أكثر وضوحا:

func (req *Request) Decode(r Reader) error {
    req.Type, err := readByte(r)
        try(err) // or add annotation like try(annotate(err, ...))
    req.Body, err := readString(r)
        try(err)
    req.ID, err := readID(r)
        try(err)

    n, err := binary.ReadUvarint(r)
        try(err)
    req.SubIDs = make([]ID, n)
    for i := range req.SubIDs {
        req.SubIDs[i], err := readID(r)
                try(err)
    }
    return nil
}

pierrec حسنًا ، دعنا نغيره:

func (req *Request) Decode(r Reader) error {
        var errOne, errTwo error
    defer func() { err = unexpected(???) }()

    req.Type = try(readByte(r))
    …
}

reusee ولماذا هو أفضل من هذا؟

func (req *Request) Decode(r Reader) error {
    req.Type, err := readByte(r)
        if err != nil { return err }
        …
}

في أي لحظة قررنا جميعًا أن هذا القصر أفضل من سهولة القراءة؟

flibustenet شكرًا لك على فهم المشكلة. يبدو أفضل بكثير ولكني ما زلت غير متأكد من أننا بحاجة إلى توافق مع الإصدارات السابقة من أجل هذا "التحسين" الصغير. إنه أمر مزعج للغاية إذا كان لدي تطبيق توقف عن البناء على الإصدار الجديد من Go:

package main

func main() {
    // ...
   try("a", "b")
    // ...
}

func try(a, b string) {
    // ...
}

makhov أوافق على أن هذا يحتاج إلى توضيح: هل أخطاء المحول البرمجي خارجة عندما لا يستطيع معرفة المتغير؟ اعتقدت أنه سيكون.
ربما يحتاج الاقتراح لتوضيح هذه النقطة؟ أو هل فاتني ذلك في المستند؟

flibustenet نعم هذه طريقة واحدة لاستخدام try () ولكن يبدو لي أنها ليست طريقة اصطلاحية لاستخدام try.

cespare مما كتبته يبدو أن تعديل قيم الإرجاع في التأجيل ميزة try لكن يمكنك فعل ذلك بالفعل.

https://play.golang.com/p/ZMauFmt9ezJ

(آسف إذا أساءت تفسير ما قلته)

@ jan-g بخصوص https://github.com/golang/go/issues/32437#issuecomment -507961463: ظهرت فكرة معالجة الأخطاء بشكل غير مرئي عدة مرات. تكمن المشكلة في مثل هذا النهج الضمني في إضافة خطأ إرجاع إلى وظيفة مستدعاة قد تتسبب في أن وظيفة الاستدعاء تتصرف بصمت وبشكل غير مرئي بشكل مختلف. نريد تمامًا أن نكون واضحين عند التحقق من الأخطاء. يتعارض النهج الضمني أيضًا مع المبدأ العام في Go بأن كل شيء واضح.

تضمين التغريدة

لقد جربت tryhand على أحد مشاريعي (https://github.com/komuw/meli) ولم يحدث أي تغيير.

gobin github.com/griesemer/tryhard
     Installed github.com/griesemer/[email protected] to ~/go/bin/tryhard

"" باش
~ / go / bin / tryhard -err "" -r
0

most of my err handling looks like;
```Go
import "github.com/pkg/errors"

func CreateDockerVolume(volName string) (string, error) {
    volume, err := VolumeCreate(volName)
    if err != nil {
        return "", errors.Wrapf(err, "unable to create docker volume %v", volName)
    }
    return volume.Name, nil
}

komuw بادئ ذي بدء ، تأكد من توفير وسيطة اسم الملف أو الدليل إلى tryhard ، كما في

tryhard -err="" -r .  // <<< note the dot
tryhard -err="" -r filename

أيضًا ، لن تتم إعادة كتابة الكود كما في تعليقك لأنه يعالج خطأ معينًا في كتلة if . يرجى قراءة وثائق tryhard عند تطبيقها. شكرا.

func CreateDockerVolume(volName string) (string, error) {
    volume, err := VolumeCreate(volName)
    if err != nil {
        return "", errors.Wrapf(err, "unable to create docker volume %v", volName)
    }
    return volume.Name, nil
}

هذا مثال مثير للاهتمام إلى حد ما. كان رد فعلي الأول عند النظر إليه هو السؤال عما إذا كان هذا سيؤدي إلى سلاسل خطأ متقطعة مثل:

unable to create docker volume: VolumeName: could not create volume VolumeName: actual problem

الإجابة هي أنها لا تفعل ذلك ، لأن الوظيفة VolumeCreate (من ريبو مختلف) هي:

func (cli *Client) VolumeCreate(ctx context.Context, options volumetypes.VolumeCreateBody) (types.Volume, error) {
        var volume types.Volume
        resp, err := cli.post(ctx, "/volumes/create", nil, options, nil)
        defer ensureReaderClosed(resp)
        if err != nil {
                return volume, err
        }
        err = json.NewDecoder(resp.body).Decode(&volume)
        return volume, err
}

بمعنى آخر ، تعتبر الزخرفة الإضافية للخطأ مفيدة لأن الوظيفة الأساسية لم تزين خطأها. يمكن تبسيط هذه الوظيفة الأساسية قليلاً باستخدام try .

ربما يجب أن تقوم الوظيفة VolumeCreate بتزيين أخطائها. ومع ذلك ، في هذه الحالة ، ليس من الواضح بالنسبة لي أن الوظيفة CreateDockerVolume يجب أن تضيف زخرفة إضافية ، نظرًا لعدم وجود معلومات جديدة لتقديمها.

neild
حتى إذا قام VolumeCreate بتزيين الأخطاء ، فسنظل بحاجة إلى CreateDockerVolume لإضافة الزخرفة الخاصة به ، حيث يمكن استدعاء VolumeCreate من وظائف أخرى مختلفة ، وإذا فشل شيء ما (ونأمل أن مسجّل) تريد أن تعرف ما الذي فشل - وهو في هذه الحالة CreateDockerVolume ،
ومع ذلك ، يعتبر اعتبار VolumeCreate جزءًا من واجهة عميل API.

الشيء نفسه ينطبق على المكتبات الأخرى - يمكن لـ os.Open تزيين اسم الملف وسبب الخطأ وما إلى ذلك ، ولكن
func ReadConfigFile(...
func WriteDataFile(...
إلخ - استدعاء os.Open هي الأجزاء الفاشلة الفعلية التي ترغب في رؤيتها من أجل تسجيل وتتبع ومعالجة أخطائك - خاصة ، ولكن ليس فقط في بيئة الإنتاج.

neild شكرا.

لا أريد إخراج هذا الموضوع عن مساره ، لكن ...

ربما يجب أن تقوم وظيفة VolumeCreate حقًا بتزيين أخطائها.
في هذه الحالة ، ومع ذلك ، ليس من الواضح بالنسبة لي أن ملف
وظيفة CreateDockerVolume
يجب إضافة زخرفة إضافية ،

المشكلة هي أن مؤلف الوظيفة CreateDockerVolume قد لا أفعل
معرفة ما إذا كان مؤلف VolumeCreate قد قام بتزيين أخطائهم أم لا
لا تحتاج لتزيين لي.
وحتى لو علمت أن لديهم ، يمكنهم أن يقرروا إلغاء تزيين ملفاتهم
تعمل في إصدار لاحق. وبما أن هذا التغيير لا يغيرها api
سيصدره كإصدار تصحيح / ثانوي والآن وظيفتي التي كانت
اعتمادًا على وظيفتها التي تحتوي على أخطاء مزخرفة لا تحتوي على كل
المعلومات التي احتاجها.
لذلك بشكل عام أجد نفسي أقوم بالتزيين / التغليف حتى لو كنت أنا في المكتبة
الدعوة قد التفاف بالفعل.

راودتني فكرة أثناء الحديث عن try مع زميل في العمل. ربما يجب تمكين try للمكتبة القياسية فقط في 1.14. قام كل من crawshaw و jimmyfrasche بجولة سريعة في بعض الحالات وقدموا بعض المنظور ، ولكن في الواقع إعادة كتابة كود المكتبة القياسي باستخدام try قدر الإمكان سيكون ذا قيمة.

يمنح ذلك فريق Go وقتًا لإعادة كتابة مشروع غير تافه باستخدامه ، ويمكن للمجتمع الحصول على تقرير تجربة حول كيفية عمله. كنا نعرف عدد مرات استخدامه ، وكم مرة يجب إقرانه بـ defer ، إذا كان يغير قابلية قراءة الكود ، ومدى فائدة tryhard ، وما إلى ذلك.

إنها تتعارض قليلاً مع روح المكتبة القياسية ، مما يسمح لها باستخدام شيء لا يستطيع رمز Go العادي استخدامه ، لكنه يمنحنا ملعبًا لنرى كيف يؤثر try على قاعدة بيانات موجودة.

نعتذر إذا فكر شخص آخر في هذا بالفعل ؛ لقد خضت العديد من المناقشات ولم أر اقتراحًا مشابهًا.

يمنحك jonbodner https://go-review.googlesource.com/c/go/+/182717 فكرة جيدة عما قد يبدو عليه الأمر.

ونسيت أن أقول: لقد شاركت في الاستطلاع الخاص بك وصوتت لتحسين معالجة الأخطاء ، وليس هذا.

قصدت أنني أرغب في رؤية المستحيل أكثر صرامة لنسيان معالجة الأخطاء.

يمنحك jonbodner https://go-review.googlesource.com/c/go/+/182717 فكرة جيدة عما قد يبدو عليه الأمر.

لنلخص:

  1. يحل السطر الأول محل 4 سطور (سطرين لمن يستخدمون if ... { return err } )
  2. يمكن تحسين تقييم النتيجة (النتائج) التي تم إرجاعها - فقط على مسار الفشل ، على الرغم من ذلك.

حوالي 6000 استبدال في المجموع لما يبدو أنه مجرد تغيير تجميلي: لن يكشف الأخطاء الموجودة ، وربما لن يقدم أخطاء جديدة (صححني إذا كنت مخطئًا بشأن أي منهما).

هل يمكنني ، بصفتي مشرفًا ، أن أفعل شيئًا كهذا باستخدام الكود الخاص بي؟ ما لم أكتب أداة الاستبدال بنفسي. مما يجعل كل شيء مناسبًا لمستودع golang/go .

ملاحظة: إخلاء مسؤولية مثير للاهتمام في CL:

... Some transformations may be incorrect due to the limitations of the tool (see https://github.com/griesemer/tryhard)...

مثل xerrors ، ماذا عن اتخاذ الخطوة الأولى لاستخدامه كحزمة طرف ثالث؟

على سبيل المثال ، حاول استخدام الحزمة أدناه.

https://github.com/junpayment/gotry

  • قد يكون قصيرًا بالنسبة لحالة الاستخدام الخاصة بك لأنني صنعتها.

ومع ذلك ، أعتقد أن تجربة نفسها فكرة رائعة ، لذلك أعتقد أن هناك أيضًا نهجًا يستخدمها بالفعل بتأثير أقل.

===

جانبا ، هناك شيئان أشعر بالقلق إزاء المحاولة.

1- هناك رأي مفاده أنه يمكن حذف السطر ، ولكن يبدو أنه لا يوجد اعتبار لشرط التأجيل (أو المعالج).

على سبيل المثال ، عند معالجة الأخطاء بالتفصيل.

foo, err: = Foo ()
if err! = nil {
  if err.Error () = "AAA" {
    some action for AAA
  } else if err.Error () = "BBB" {
    some action for BBB
  } else if err.Error () = "CCC" {
    some action for CCC
  } else {
    return err
  }
}

إذا قمت ببساطة باستبدال هذا بالمحاولة ، فسيكون على النحو التالي.

handler: = func (err error) {
  if err.Error () = "AAA" {
    some action for AAA
  } else if err.Error () = "BBB" {
    some action for BBB
  } else if err.Error () = "CCC" {
    some action for CCC
  } else {
    return err
  }
}
foo: = try (Foo (), handler)

2- قد تكون هناك حزم تالفة أخرى قامت بطريق الخطأ بتنفيذ واجهة الخطأ.

type Bad struct {}
func (bad * Bad) Error () {
  return "i really do not intend to be an error"
}

junpayment شكرًا لحزمتك gotry - أعتقد أن هذه طريقة واحدة للحصول على القليل من الإحساس مقابل try ولكن سيكون من المزعج بعض الشيء أن تضطر إلى كتابة تأكيد كل Try نتائج من أصل interface{} في الاستخدام الفعلي.

بخصوص سؤالك:
1) لست متأكدًا إلى أين أنت ذاهب مع هذا. هل تقترح أن يقبل try معالجًا كما في المثال الخاص بك؟ (وكما فعلنا في إصدار داخلي سابق try ؟)
2) لست قلقًا جدًا بشأن الوظائف التي تنفذ واجهة الخطأ عن طريق الخطأ. هذه المشكلة ليست جديدة ولا يبدو أنها تسببت في مشاكل خطيرة على حد علمنا.

يمنحك jonbodner https://go-review.googlesource.com/c/go/+/182717 فكرة جيدة عما قد يبدو عليه الأمر.

شكرا للقيام بهذا التمرين. ومع ذلك ، هذا يؤكد لي ما كنت أظنه ، فإن شفرة المصدر go نفسها بها الكثير من الأماكن حيث يكون try() مفيدًا لأن الخطأ قد تم تمريره للتو. ومع ذلك ، كما يمكنني أن أرى من التجارب مع tryhard التي قدمتها أنا والآخرون أعلاه ، بالنسبة للعديد من قواعد الرموز الأخرى ، لن يكون try() مفيدًا جدًا لأنه في التطبيق يتم التعامل مع أخطاء التعليمات البرمجية فعليًا ، وليس مرت للتو.

أعتقد أن هذا شيء يجب على مصممي Go أن يضعوه في اعتبارهم ، أن برنامج التحويل البرمجي go ووقت التشغيل هما رمز Go "فريد" إلى حد ما ، يختلف عن كود تطبيق Go. لذلك ، أعتقد أنه يجب تحسين try() ليكون مفيدًا أيضًا في حالات أخرى حيث يجب معالجة الخطأ بالفعل ، وحيث لا يكون التعامل مع الخطأ مع بيان التأجيل أمرًا غير مرغوب فيه حقًا.

تضمين التغريدة

سيكون أمرًا مزعجًا بعض الشيء أن تضطر إلى تأكيد كتابة جميع نتائج التجربة من واجهة {} في الاستخدام الفعلي.

أنت على حق. تتطلب هذه الطريقة من المتصل إرسال النوع.

لست متأكدًا إلى أين أنت ذاهب مع هذا. هل تقترح محاولة قبول معالج كما في مثالك؟ (وكما فعلنا في إصدار داخلي سابق من try؟)

لقد ارتكبت خطأ. كان يجب شرحه باستخدام المؤجل بدلاً من المعالج. أنا اسف.

ما أردت أن أقوله هو أن هناك حالة لا تساهم فيها في مقدار الكود نتيجة لعملية معالجة الخطأ التي تم حذفها تحتاج إلى وصفها في المؤجل على أي حال.

من المتوقع أن يكون التأثير أكثر وضوحًا عندما تريد معالجة الأخطاء بالتفصيل.

لذلك ، بدلاً من تقليل عدد سطور التعليمات البرمجية ، يمكننا فهم الاقتراح الذي ينظم مواقع معالجة الأخطاء.

لست قلقًا جدًا بشأن الوظائف التي تنفذ واجهة الخطأ عن طريق الخطأ. هذه المشكلة ليست جديدة ولا يبدو أنها تسببت في مشاكل خطيرة على حد علمنا.

بالضبط هو حالة نادرة.

beoran لقد أجريت بعض التحليلات الأولية لـ Go Corpus (https://github.com/rsc/corpus). أعتقد أن tryhard في حالته الحالية يمكن أن يلغي 41.7٪ من جميع الشيكات err != nil في المجموعة. إذا استبعدت النمط "_test.go" ، يرتفع هذا الرقم إلى 51.1٪ (يعمل tryhard فقط على الدوال التي ترجع أخطاء ، ولا تميل إلى العثور على العديد من تلك الموجودة في الاختبارات). تحذير ، خذ هذه الأرقام بحذر ، لقد حصلت على المقام (على سبيل المثال ، عدد الأماكن في الكود الذي نقوم بإجراء الشيكات عليه err != nil ) باستخدام نسخة مخترقة tryhard ، وبشكل مثالي كنا ننتظر حتى tryhard هذه الإحصائيات نفسها.

أيضًا ، إذا أصبح tryhard مدركًا للنوع ، فيمكنه نظريًا إجراء تحويلات مثل هذا:

// Before.
a, err := foo()
if err != nil {
  return 0, nil, errors.Wrapf(err, "some message %v", b)
}

// After.
a, err := foo()
try(errors.Wrapf(err, "some message %v", b))

هذا يستفيد من الأخطاء. سلوك التفاف إرجاع nil عندما تم تمرير وسيطة الخطأ nil . (github.com/pkg/errors ليس فريدًا أيضًا في هذا الصدد ، فالمكتبة الداخلية التي أستخدمها للقيام بتغليف الأخطاء تحافظ أيضًا على أخطاء nil ، وستعمل أيضًا مع هذا النمط ، كما تفعل معظم مكتبات معالجة الأخطاء post- try ، أتخيل). من المحتمل أيضًا أن يقوم الجيل الجديد من مكتبات الدعم بتسمية مساعدي الانتشار هؤلاء بشكل مختلف قليلاً.

بالنظر إلى أن هذا قد ينطبق على 50٪ من غير الاختبار err != nil الشيكات خارج الصندوق ، قبل أي تطور مكتبة لدعم النمط ، لا يبدو أن مترجم Go ووقت التشغيل فريدان ، كما تقترح .

حول المثال مع CreateDockerVolume https://github.com/golang/go/issues/32437#issuecomment -508199875
لقد وجدت بالضبط نفس النوع من الاستخدام. في lib i wrap الخطأ مع السياق عند كل خطأ ، عند استخدام lib ، أود استخدام try وإضافة سياق في defer للدالة بأكملها.

حاولت محاكاة هذا عن طريق إضافة وظيفة معالج الأخطاء في البداية ، إنها تعمل بشكل جيد:

func MyLib() error {
    return errors.New("Error from my lib")
}
func MyOtherLib() error {
    return errors.New("Error from my otherLib")
}

func Caller(a, b int) error {
    eh := func(err error) error {
        return fmt.Errorf("From Caller with %d and %d i found this error: %v", a, b, err)
    }

    err := MyLib()
    if err != nil {
        return eh(err)
    }

    err = MyOtherLib()
    if err != nil {
        return eh(err)
    }

    return nil
}

سيبدو ذلك جيدًا ومصطلحًا مع try+defer

func Caller(a, b int) (err error) {
    defer fmt.Errorf("From Caller with %d and %d i found this error: %v", a, b, &err)

    try(MyLib())
    try(MyOtherLib())

    return nil
}

تضمين التغريدة

يحتوي مستند التصميم حاليًا على العبارات التالية:

إذا كانت وظيفة التضمين تعلن عن معلمات نتائج مسماة أخرى ، فإن معلمات النتيجة هذه تحتفظ بأي قيمة لديها. إذا كانت الوظيفة تعلن عن معلمات نتائج أخرى غير مسماة ، فإنها تفترض قيم الصفر المقابلة لها (وهو نفس الاحتفاظ بالقيمة التي لديها بالفعل).

هذا يعني أن هذا البرنامج سيطبع 1 ، بدلاً من 0: https://play.golang.org/p/KenN56iNVg7.

كما أشرت لي على Twitter ، فإن هذا يجعل try يتصرف كعودة عارية ، حيث القيم التي يتم إرجاعها ضمنية ؛ لمعرفة القيم الفعلية التي يتم إرجاعها ، قد يحتاج المرء إلى إلقاء نظرة على الكود على مسافة كبيرة من الاستدعاء إلى try نفسه.

نظرًا لأن خاصية المرتجعات المجردة (غير المحلية) غير مرغوبة بشكل عام ، فما هي أفكارك حول جعل try يعرض دائمًا القيم الصفرية للوسيطات الخالية من الأخطاء (إذا تم إرجاعها على الإطلاق)؟

بعض الاعتبارات:

قد يجعل هذا بعض الأنماط التي تتضمن استخدام قيم الإرجاع المسماة غير قادرة على استخدام try . على سبيل المثال ، لتطبيقات io.Writer ، والتي تحتاج إلى إرجاع عدد البايتات المكتوبة ، حتى في حالة الكتابة الجزئية. ومع ذلك ، يبدو أن try عرضة للخطأ في هذه الحالة على أي حال (على سبيل المثال ، n += try(wrappedWriter.Write(...)) لا يقوم بتعيين n على الرقم الصحيح في حالة حدوث خطأ في الإرجاع). يبدو لي أنه من الجيد أن try سيصبح غير قابل للاستخدام لهذه الأنواع من حالات الاستخدام ، حيث إن السيناريوهات التي نحتاج فيها إلى كل من القيم والخطأ نادرة إلى حد ما ، في تجربتي.

إذا كانت هناك دالة ذات استخدامات عديدة try ، فقد يؤدي ذلك إلى تضخم الكود ، حيث توجد العديد من الأماكن في الدالة التي تحتاج إلى التخلص من متغيرات الإخراج. أولاً ، المترجم جيد جدًا في تحسين الكتابة غير الضرورية هذه الأيام. وثانيًا ، إذا ثبت أنه ضروري ، يبدو وكأنه تحسين مباشر للحصول على كل الكتل $ try التي تم إنشاؤها goto إلى تسمية مشتركة مشتركة على مستوى الوظيفة ، والتي تحدد قيم الإخراج غير الخطأ.

أيضًا ، كما تعلمون ، تم تنفيذ tryhard بالفعل بهذه الطريقة ، لذا كميزة جانبية ستجعل هذا بأثر رجعي tryhard أكثر صحة.

يمنحك jonbodner https://go-review.googlesource.com/c/go/+/182717 فكرة جيدة عما قد يبدو عليه الأمر.

شكرا للقيام بهذا التمرين. ومع ذلك ، هذا يؤكد لي ما كنت أظنه ، فإن شفرة المصدر go نفسها بها الكثير من الأماكن حيث يكون try() مفيدًا لأن الخطأ قد تم تمريره للتو. ومع ذلك ، كما يمكنني أن أرى من التجارب مع tryhard التي قدمتها أنا والآخرون أعلاه ، بالنسبة للعديد من قواعد الرموز الأخرى ، لن يكون try() مفيدًا جدًا لأنه في التطبيق يتم التعامل مع أخطاء التعليمات البرمجية فعليًا ، وليس مرت للتو.

سأفسر هذا بشكل مختلف.

لم يكن لدينا أدوية عامة ، لذلك سيكون من الصعب العثور على رمز في البرية يمكن أن يستفيد بشكل مباشر من الأدوية الجنيسة بناءً على التعليمات البرمجية المكتوبة. هذا لا يعني أن الأدوية الجنيسة لن تكون مفيدة.

بالنسبة لي ، هناك نمطان استخدمتهما في التعليمات البرمجية لمعالجة الأخطاء

  1. استخدام الذعر داخل الحزمة ، واستعادة حالة الذعر وإرجاع الخطأ الناتج في عدد قليل من الطرق المصدرة
  2. استخدم بشكل انتقائي معالج مؤجل في بعض الطرق حتى أتمكن من تزيين الأخطاء بملف مكدس غني / معلومات رقم سطر الكمبيوتر والمزيد من السياق

هذه الأنماط ليست منتشرة لكنها تعمل. 1) تُستخدم في المكتبة القياسية في وظائفها غير المُصدرة و 2) تُستخدم على نطاق واسع في قاعدة الشفرة الخاصة بي خلال السنوات القليلة الماضية لأنني اعتقدت أنها طريقة لطيفة لاستخدام الميزات المتعامدة للقيام بزخرفة خطأ مبسطة ، ويوصي الاقتراح و لقد بارك هذا النهج. حقيقة أنها ليست منتشرة لا تعني أنها ليست جيدة. ولكن كما هو الحال مع كل شيء ، فإن الإرشادات الصادرة عن فريق Go والتي توصي بها ستؤدي إلى استخدامها بشكل أكبر في الممارسة ، في المستقبل .

نقطة أخيرة من الملاحظة هي أن تزيين الأخطاء في كل سطر من التعليمات البرمجية الخاصة بك يمكن أن يكون أكثر من اللازم. ستكون هناك بعض الأماكن التي يكون من المنطقي فيها تزيين الأخطاء ، وبعض الأماكن التي لا يكون فيها ذلك منطقيًا. نظرًا لأنه لم يكن لدينا إرشادات رائعة من قبل ، فقد قرر الناس أنه من المنطقي دائمًا تزيين الأخطاء. ولكن قد لا تضيف قيمة كبيرة لتزيينها دائمًا في كل مرة لا يتم فيها فتح ملف ، حيث قد يكون من الكافي داخل الحزمة وجود خطأ فقط مثل "غير قادر على فتح الملف: conf.json" ، على عكس: "غير قادر على للحصول على اسم المستخدم: غير قادر على الحصول على اتصال db: غير قادر على تحميل ملف النظام: غير قادر على فتح الملف: conf.json ".

من خلال الجمع بين قيم الخطأ ومعالجة الأخطاء الموجزة ، نحصل الآن على إرشادات أفضل حول كيفية معالجة الأخطاء. يبدو أن التفضيل هو:

  • سيكون الخطأ بسيطًا ، على سبيل المثال "غير قادر على فتح الملف: conf.json"
  • يمكن إرفاق إطار خطأ يتضمن السياق: GetUserName -> GetConnection -> LoadSystemFile.
  • إذا كان يضيف إلى السياق ، فيمكنك التفاف هذا الخطأ إلى حد ما ، على سبيل المثال MyAppError {خطأ}

أميل إلى الشعور وكأننا نتجاهل أهداف اقتراح المحاولة والأشياء عالية المستوى التي يحاول حلها:

  1. تقليل النمطي المعياري إذا كان يخطئ! = لا شيء {إرجاع الخطأ} للأماكن التي يكون من المنطقي فيها نشر الخطأ حتى يتم التعامل معه في أعلى المكدس
  2. السماح بالاستخدام المبسط لقيم الإرجاع حيث يخطئ == لا شيء
  3. السماح بتمديد الحل لاحقًا للسماح ، على سبيل المثال ، بمزيد من زخرفة الأخطاء في الموقع ، والانتقال إلى معالج الأخطاء ، واستخدام goto بدلاً من دلالات الإرجاع وما إلى ذلك.
  4. اسمح بمعالجة الأخطاء لعدم تشويش منطق قاعدة الشفرة ، أي ضعه جانبًا إلى حد ما باستخدام معالج أخطاء من نوع ما.

كثير من الناس لا يزال لديهم 1). لقد عمل العديد من الأشخاص حول 1) لأنه لم تكن هناك إرشادات أفضل من قبل. لكن هذا لا يعني أنه بعد أن بدأوا في استخدامه ، لن يتغير رد فعلهم السلبي ليصبح أكثر إيجابية.

يمكن استخدام العديد من الناس 2). قد يكون هناك خلاف حول المقدار ، لكنني أعطيت مثالاً حيث يجعل الكود الخاص بي أسهل بكثير.

var u user = try(db.LoadUser(try(strconv.ParseInt(stringId)))

في جافا حيث الاستثناءات هي القاعدة ، سيكون لدينا:

User u = db.LoadUser(Integer.parseInt(stringId)))

لن ينظر أحد إلى هذا الرمز ويقول إنه يتعين علينا القيام بذلك في سطرين ، أي.

int id = Integer.parseInt(stringId)
User u = db.LoadUser(id))

لا ينبغي علينا القيام بذلك هنا ، بموجب المبدأ التوجيهي الذي يجب ألا يسمى المحاولة مضمنة ويجب أن تكون دائمًا في صفها الخاص .

علاوة على ذلك ، اليوم ، ستقوم معظم التعليمات البرمجية بأشياء مثل:

var u user
var err error
var id int
id, err = strconv.ParseInt(stringId)
if err != nil {
  return u, errors.Wrap("cannot load userid from string: %s: %v", stringId, err)
}
u, err = db.LoadUser(id)
if err != nil {
  return u, errors.Wrap("cannot load user given user id: %d: %v", id, err)
}
// now work with u

الآن ، يجب على أي شخص يقرأ هذا أن يحلل هذه الأسطر العشرة ، والتي كانت في جافا ستكون سطرًا واحدًا ، والتي يمكن أن تكون سطرًا واحدًا مع الاقتراح هنا. بصريًا ، يجب أن أحاول عقليًا أن أرى ما هي الأسطر الموجودة هنا ذات الصلة حقًا عندما أقرأ هذا الرمز. اللوح المعياري يجعل قراءة هذا الرمز أكثر صعوبة.

أتذكر في حياتي السابقة العمل على / مع البرمجة الموجهة للجانب في جافا. هناك ، كان الهدف

يتيح ذلك إضافة السلوكيات غير المركزية لمنطق الأعمال (مثل التسجيل) إلى برنامج دون تشويش الكود ، وهو جوهر الوظيفة. (نقلاً عن ويكيبيديا https://en.wikipedia.org/wiki/Aspect-oriented_programming).
معالجة الأخطاء ليست مركزية لمنطق العمل ، ولكنها مركزية للصحة. الفكرة هي نفسها - لا ينبغي أن نشوش كودنا بأشياء ليست مركزية في منطق الأعمال لأن " التعامل مع الأخطاء مهم للغاية ". نعم هي كذلك ، ونعم يمكننا وضعها جانبًا.

فيما يتعلق 4) ، اقترحت العديد من المقترحات معالجات الأخطاء ، وهي رمز إلى الجانب الذي يعالج الأخطاء ولكنه لا يفسد منطق الأعمال. يحتوي الاقتراح الأولي على الكلمة الأساسية للمقبض له ، وقد اقترح الأشخاص أشياء أخرى. يقول هذا الاقتراح أنه يمكننا الاستفادة من آلية التأجيل الخاصة به ، وجعل ذلك أسرع الذي كان كعب أخيل من قبل. أعلم - لقد أحدثت ضوضاء حول أداء آلية الإرجاء عدة مرات لفريق go go.

لاحظ أن tryhard لن يضع علامة على هذا الرمز كشيء يمكن تبسيطه. ولكن باستخدام try والإرشادات الجديدة ، قد يرغب الأشخاص في تبسيط هذا الرمز إلى سطر واحد والسماح لإطار الخطأ بالتقاط السياق المطلوب.

السياق ، الذي تم استخدامه جيدًا في اللغات المستندة إلى الاستثناءات ، سيلتقط أن أحدهم حاول حدوث خطأ أثناء تحميل مستخدم بسبب عدم وجود معرف المستخدم ، أو لأن stringId لم يكن بتنسيق يمكن أن يكون رقمًا صحيحًا حللت منه.

قم بدمج ذلك مع Error Formatter ، ويمكننا الآن فحص إطار الخطأ والخطأ نفسه بشكل غني وتنسيق الرسالة بشكل جيد للمستخدمين ، دون صعوبة قراءة نمط a: b: c: d: e: underlying error الذي فعله العديد من الأشخاص والذي لم نقم به إرشادات رائعة لـ.

تذكر أن كل هذه المقترحات معًا توفر لنا الحل الذي نريده: معالجة موجزة للأخطاء بدون نموذج معياري غير ضروري ، مع توفير تشخيصات أفضل وتنسيق أخطاء أفضل للمستخدمين. هذه مفاهيم متعامدة ولكنها معًا تصبح قوية للغاية.

أخيرًا ، بالنظر إلى 3) أعلاه ، من الصعب استخدام كلمة رئيسية لحل هذه المشكلة. بحكم التعريف ، لا تسمح الكلمة الأساسية بالامتداد في المستقبل لتمرير معالج بالاسم ، أو السماح بتزيين الخطأ الفوري ، أو دعم دلالات الانتقال (بدلاً من إرجاع دلالات). باستخدام كلمة رئيسية ، يجب أن نضع الحل الكامل في الاعتبار أولاً. والكلمة الرئيسية ليست متوافقة مع الإصدارات السابقة. ذكر فريق go أنه عندما بدأ Go 2 ، أرادوا محاولة الحفاظ على التوافق مع الإصدارات السابقة قدر الإمكان. تحافظ الدالة try على ذلك ، وإذا رأينا لاحقًا أنه لا يوجد ملحق ضروري ، يمكن بسهولة تعديل gofix البسيط للكود لتغيير وظيفة try إلى كلمة رئيسية.

2 سنتي مرة أخرى!

في 7/4/19 ، كتب Sanjay Menakuru [email protected] :

تضمين التغريدة

[...]
كما أشرت لي على Twitter ، فإن هذا يجعل try يتصرف مثل العار
العودة ، حيث تكون القيم التي يتم إرجاعها ضمنية ؛ لمعرفة ماذا
يتم إرجاع القيم الفعلية ، قد يحتاج المرء إلى إلقاء نظرة على الكود في ملف
مسافة كبيرة من المكالمة إلى try نفسها.

بالنظر إلى أن هذه الخاصية من العائدات العارية (غير المحلية) بشكل عام
لم تعجبك ، ما هي أفكارك حول جعل try دائمًا يعيد الصفر
قيم الوسائط غير الخطأ (إذا تم إرجاعها على الإطلاق)؟

لا يُسمح بالعودة المجردة إلا عند تسمية وسيطات الإرجاع. هو - هي
يبدو أن المحاولة تتبع قاعدة مختلفة؟

تعجبني الفكرة العامة لإعادة استخدام defer لمعالجة المشكلة. ومع ذلك ، أتساءل ما إذا كانت الكلمة الرئيسية try هي الطريقة الصحيحة للقيام بذلك. ماذا لو تمكنا من إعادة استخدام النمط الموجود بالفعل. شيء يعرفه الجميع بالفعل من الواردات:

التعامل الصريح

res, err := doSomething()
if err != nil {
    return err
}

التجاهل الصريح

res, _ := doSomething()

المناولة المؤجلة

سلوك مشابه لما سيفعله try .

res, . := doSomething()

تضمين التغريدة
قد يكون هذا بناء جملة أجمل بالنسبة له ولكني لا أعرف مدى سهولة التكيف مع Go لجعل هذا قانونيًا ، سواء في Go أو في أدوات تمييز بناء الجملة.

balasanjay (و @ lootch): حسب تعليقك هنا ، نعم ، سيقوم البرنامج https://play.golang.org/p/KenN56iNVg7 بطباعة 1.

نظرًا لأن try يتعلق فقط بنتيجة الخطأ ، فإنه يترك كل شيء بمفرده. يمكن أن تحدد قيم الإرجاع الأخرى لقيمها الصفرية ، ولكن ليس من الواضح لماذا سيكون ذلك أفضل. فمن الممكن أن يتسبب ذلك في مزيد من العمل عند تسمية قيم النتائج لأنه قد يتعين تعيينها على الصفر ؛ ومع ذلك (من المحتمل) أن المتصل سوف يتجاهلهم إذا كان هناك خطأ. لكن هذا قرار تصميم يمكن تغييره إذا كانت هناك أسباب وجيهة لذلك.

[تحرير: لاحظ أن هذا السؤال (حول ما إذا كان سيتم مسح النتائج غير المتعلقة بالخطأ عند مواجهة خطأ) ليس خاصًا بالاقتراح try . أي من البدائل المقترحة التي لا تتطلب صراحة return يجب أن يجيب على نفس السؤال.]

فيما يتعلق بمثال الكاتب n += try(wrappedWriter.Write(...)) : نعم ، في حالة تحتاج فيها إلى زيادة n حتى في حالة حدوث خطأ ، لا يمكن استخدام try - حتى لو try لا يُصفر قيم النتائج غير الخاطئة. هذا لأن try لا يعرض أي شيء إلا إذا لم يكن هناك خطأ: try يتصرف بشكل واضح مثل وظيفة (لكن وظيفة قد لا تعود إلى المتصل ، ولكن إلى متصل المتصل). راجع استخدام الموقتات في تنفيذ try .

ولكن في حالات مثل مثالك ، يتعين على المرء أيضًا توخي الحذر عند استخدام بيان if والتأكد من دمج عدد البايت المرتجع في n .

لكن ربما أكون قد أسأت فهم قلقك.

griesemer : أقترح أنه من الأفضل تعيين قيم الإرجاع الأخرى على قيمها الصفرية ، لأنه من الواضح بعد ذلك ما سيفعله try بمجرد فحص موقع الاتصال. سيكون إما أ) لا يفعل شيئًا ، أو ب) سيعود من الدالة بقيم صفرية والوسيطة التي يجب تجربتها.

كما هو محدد ، سيحتفظ try بقيم قيم الإرجاع المسماة بدون أخطاء ، وبالتالي سيحتاج المرء إلى فحص الوظيفة بأكملها لتوضيح القيم التي تعود try .

هذه هي نفس المشكلة مع الإرجاع المجرد (الاضطرار إلى فحص الوظيفة بالكامل لمعرفة القيمة التي يتم إرجاعها) ، وكان من المفترض أن يكون سبب إيداع https://github.com/golang/go/issues/21291. هذا ، بالنسبة لي ، يعني أن try في وظيفة كبيرة ذات قيم إرجاع مسماة ، يجب أن يتم تثبيطها على نفس الأساس مثل المرتجعات العارية (https://github.com/golang/go/wiki/CodeReviewComments # معلمات النتائج المسماة). بدلاً من ذلك ، أقترح تحديد try لإرجاع القيم الصفرية دائمًا للوسيطة غير الخاطئة.

بالحيرة والشعور بالسوء تجاه فريق Go مؤخرًا. try هو حل نظيف ومفهوم للمشكلة المحددة التي يحاول حلها: الإسهاب في معالجة الأخطاء.

يقرأ الاقتراح: بعد مناقشة استمرت لمدة عام ، نضيف هذا المضمّن. استخدمه إذا كنت تريد رمزًا أقل إسهابًا ، وإلا فاستمر في فعل ما تفعله. رد الفعل هو بعض المقاومة غير المبررة تمامًا لميزة الاشتراك التي أظهر أعضاء الفريق مزايا واضحة لها!

أود أيضًا أن أشجع فريق go على جعل try متغيرًا مدمجًا إذا كان من السهل القيام بذلك

try(outf.Seek(linkstart, 0))
try(io.Copy(outf, exef))

يصبح

try(outf.Seek(linkstart, 0)), io.Copy(outf, exef)))

قد يكون الشيء المطول التالي هو تلك المكالمات المتتالية إلى try .

أتفق مع nvictor في الغالب ، باستثناء المعلمات المتغيرة لـ try . ما زلت أعتقد أنه يجب أن يكون لها مكان للمعالج ويمكن أن يدفع الاقتراح المتنوع حد القراءة بنفسي.

nvictor Go هي لغة لا تحب الميزات غير المتعامدة. هذا يعني أننا إذا توصلنا في المستقبل إلى حل أفضل لمعالجة الأخطاء ليس try ، فسيكون التبديل أكثر تعقيدًا (إذا لم يتم رفضه بشكل قاطع لأننا الحاليين الحل "جيد بما فيه الكفاية").

أعتقد أن هناك حلاً أفضل من try ، وأنا أفضل أن أكون بطيئًا وأجد هذا الحل بدلاً من الاستقرار على هذا الحل.

ومع ذلك ، لن أغضب إذا تمت إضافة هذا. إنه ليس حلاً سيئًا ، أعتقد أننا قد نكون قادرين على اكتشاف حل أفضل.

في رأيي ، أريد تجربة رمز الحظر ، الآن try مثل مقبض الخطأ func

عند قراءة هذه المناقشة (والمناقشات على Reddit) ، لم أشعر دائمًا أن الجميع على نفس الصفحة.

وهكذا ، كتبت منشور مدونة صغير يوضح كيف يمكن استخدام try : https://faiface.github.io/post/how-to-use-try/.

حاولت إظهار جوانب متعددة لهذا الاقتراح حتى يتمكن الجميع من رؤية ما يمكنه فعله وتشكيل رأي أكثر استنارة (حتى لو كان سلبياً).

إذا فاتني شيء مهم ، فيرجى إبلاغي بذلك!

faiface أنا متأكد من أنه يمكنك استبداله

if err != nil {
    return resps, err
}

بـ try(err) .

بخلاف ذلك - مقال عظيم!

تضمين التغريدة لكني أعتقد أنني سأحتفظ بها كما هي ، بحيث يكون هناك مثال واحد على الأقل من if err != nil الكلاسيكي ، وإن لم يكن جيدًا جدًا.

لدي شاغلان:

  • لقد كانت عمليات الإرجاع المسماة مربكة للغاية ، وهذا يشجعهم على استخدام حالة استخدام جديدة ومهمة
  • سيؤدي هذا إلى تثبيط إضافة سياق إلى الأخطاء

من واقع خبرتي ، تعد إضافة السياق إلى الأخطاء فورًا بعد كل موقع اتصال أمرًا بالغ الأهمية للحصول على رمز يمكن تصحيحه بسهولة. وقد تسببت عمليات العودة المسماة في حدوث ارتباك لكل مطور Go أعرفه تقريبًا في مرحلة ما.

مصدر القلق الأسلوبي البسيط هو أنه من المؤسف عدد سطور التعليمات البرمجية التي سيتم تغليفها الآن بـ try(actualThing()) . يمكنني تخيل رؤية معظم السطور في قاعدة بيانات ملفوفة بـ try() . هذا شعور مؤسف.

أعتقد أنه سيتم التعامل مع هذه المخاوف من خلال تعديل:

a, b, err := myFunc()
check(err, "calling myFunc on %v and %v", a, b)

سيتصرف check() مثل try() ، لكنه سيتخلى عن سلوك تمرير قيم إرجاع الدالة بشكل عام ، وبدلاً من ذلك سيوفر القدرة على إضافة سياق. لا يزال من شأنه أن يؤدي إلى العودة.

سيحتفظ هذا بالعديد من مزايا try() :

  • إنه مدمج
  • إنه يتبع WRT لتدفق التحكم الحالي للتأجيل
  • يتماشى مع الممارسة الحالية لإضافة سياق إلى الأخطاء بشكل جيد
  • يتماشى مع العروض والمكتبات الحالية لتغليف الأخطاء ، مثل errors.Wrap(err, "context message")
  • ينتج عنه موقع اتصال نظيف: لا توجد لغة معيارية في السطر a, b, err := myFunc()
  • لا يزال وصف الأخطاء باستخدام defer fmt.HandleError(&err, "msg") ممكنًا ، ولكن لا داعي للتشجيع.
  • التوقيع check أبسط قليلاً ، لأنه لا يحتاج إلى إرجاع عدد عشوائي من الوسيطات من الوظيفة التي يتم تغليفها.

هذا جيد ، أعتقد أن فريق Go يجب أن يأخذ هذا. هذا أفضل من المحاولة بشكل أوضح !!!

buchanae سأكون مهتمًا برأيك في منشور المدونة الخاص بي لأنك جادلت بأن try سيثبط إضافة سياق إلى الأخطاء ، بينما أجادل أنه على الأقل في مقالتي أسهل من المعتاد.

سأقوم برمي هذا هناك في المرحلة الحالية. سأفكر في الأمر أكثر ، لكنني اعتقدت أنني أنشر هنا لأرى ما هو رأيك. ربما يجب أن أفتح قضية جديدة لهذا؟ لقد نشرت هذا أيضًا على # 32811

إذن ، ماذا عن القيام بنوع من الأشياء الكلية C العامة بدلاً من الانفتاح لمزيد من المرونة؟

مثله:

define returnIf(err error, desc string, args ...interface{}) {
    if (err != nil) {
        return fmt.Errorf("%s: %s: %+v", desc, err, args)
    }
}

func CopyFile(src, dst string) error {
    r, err := os.Open(src)
    :returnIf(err, "Error opening src", src)
    defer r.Close()

    w, err := os.Create(dst)
    :returnIf(err, "Error Creating dst", dst)
    defer w.Close()

    ...
}

في الأساس ، سيتم استبدال / تسطير ذلك الذي تم تحديده أعلاه. المرونة هي أن الأمر متروك لك فيما تفعله. قد يكون تصحيح هذا أمرًا غريبًا ، إلا إذا استبدله المحرر في المحرر بطريقة لطيفة. هذا أيضًا يجعله أقل سحرية ، حيث يمكنك قراءة التعريف بوضوح. وأيضًا ، يمكّنك هذا من الحصول على سطر واحد يمكن أن يعود عند الخطأ. وقادرة على الحصول على رسائل خطأ مختلفة حسب مكان حدوثها (السياق).

تحرير: تمت أيضًا إضافة نقطتين أمام الماكرو للإشارة إلى أنه ربما يمكن القيام بذلك لتوضيح أنه ماكرو وليس استدعاء دالة.

تضمين التغريدة

أود أيضًا أن أشجع فريق go على جعل try متغيرًا مدمجًا

ما هو try(foo(), bar()) الذي سيعرضه إذا لم foo و bar نفس الشيء؟

سأقوم برمي هذا هناك في المرحلة الحالية. سأفكر في الأمر أكثر ، لكنني اعتقدت أنني أنشر هنا لأرى ما هو رأيك. ربما يجب أن أفتح قضية جديدة لهذا؟ لقد نشرت هذا أيضًا على # 32811

إذن ، ماذا عن القيام بنوع من الأشياء الكلية C العامة بدلاً من الانفتاح لمزيد من المرونة؟

Chillance ، IMHO ، أعتقد أن نظام الماكرو الصحي مثل Rust (والعديد من اللغات الأخرى) من شأنه أن يمنح الناس فرصة للعب بأفكار مثل try أو الأدوية الجنيسة ، وبعد اكتساب الخبرة ، يمكن أن تصبح أفضل الأفكار جزء من اللغة والمكتبات. لكنني أعتقد أيضًا أن هناك فرصة ضئيلة جدًا لإضافة مثل هذا الشيء إلى Go.

jonbodner ، يوجد حاليًا اقتراح لإضافة وحدات ماكرو صحية في Go. لا يوجد بناء جملة مقترح أو أي شيء حتى الآن ، ولكن لم يكن هناك الكثير ضد فكرة إضافة وحدات ماكرو صحية. # 32620

Allenyn ، بخصوص اقتراحbuchanae السابق الذي نقلته للتو :

a, b, err := myFunc()
check(err, "calling myFunc on %v and %v", a, b)

من خلال ما رأيته من المناقشة ، أعتقد أنه سيكون نتيجة غير محتملة هنا بالنسبة لدلالات fmt ليتم سحبها إلى وظيفة مضمنة. (انظر على سبيل المثال رد josharian ).

ومع ذلك ، ليست هناك حاجة إليه حقًا ، بما في ذلك لأن السماح بوظيفة المعالج يمكن أن يتجنب سحب الدلالات fmt مباشرة إلى عنصر مدمج. تم اقتراح أحد هذه الأساليب بواسطة eihigh في اليوم الأول أو نحو ذلك من المناقشة هنا ، وهو مشابه لروح اقتراح buchanae ، والذي اقترح تعديل try المبني بدلاً من ذلك للحصول على التوقيع التالي:

func try(error, optional func(error) error)

لأن هذا البديل try لا يعيد أي شيء ، فهذا التوقيع يعني:

  • لا يمكن تداخله داخل استدعاء دالة آخر
  • يجب أن يكون في بداية السطر

لا أرغب في إطلاق اسم bikeshding ، لكن هذا النموذج try قد يقرأ بشكل أفضل مع اسم بديل مثل check . يمكن للمرء أن يتخيل مساعدي المكتبة القياسيين الذين يمكن أن يجعلوا التعليق التوضيحي الاختياري في المكان مناسبًا ، بينما يمكن أن يظل defer خيارًا للتعليق التوضيحي الموحد عند الرغبة.

كانت هناك بعض المقترحات ذات الصلة التي تم إنشاؤها لاحقًا في # 32811 ( catch كمضمن) و # 32611 ( on الكلمة الرئيسية للسماح on err, <statement> ). قد تكون هذه أماكن جيدة لمزيد من المناقشة ، أو لإضافة إبهام لأعلى أو رفض ، أو لاقتراح تعديلات محتملة على تلك المقترحات.

jonbodner ، يوجد حاليًا اقتراح لإضافة وحدات ماكرو صحية في Go. لا يوجد بناء جملة مقترح أو أي شيء حتى الآن ، ولكن لم يكن هناك الكثير ضد فكرة إضافة وحدات ماكرو صحية. # 32620

إنه لأمر رائع أن يكون هناك اقتراح ، لكنني أظن أن فريق Go الأساسي لا ينوي إضافة وحدات ماكرو. ومع ذلك ، يسعدني أن أكون مخطئًا بشأن هذا لأنه سينهي جميع الحجج حول التغييرات التي تتطلب حاليًا تعديلات على جوهر اللغة. لنقتبس دمية شهيرة ، "افعل. أو لا تفعل. لا توجد محاولة".

jonbodner لا أعتقد أن إضافة وحدات ماكرو صحية ستنهي الجدل. العكس تماما. الانتقاد الشائع هو أن try "يخفي" العائد. ستكون وحدات الماكرو أسوأ تمامًا من وجهة النظر هذه ، لأن أي شيء سيكون ممكنًا في الماكرو. وحتى لو سمح Go بوحدات الماكرو الصحية التي يحددها المستخدم ، فلا يزال يتعين علينا مناقشة ما إذا كان try يجب أن يكون ماكروًا مضمّنًا مُعلن مسبقًا في كتلة الكون أم لا. سيكون من المنطقي لمن يعارضون try أن يكونوا أكثر معارضة لوحدات الماكرو الصحية ؛-)

ngrilly هناك عدة طرق للتأكد من أن وحدات الماكرو بارزة ويسهل رؤيتها. الطريقة التي يقوم بها Rust هي أن وحدات الماكرو تتم متابعتها دائمًا بواسطة ! (على سبيل المثال ، try!(...) و println!(...) ).

كنت أزعم أنه إذا تم اعتماد وحدات الماكرو الصحية وكان من السهل رؤيتها ، ولم تبدو مثل مكالمات الوظائف العادية ، فستكون مناسبة بشكل أفضل. يجب أن نختار المزيد من الحلول ذات الأغراض العامة بدلاً من حل المشكلات الفردية.

thepudds أوافق على أن إضافة معلمة اختيارية من النوع func(error) error قد تكون مفيدة (تمت مناقشة هذا الاحتمال في الاقتراح ، مع بعض المشكلات التي قد تحتاج إلى حل) ، لكنني لا أرى الهدف من try لا يُرجع شيئًا. try الذي اقترحه فريق Go هو أداة أكثر عمومية.

deanveloper نعم ، ! في نهاية وحدات الماكرو في Rust ذكي. تذكر بالمعرفات المصدرة التي تبدأ بحرف كبير في Go :-)

أوافق على وجود وحدات ماكرو صحية في Go إذا وفقط إذا استطعنا الحفاظ على سرعة التجميع وحل المشكلات المعقدة المتعلقة بالأدوات (ستحتاج أدوات إعادة البناء إلى توسيع وحدات الماكرو لفهم دلالات الكود ، ولكن يجب إنشاء رمز باستخدام وحدات الماكرو غير الموسعة) . من الصعب. في غضون ذلك ، ربما يمكن إعادة تسمية $ # $ try try! ؟ ؛-)

فكرة خفيفة الوزن: إذا احتوى جسم if / for build على جملة واحدة ، فلا داعي للأقواس شريطة أن تكون هذه العبارة على نفس السطر مثل if أو for . مثال:

fd, err := os.Open("foo")
if err != nil return err

لاحظ أنه في الوقت الحالي ، يعد النوع error مجرد نوع واجهة عادي. المترجم لا يتعامل معها على أنها أي شيء خاص. try يغير ذلك. إذا سُمح للمترجم بالتعامل مع error على أنه خاص ، فأنا أفضل /bin/sh مستوحى من || :

fd, err := os.Open("foo") || return err

سيكون معنى هذا الرمز واضحًا إلى حد ما لمعظم المبرمجين ، ولا يوجد تدفق تحكم خفي ، وبما أن هذا الرمز في الوقت الحالي غير قانوني ، فلا يتضرر أي رمز عمل.

على الرغم من أنني أستطيع أن أتخيل أن بعضكم يرتد في حالة من الرعب.

bakul في if err != nil return err ، كيف تعرف أين ينتهي التعبير err != nil وأين تبدأ العبارة return err ؟ ستكون فكرتك بمثابة تغيير كبير في قواعد اللغة ، أكبر بكثير مما هو مقترح بـ try .

تبدو فكرتك الثانية مثل catch |err| return err في Zig . أنا شخصياً لست "ارتد من الرعب" وأقول لماذا لا؟ ولكن يجب ملاحظة أن Zig لديها أيضًا كلمة رئيسية try ، وهي اختصار لـ catch |err| return err ، وتعادل تقريبًا ما يقترحه فريق Go هنا كوظيفة مضمنة. لذلك ربما يكون try كافيًا ولا نحتاج إلى الكلمة الأساسية catch ؟ ؛-)

ngrilly ، حاليًا <expr> <statement> غير صالح لذا لا أعتقد أن هذا التغيير سيجعل القواعد أكثر غموضًا ولكن قد يكون أكثر هشاشة.

سيؤدي هذا إلى إنشاء نفس الكود تمامًا مثل اقتراح المحاولة ولكن أ) الإرجاع واضح هنا ب) لا يوجد تداخل ممكن كما هو الحال مع try و c) سيكون هذا بناء جملة مألوفًا لمستخدمي shell (الذين يفوق عدد مستخدمي zig بكثير). لا يوجد catch هنا.

لقد طرحت هذا كبديل ولكن لأكون صريحًا ، فأنا على ما يرام تمامًا مع كل ما يقرره مصممو اللغة الأساسية.

لقد قمت بتحميل إصدار محسن قليلاً من tryhard . يقدم الآن معلومات أكثر تفصيلاً عن ملفات الإدخال. على سبيل المثال ، يعمل مقابل طرف Go repo ، فإنه يُبلغ الآن:

$ tryhard $HOME/go/src
...
--- stats ---
  55620 (100.0% of   55620) function declarations
  14936 ( 26.9% of   55620) functions returning an error
 116539 (100.0% of  116539) statements
  27327 ( 23.4% of  116539) if statements
   7636 ( 27.9% of   27327) if <err> != nil statements
    119 (  1.6% of    7636) <err> name is different from "err" (use -l flag to list file positions)
   6037 ( 79.1% of    7636) return ..., <err> blocks in if <err> != nil statements
   1599 ( 20.9% of    7636) more complex error handler in if <err> != nil statements; prevent use of try (use -l flag to list file positions)
     17 (  0.2% of    7636) non-empty else blocks in if <err> != nil statements; prevent use of try (use -l flag to list file positions)
   5907 ( 77.4% of    7636) try candidates (use -l flag to list file positions)

هناك المزيد مما يتعين القيام به ، ولكن هذا يعطي صورة أوضح. على وجه التحديد ، يبدو أن 28٪ من جميع كشوفات الحساب if مخصصة للتحقق من الأخطاء ؛ هذا يؤكد وجود قدر كبير من التعليمات البرمجية المتكررة. من بين عمليات التحقق من الأخطاء هذه ، سيكون 77٪ قابلاً للتطبيق try .

تريارد $.
- احصائيات -
2930 (100.0٪ من 2930) تعريفات دالة
تقوم 1408 (48.1٪ من 2930) بإرجاع خطأ
10497 (100.0٪ من 10497) كشوف
2265 (21.6٪ من 10497) بيانات إذا
1383 (61.1٪ من 2265) إذا! = عبارات لا شيء
0 (0.0٪ من 1383)الاسم مختلف عن "err" (استخدم علامة -l
لسرد مواقف الملفات)
عودة 645 (46.6٪ من 1383) ... ،كتل في إذا! = لا شيء
صياغات
738 (53.4٪ من 1383) معالج أخطاء أكثر تعقيدًا في if! = لا شيء
صياغات؛ منع استخدام المحاولة (استخدم العلامة -l لسرد مواضع الملفات)
1 (0.1٪ من 1383) كتل أخرى غير فارغة في إذا! = لا شيء
صياغات؛ منع استخدام المحاولة (استخدم العلامة -l لسرد مواضع الملفات)
638 (46.1٪ من 1383) جرب المرشحين (استخدم الراية -l لملف القائمة
مناصب)
الذهاب بائع وزارة الدفاع
بائع tryhard $
- احصائيات -
37757 (100.0٪ من 37757) تعريفات الوظائف
تقوم 12557 (33.3٪ من 37757) بإرجاع خطأ
88919 كشف حساب (100.0٪ من 88919)
20143 (22.7٪ من 88919) إفادات
6555 (32.5٪ من 20143) إذا! = عبارات لا شيء
109 (1.7٪ من 6555)الاسم مختلف عن "err" (استخدم علامة -l
لسرد مواقف الملفات)
5545 (84.6٪ من 6555) يعود ... ،كتل في إذا! = لا شيء
صياغات
1010 (15.4٪ من 6555) معالج أخطاء أكثر تعقيدًا في if! = لا شيء
صياغات؛ منع استخدام المحاولة (استخدم العلامة -l لسرد مواضع الملفات)
12 (0.2٪ من 6555) كتل أخرى غير فارغة في إذا! = لا شيء
صياغات؛ منع استخدام المحاولة (استخدم العلامة -l لسرد مواضع الملفات)
5427 (82.8٪ من 6555) جرب المرشحين (استخدم علامة -l لملف القائمة
مناصب)

لذلك ، هذا هو السبب في أنني أضفت النقطتين في مثال الماكرو ، لذلك ستظل بارزة ولا تبدو وكأنها استدعاء دالة. لا يجب أن يكون القولون بالطبع. إنه مجرد مثال. أيضًا ، لا يخفي الماكرو أي شيء. أنت فقط تنظر إلى ما يفعله الماكرو ، وهناك تذهب. كما لو كانت دالة ، لكنها ستكون مضمنة. يبدو الأمر كما لو أجريت بحثًا واستبدلت بقطعة التعليمات البرمجية من الماكرو إلى وظائفك حيث تم استخدام الماكرو. بطبيعة الحال ، إذا صنع الأشخاص وحدات ماكرو من وحدات الماكرو وبدأوا في تعقيد الأمور ، حسنًا ، ألوم نفسك على جعل الكود أكثر تعقيدًا. :)

تضمين التغريدة

$ tryhard .
--- stats ---
   2930 (100.0% of    2930) function declarations
   1408 ( 48.1% of    2930) functions returning an error
  10497 (100.0% of   10497) statements
   2265 ( 21.6% of   10497) if statements
   1383 ( 61.1% of    2265) if <err> != nil statements
      0 (  0.0% of    1383) <err> name is different from "err" (use -l flag to list file positions)
    645 ( 46.6% of    1383) return ..., <err> blocks in if <err> != nil statements
    738 ( 53.4% of    1383) more complex error handler in if <err> != nil statements; prevent use of try (use -l flag to list file positions)
      1 (  0.1% of    1383) non-empty else blocks in if <err> != nil statements; prevent use of try (use -l flag to list file positions)
    638 ( 46.1% of    1383) try candidates (use -l flag to list file
positions)
$ go mod vendor
$ tryhard vendor
--- stats ---
  37757 (100.0% of   37757) function declarations
  12557 ( 33.3% of   37757) functions returning an error
  88919 (100.0% of   88919) statements
  20143 ( 22.7% of   88919) if statements
   6555 ( 32.5% of   20143) if <err> != nil statements
    109 (  1.7% of    6555) <err> name is different from "err" (use -l flag to list file positions)
   5545 ( 84.6% of    6555) return ..., <err> blocks in if <err> != nil statements
   1010 ( 15.4% of    6555) more complex error handler in if <err> != nil statements; prevent use of try (use -l flag to list file positions)
     12 (  0.2% of    6555) non-empty else blocks in if <err> != nil statements; prevent use of try (use -l flag to list file positions)
   5427 ( 82.8% of    6555) try candidates (use -l flag to list file
positions)
$

@ av86743 ،

آسف ، لم أعتبر أن "ردود البريد الإلكتروني لا تدعم Markdown"

علق بعض الأشخاص بأنه ليس من العدل حساب كود البائع في نتائج tryhard . على سبيل المثال ، في مكتبة الأمراض المنقولة جنسياً ، تشتمل التعليمات البرمجية المباعة في مكتبة الأمراض المنقولة جنسياً على حزم syscall التي تم إنشاؤها والتي تحتوي على الكثير من التحقق من الأخطاء والتي قد تشوه الصورة الإجمالية. الإصدار الأحدث من tryhard يستبعد الآن مسارات الملفات التي تحتوي على "vendor" افتراضيًا (يمكن التحكم في هذا أيضًا باستخدام العلم الجديد -ignore ). يتم تطبيقه على مكتبة الأمراض المنقولة جنسياً عند التلميح:

tryhard $HOME/go/src
/Users/gri/go/src/cmd/go/testdata/src/badpkg/x.go:1:1: expected 'package', found pkg
/Users/gri/go/src/cmd/go/testdata/src/notest/hello.go:6:1: expected declaration, found Hello
/Users/gri/go/src/cmd/go/testdata/src/syntaxerror/x_test.go:3:11: expected identifier
--- stats ---
  45424 (100.0% of   45424) func declarations
   8346 ( 18.4% of   45424) func declarations returning an error
  71401 (100.0% of   71401) statements
  16666 ( 23.3% of   71401) if statements
   4812 ( 28.9% of   16666) if <err> != nil statements
     86 (  1.8% of    4812) <err> name is different from "err" (-l flag lists details)
   3463 ( 72.0% of    4812) return ..., <err> blocks in if <err> != nil statements
   1349 ( 28.0% of    4812) complex error handler in if <err> != nil statements; cannot use try (-l flag lists details)
     17 (  0.4% of    4812) non-empty else blocks in if <err> != nil statements; cannot use try (-l flag lists details)
   3345 ( 69.5% of    4812) try candidates (-l flag lists details)

يبدو الآن أن 29٪ (28.9٪) من جميع كشوفات الحساب if مخصصة للتحقق من الأخطاء (أكثر قليلاً من ذي قبل) ، ويبدو أن 70٪ من تلك الكشوفات مرشحة للحصول على try (قليلًا) أقل من ذي قبل).

تغيير https://golang.org/cl/185177 يذكر هذه المشكلة: src: apply tryhard -err="" -ignore="vendor" -r $GOROOT/src

griesemer قمت بحساب "معالجات الأخطاء المعقدة" ولكن ليس "معالجات الأخطاء أحادية العبارة".

إذا كانت معظم المعالجات "المعقدة" عبارة عن عبارة واحدة ، فإن on err # 32611 سينتج عنها قدر من المدخرات المعيارية مثل try() - 2 سطور مقابل 3 خطوط × 70٪. ويضيف on err ميزة وجود نمط ثابت للغالبية العظمى من الأخطاء.

تضمين التغريدة

try هو حل نظيف ومفهوم للمشكلة المحددة التي يحاول حلها:
الإسهاب في معالجة الأخطاء.

الإسهاب في معالجة الأخطاء ليس مشكلة ، إنه قوة Go.

يقرأ الاقتراح: بعد مناقشة استمرت لمدة عام ، نضيف هذا المضمّن. استخدمه إذا كنت تريد رمزًا أقل إسهابًا ، وإلا فاستمر في فعل ما تفعله. رد الفعل هو بعض المقاومة غير المبررة تمامًا لميزة الاشتراك التي أظهر أعضاء الفريق مزايا واضحة لها!

_opt-in_ الخاص بك في وقت الكتابة هو _ ضرورة_ لجميع القراء ، بما في ذلك أنت في المستقبل.

مزايا واضحة

إذا كان من الممكن تسمية تعكير تدفق التحكم "بميزة" ، فعندئذٍ نعم.

يقدم try ، من أجل عادات المغتربين في جافا وسي ++ ، السحر الذي يجب أن يفهمه جميع غوفر. في غضون ذلك ، احتفظ ببعض الأسطر الأقلية للكتابة في أماكن قليلة (كما هو موضح tryhard عمليات التشغيل).

أود أن أزعم أن طريقي الأبسط onErr macro سيوفر المزيد من السطور في الكتابة ، وبالنسبة للأغلبية:

x, err = fa()
onErr break

r, err := fb(x)
onErr return 0, nil, err

if r, err := fc(x); onErr && triesleft > 0 {
  triesleft--
  continue retry
}

_ (لاحظ أنني في معسكر "اترك if err!= nil بمفرده" وتم نشر اقتراح مضاد أعلى لإظهار حل أبسط يمكن أن يجعل المزيد من المتذمرون سعداء.) _

يحرر:

أود أيضًا أن أشجع فريق go على جعل try متغيرًا مدمجًا إذا كان من السهل القيام بذلك
try(outf.Seek(linkstart, 0)), io.Copy(outf, exef)))

~ قصير للكتابة ، طويل للقراءة ، عرضة للانزلاق أو لسوء الفهم ، غير مستقر وخطير في مرحلة الصيانة. ~

كنت مخطئا. في الواقع ، سيكون المتغير try أفضل بكثير من الأعشاش ، حيث قد نكتبه بالسطور:

try( outf.Seek(linkstart, 0),
 io.Copy(outf, exef),
)

وإرجاع try(…) بعد الخطأ الأول.

لا أعتقد أن معالجة الخطأ الضمني (سكر التركيب) مثل المحاولة أمر جيد ، لأنه لا يمكنك التعامل مع أخطاء متعددة بشكل حدسي خاصة عندما تحتاج إلى تنفيذ وظائف متعددة بالتتابع.

أود أن أقترح شيئًا مثل Elixir's مع عبارة: https://www.openmymind.net/Elixirs-With-Statement/

شيء من هذا القبيل أدناه في golang:

switch a, b, err1 := go_func_01(),
       apple, banana, err2 := go_func_02(),
       fans, dissman, err3 := go_func_03()
{
   normal_func()
else
   err1 -> handle_err1()
   err2 -> handle_err2()
   _ -> handle_other_errs()
}

هل هذا النوع من الانتهاك لـ "Go يفضل ميزات أقل" و "إضافة ميزات إلى Go لن تجعله أفضل ولكنه أكبر"؟ أنا غير متاكد...

أنا فقط أريد أن أقول ، أنا شخصياً أنا راضٍ تمامًا عن الطريقة القديمة

if err != nil {
    return …, err
}

وبالتأكيد لا أريد قراءة الكود الذي كتبه الآخرون باستخدام try ... يمكن أن يكون السبب شقين:

  1. من الصعب أحيانًا تخمين ما بالداخل للوهلة الأولى
  2. يمكن دمج try s ، على سبيل المثال ، try( ... try( ... try ( ... ) ... ) ... ) ، يصعب قراءته

إذا كنت تعتقد أن كتابة التعليمات البرمجية بالطريقة القديمة لتمرير الأخطاء أمر شاق ، فلماذا لا تقوم فقط بالنسخ واللصق لأنهم يقومون دائمًا بنفس المهمة؟

حسنًا ، قد تعتقد أننا لا نريد دائمًا القيام بنفس المهمة ، ولكن بعد ذلك سيتعين عليك كتابة وظيفة "المعالج". لذلك ربما لن تخسر شيئًا إذا كنت لا تزال تكتب بالطريقة القديمة.

أليس أداء تأجيل مشكلة بهذا الحل المقترح؟ لقد قمت بقياس الوظائف مع وبدون تأجيل وكان هناك تأثير كبير على الأداء. لقد بحثت للتو على Google عن شخص آخر أجرى مثل هذا المعيار ووجدت تكلفة تبلغ 16 ضعفًا. لا أتذكر أنني كنت بهذا السوء ولكن أبطأ 4 مرات من رنين الجرس. كيف يمكن اعتبار شيء قد يضاعف أو يسوء وقت تشغيل الكثير من الوظائف حلاً عامًا قابلاً للتطبيق؟

@ eric-hawthorne أداء مؤجل قضية منفصلة. لا تتطلب المحاولة بطبيعتها تأجيلًا ولا تزيل القدرة على معالجة الأخطاء بدونها.

@ fabian-f لكن هذا الاقتراح قد يشجع على استبدال الكود الذي يقوم فيه شخص ما بتزيين الأخطاء بشكل منفصل لكل خطأ مضمن ضمن نطاق كتلة if err! = nil. سيكون ذلك فرقًا كبيرًا في الأداء.

@ eric-hawthorne نقلاً عن مستند التصميم:

س: ألا يكون استخدام التأجيل لتغليف الأخطاء بطيئًا؟

ج: في الوقت الحالي ، يعد البيان المؤجل مكلفًا نسبيًا مقارنة بتدفق التحكم العادي. ومع ذلك ، نعتقد أنه من الممكن جعل حالات الاستخدام الشائع لتأجيل معالجة الأخطاء قابلة للمقارنة في الأداء مع النهج "اليدوي" الحالي. راجع أيضًا CL 171758 والذي من المتوقع أن يحسن أداء التأجيل بحوالي 30٪.

كان هذا حديثًا مثيرًا للاهتمام من Rust مرتبطًا على Reddit. يبدأ الجزء الأكثر صلة في 47:55

حاولت tryhard على أكبر ريبو عام خاص بي ، https://github.com/dpinela/mflg ، وحصلت على ما يلي:

--- stats ---
    309 (100.0% of     309) func declarations
     36 ( 11.7% of     309) func declarations returning an error
    305 (100.0% of     305) statements
     73 ( 23.9% of     305) if statements
     29 ( 39.7% of      73) if <err> != nil statements
      0 (  0.0% of      29) <err> name is different from "err"
     19 ( 65.5% of      29) return ..., <err> blocks in if <err> != nil statements
     10 ( 34.5% of      29) complex error handler in if <err> != nil statements; cannot use try
      0 (  0.0% of      29) non-empty else blocks in if <err> != nil statements; cannot use try
     15 ( 51.7% of      29) try candidates

معظم الكود في هذا الريبو يدير حالة المحرر الداخلي ولا يقوم بأي إدخال / إخراج ، وكذلك هناك عدد قليل من عمليات التحقق من الأخطاء - وبالتالي فإن الأماكن التي يمكن استخدام المحاولة فيها محدودة نسبيًا. تقدمت وأعدت كتابة الكود يدويًا لاستخدامه في المحاولة حيثما أمكن ذلك ؛ يسترجع git diff --stat ما يلي:

 application.go                  | 42 +++++++++++-------------------------------
 internal/atomicwrite/write.go   | 35 ++++++++++++++---------------------
 internal/clipboard/clipboard.go | 17 +++--------------
 internal/config/config.go       | 15 +++++++--------
 internal/termesc/term.go        |  5 +----
 render.go                       |  8 ++------
 6 files changed, 38 insertions(+), 84 deletions(-)

(فرق ​​كامل هنا .)

من بين المعالجات العشرة التي وصفها tryhard بأنها "معقدة" ، 5 منها عبارة عن سلبيات كاذبة في Internal / atomicwrite / write.go؛ كانوا يستخدمون pkg / errors.WithMessage لالتفاف الخطأ. كان الالتفاف هو نفسه تمامًا بالنسبة لهم جميعًا ، لذلك أعدت كتابة هذه الوظيفة لاستخدام معالجات المحاولة والمؤجلة. انتهى بي الأمر بهذا الفرق (+14 ، -21 سطرًا):

@@ -20,21 +20,20 @@ const (
 // The file is created with mode 0644 if it doesn't already exist; if it does, its permissions will be
 // preserved if possible.
 // If some of the directories on the path don't already exist, they are created with mode 0755.
-func Write(filename string, contentWriter func(io.Writer) error) error {
+func Write(filename string, contentWriter func(io.Writer) error) (err error) {
+       defer func() { err = errors.WithMessage(err, errString(filename)) }()
+
        dir := filepath.Dir(filename)
-       if err := os.MkdirAll(dir, defaultDirPerms); err != nil {
-               return errors.WithMessage(err, errString(filename))
-       }
-       tf, err := ioutil.TempFile(dir, "mflg-atomic-write")
-       if err != nil {
-               return errors.WithMessage(err, errString(filename))
-       }
+       try(os.MkdirAll(dir, defaultDirPerms))
+       tf := try(ioutil.TempFile(dir, "mflg-atomic-write"))
        name := tf.Name()
-       if err = contentWriter(tf); err != nil {
-               os.Remove(name)
-               tf.Close()
-               return errors.WithMessage(err, errString(filename))
-       }
+       defer func() {
+               if err != nil {
+                       tf.Close()
+                       os.Remove(name)
+               }
+       }()
+       try(contentWriter(tf))
        // Keep existing file's permissions, when possible. This may race with a chmod() on the file.
        perms := defaultPerms
        if info, err := os.Stat(filename); err == nil {
@@ -42,14 +41,8 @@ func Write(filename string, contentWriter func(io.Writer) error) error {
        }
        // It's better to save a file with the default TempFile permissions than not save at all, so if this fails we just carry on.
        tf.Chmod(perms)
-       if err = tf.Close(); err != nil {
-               os.Remove(name)
-               return errors.WithMessage(err, errString(filename))
-       }
-       if err = os.Rename(name, filename); err != nil {
-               os.Remove(name)
-               return errors.WithMessage(err, errString(filename))
-       }
+       try(tf.Close())
+       try(os.Rename(name, filename))
        return nil
 }

لاحظ التأجيل الأول ، الذي يشرح الخطأ - لقد تمكنت من وضعه بشكل مريح في سطر واحد بفضل إعادة WithMessage صفرًا لخطأ لا شيء. يبدو أن هذا النوع من الغلاف يعمل أيضًا مع هذا النهج كما هو مقترح في الاقتراح.

كان اثنان من المعالجات "المعقدة" الأخرى قيد التنفيذ لـ ReadFrom و WriteTo:

var line string
line, err = br.ReadString('\n')
b.lines = append(b.lines, line)
if err != nil {
  if err == io.EOF {
    err = nil
  }
  return
}
func (b *Buffer) WriteTo(w io.Writer) (int64, error) {
    var n int64
    for _, line := range b.lines {
        nw, err := w.Write([]byte(line))
        n += int64(nw)
        if err != nil {
            return n, err
        }
    }
    return n, nil
}

لم تكن هذه قابلة للمحاولة حقًا ، لذلك تركتها وشأنها.

كان هناك رمزان آخران مثل هذا ، حيث أعيد خطأ مختلفًا تمامًا عن الخطأ الذي تحققت منه (وليس مجرد تغليفه). تركتهم دون تغيير أيضًا:

n, err := strconv.ParseInt(s[1:], 16, 32)
if err != nil {
    return Color{}, errors.WithMessage(err, fmt.Sprintf("color: parse %q", s))
}

آخرها كان في وظيفة لتحميل ملف التكوين ، والذي يقوم دائمًا بإرجاع تكوين (غير صفري) حتى إذا كان هناك خطأ. لقد تم التحقق من خطأ واحد فقط ، لذلك لم يستفد كثيرًا من المحاولة على الإطلاق:

-func Load() (*Config, error) {
-       c := Config{
+func Load() (c *Config, err error) {
+       defer func() { err = errors.WithMessage(err, "error loading config file") }()
+
+       c = &Config{
                TabWidth:    4,
                ScrollSpeed: 1,
                Lang:        make(map[string]LangConfig),
        }
-       f, err := basedir.Config.Open(filepath.Join("mflg", "config.toml"))
-       if err != nil {
-               return &c, errors.WithMessage(err, "error loading config file")
-       }
+       f := try(basedir.Config.Open(filepath.Join("mflg", "config.toml")))
        defer f.Close()
-       _, err = toml.DecodeReader(f, &c)
+       _, err = toml.DecodeReader(f, c)
        if c.TextStyle.Comment == (Style{}) {
                c.TextStyle.Comment = Style{Foreground: &color.Color{R: 0, G: 200, B: 0}}
        }
        if c.TextStyle.String == (Style{}) {
                c.TextStyle.String = Style{Foreground: &color.Color{R: 0, G: 0, B: 200}}
        }
-       return &c, errors.WithMessage(err, "error loading config file")
+       return c, err
 }

في الواقع ، الاعتماد على سلوك المحاولة المتمثل في الحفاظ على قيم معاملات العودة - مثل العودة العارية - يشعر ، في رأيي ، بصعوبة المتابعة ؛ ما لم أضف المزيد من عمليات التحقق من الأخطاء ، سألتزم بـ if err != nil في هذه الحالة بالذات.

TL ؛ DR: المحاولة مفيدة فقط في نسبة صغيرة إلى حد ما (عن طريق عدد الأسطر) من هذا الرمز ، ولكن حيثما تساعد ، فهي تساعد حقًا.

(مستجد هنا). فكرة أخرى لحجج متعددة. ماذا عن:

package trytest

import "fmt"

func errorInner() (string, error) {
   return "", fmt.Errorf("inner error")
}

func errorOuter() (string, error) {
   tryreturn errorInner()
   return "", nil
}

func errorOuterWithArg() (string, error) {
   var toProcess string
   tryreturn toProcess, _ = errorOuter()
   return toProcess + "", nil
}

func errorOuterWithArgStretch() (bool, string, error) {
   var toProcess string
   tryreturn false, ( toProcess,_ = errorOuterWithArg() )
   return true, toProcess + "", nil
}

على سبيل المثال ، يؤدي tryreturn إلى إرجاع جميع القيم إذا كان الخطأ في الماضي
القيمة ، وإلا يستمر التنفيذ.

المبادئ التي أتفق معها:
-

  • خطأ في معالجة استدعاء دالة يستحق السطر الخاص به. Go هو صريح بشكل متعمد في تدفق التحكم ، وأعتقد أن تعبئة ذلك في تعبير يتعارض مع صراحة.
  • سيكون من المفيد أن يكون لديك طريقة معالجة خطأ تناسب سطر واحد. (ومن الناحية المثالية تتطلب كلمة واحدة فقط أو بضعة أحرف من الصيغة المعيارية قبل معالجة الخطأ الفعلي). 3 سطور من معالجة الخطأ لكل استدعاء دالة هي نقطة احتكاك في اللغة التي تستحق بعض الحب والاهتمام.
  • يجب أن يكون أي عنصر مضمّن يتم إرجاعه (مثل try المقترح) عبارة على الأقل ، ويجب أن يحتوي على كلمة إرجاع بشكل مثالي. مرة أخرى ، أعتقد أن التحكم في التدفق في Go يجب أن يكون واضحًا.
  • تكون أخطاء Go مفيدة للغاية عندما تحتوي على سياق إضافي (أقوم دائمًا بإضافة سياق إلى أخطائي). يجب أن يدعم حل هذه المشكلة أيضًا رمز معالجة أخطاء إضافة السياق.

بناء الجملة الذي أؤيده:
-

  • عبارة reterr _x_ (السكر النحوي لـ if err != nil { return _x_ } ، مُسمى صراحة للإشارة إلى أنه سيعود)

لذلك يمكن أن تكون الحالات الشائعة عبارة عن سطر واحد قصير وصريح لطيف:

func foo() error {
    a, err := bar()
    reterr err

    b, err := baz(a)
    reterr fmt.Errorf("getting the baz of %v: %v", a, err)

    return nil
}

بدلاً من الخطوط الثلاثة هم الآن:

func foo() error {
    a, err := bar()
    if err != nil {
        return err
    }

    b, err := baz()
    if err != nil {
        return fmt.Errorf("getting the baz of %v: %v", a, err)
    }

    return nil
}

الأشياء التي لا أتفق معها:



    • "هذا تغيير صغير جدًا بحيث لا يستحق تغيير اللغة"

      لا أوافق ، هذا تغيير في نوعية الحياة يزيل أكبر مصدر للاحتكاك لدي عند كتابة كود Go. عند استدعاء وظيفة تتطلب 4 خطوط

  • "سيكون من الأفضل انتظار حل أكثر عمومية"
    لا أوافق ، أعتقد أن هذه المشكلة تستحق حلها الخاص. تعمل النسخة المعممة من هذه المشكلة على تقليل الشفرة المعيارية ، والإجابة العامة هي وحدات الماكرو - والتي تتعارض مع روح Go الخاصة بالتعليمات البرمجية الصريحة. إذا لم تقدم Go منشأة ماكرو عامة ، فيجب أن توفر بدلاً من ذلك بعض وحدات الماكرو المحددة والمستخدمة على نطاق واسع مثل reterr (كل شخص يكتب Go سيستفيد من reterr).

Qhesz الأمر لا يختلف كثيرًا عن المحاولة:

func foo() error {
    a, err := bar()
    try(err)

    b, err := baz(a)
    try(wrap(err, "getting the baz of %v", a))

    return nil
}

reusee أقدر هذا الاقتراح ، لم أدرك أنه يمكن استخدامه بهذه الطريقة. يبدو لي الأمر مزعجًا بعض الشيء ، لكنني أحاول أن أضع إصبعي على السبب.

أعتقد أن "محاولة" هي كلمة غريبة لاستخدامها بهذه الطريقة. "try (action ())" منطقي في اللغة الإنجليزية ، بينما "try (value)" ليس كذلك في الحقيقة. سأكون على ما يرام معها إذا كانت كلمة مختلفة.

أيضًا try(wrap(...)) يقيّم wrap(...) أولاً ، أليس كذلك؟ ما مقدار ذلك في رأيك يتم تحسينه بواسطة المترجم؟ (مقارنة بتشغيل if err != nil فقط؟)

أيضًا # 32611 هو اقتراح مشابه بشكل غامض ، وتحتوي التعليقات على بعض الآراء المفيدة من كل من فريق Go الأساسي وأعضاء المجتمع ، لا سيما حول الاختلافات بين الكلمات الرئيسية والوظائف المضمنة.

Qhesz أتفق معك بشأن التسمية. ربما يكون check أكثر ملاءمة نظرًا لأن "check (action ())" أو "check (err)" تقرأ جيدًا.

reusee وهذا مثير للسخرية بعض الشيء ، حيث أن مسودة التصميم الأصلي استخدمت check .

في 7/6/19 ، كتب mirtchovski [email protected] :

تريارد $.
- احصائيات -
2930 (100.0٪ من 2930) تعريفات دالة
تقوم 1408 (48.1٪ من 2930) بإرجاع خطأ
[...]

لا يسعني إلا أن أكون مؤذًا هنا: هو أن "الوظائف تعود و
الخطأ كحجة أخيرة "؟

لوسيو.

فكرت أخيرًا في سؤالي أعلاه ، ما زلت أفضل بناء الجملة try(err, wrap("getting the baz of %v: %v", a, err)) ، مع تنفيذ التفاف () فقط إذا لم يكن الخطأ لا شيء. بدلاً من try(wrap(err, "getting the baz of %v", a)) .

Qhesz التنفيذ المحتمل لـ wrap يمكن أن يكون:

func wrap(err error, format string, args ...interface{}) error {
    if err == nil {
        return nil
    }
    return fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), err)
}

إذا كان المترجم يمكنه تضمين wrap ، فلا يوجد فرق في الأداء بين عبارة wrap و if err != nil .

reusee أعتقد أنك تقصد if err == nil ؛)

Qhesz التنفيذ المحتمل لـ wrap يمكن أن يكون:

func wrap(err error, format string, args ...interface{}) error {
  if err == nil {
      return nil
  }
  return fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), err)
}

إذا كان المترجم يمكنه تضمين wrap ، فلا يوجد فرق في الأداء بين عبارة wrap و if err != nil .

%w غير صالح فعل go

(أفترض أنه كان يقصد٪ v ...)

لذلك ، بينما يُفضل كتابة كلمة رئيسية ، فأنا أفهم أن المضمنة هي الطريقة المفضلة لتطبيقها.

أعتقد أنني سأكون على متن هذا الاقتراح إذا

  • كان check بدلاً من try
  • لا يمكن استخدام جزء من أداة Go التي يتم فرضها إلا كإفادة (على سبيل المثال ، تعامل معها مثل "عبارة" مضمنة ، وليس مثل "وظيفة" مضمنة. إنها فقط مضمنة لأسباب عملية ، إنها تحاول أن تكون عبارة دون أن تكون تم تنفيذه بواسطة اللغة.) على سبيل المثال ، إذا لم يُرجع أي شيء ، فلن يكون صالحًا أبدًا داخل تعبير ، مثل panic() .
  • ~ ربما بعض المؤشرات على أنه ماكرو ويؤثر على تدفق التحكم ، وهو ما يميزه عن استدعاء الوظيفة. (على سبيل المثال check!(...) كما يفعل Rust ، لكن ليس لدي رأي قوي حول الصيغة المحددة) ~ غيرت رأيي

ثم سيكون ذلك رائعًا ، سأستخدمه في كل مكالمة وظيفية أجريها.

واعتذارات بسيطة للموضوع ، لقد وجدت الآن فقط التعليقات أعلاه التي تحدد إلى حد كبير ما قلته للتو.

deanveloper ثابت ، شكرا.

تمت إضافةolekukonkoQhesz ٪ w حديثًا في نصيحة: https://tip.golang.org/pkg/fmt/#Errorf

أعتذر عن عدم قراءة كل شيء في هذا الموضوع ، لكني أود أن أذكر شيئًا لم أره.

أرى حالتين منفصلتين حيث يمكن أن تكون معالجة أخطاء Go1 مزعجة: رمز "جيد" صحيح ولكنه متكرر بعض الشيء ؛ والشفرة "السيئة" هي خاطئة ، لكنها تعمل في الغالب.

في الحالة الأولى ، يجب أن يكون هناك حقًا بعض المنطق في كتلة if-err ، والانتقال إلى بناء أسلوب المحاولة يثبط هذه الممارسة الجيدة من خلال جعل إضافة منطق إضافي أكثر صعوبة.

في الحالة الثانية ، غالبًا ما يكون الرمز السيئ بالشكل:

..., _ := might_error()

أو فقط

might_error()

حيث يحدث هذا عادةً لأن المؤلف لا يعتقد أنه من المهم بدرجة كافية قضاء أي وقت في معالجة الأخطاء ، ويأمل فقط أن يعمل كل شيء. يمكن تحسين هذه الحالة بشيء قريب جدًا من عدم بذل أي جهد ، مثل:

..., XXX := might_error()

حيث XXX هو رمز يعني "أي شيء هنا يجب أن يوقف التنفيذ بطريقة ما". هذا من شأنه أن يوضح أن هذا ليس رمزًا جاهزًا للإنتاج - المؤلف على دراية بحالة خطأ ، لكنه لم يستثمر الوقت لتقرير ما يجب فعله.

بالطبع هذا لا يمنع الحل من النوع returnif handle(err) .

أنا ضد المحاولة ، بشكل متوازن ، مع تحياتي للمساهمين على التصميم البسيط اللطيف. لست خبيرًا كبيرًا في Go ، لكنني كنت من أوائل المتبنين ولديّ كود في الإنتاج هنا وهناك. أنا أعمل في مجموعة Serverless في AWS ويبدو أننا سنطلق خدمة قائمة على Go في وقت لاحق من هذا العام والتي كتبتها بشكل أساسي لتسجيل الوصول الأول. أنا رجل عجوز حقًا ، طريقي للذهاب عبر C و Perl و Java و Ruby. ظهرت مشاكلي من قبل في ملخص المناقشة المفيد للغاية ولكني ما زلت أعتقد أنها تستحق التكرار.

  1. Go هي لغة صغيرة وبسيطة ، وبالتالي حققت سهولة قراءة لا مثيل لها. أنا بشكل انعكاسي ضد إضافة أي شيء إليها ما لم تكن الفائدة كبيرة من الناحية النوعية. عادة لا يلاحظ المرء منحدرًا زلقًا حتى يميله المرء ، لذلك دعونا لا نتخذ الخطوة الأولى.
  2. لقد تأثرت بشدة بالحجة أعلاه حول تسهيل تصحيح الأخطاء. يعجبني الإيقاع البصري ، في كود البنية التحتية منخفض المستوى ، لمقاطع صغيرة من التعليمات البرمجية على غرار "Do A. تحقق مما إذا كان يعمل. افعل ب. تحقق مما إذا كان يعمل… إلخ "لأن سطور" الفحص "هي المكان الذي تضع فيه printf أو نقطة التوقف. ربما يكون الجميع أكثر ذكاءً ، لكني في النهاية استخدم مصطلح نقطة التوقف هذا بانتظام.
  3. بافتراض قيم الإرجاع المسماة ، "try" تعادل if err != nil { return } (أعتقد؟) أنا شخصياً أحب قيم الإرجاع المسماة ، وبالنظر إلى مزايا أدوات تزيين الأخطاء ، أظن أن نسبة قيم إرجاع الخطأ المسماة ستذهب إلى زيادة رتيبة مما يضعف فوائد المحاولة.
  4. لقد أحببت في البداية اقتراح الحكومة المباركة أحادية الخط في السطر أعلاه ، ولكن بشكل عام ، ستتبنى IDEs بلا شك لغة العرض هذه على أي حال ، وسيضحي الخط الواحد بميزة التصحيح هنا.
  5. يبدو من المحتمل جدًا أن بعض أشكال التعشيش التي تحتوي على كلمة "try" ستفتح الباب للمُضاعِفين في مهنتنا لإحداث نفس النوع من الخراب الذي لديهم مع تدفقات Java والمقسمات وما إلى ذلك. كان Go أكثر نجاحًا من معظم اللغات الأخرى في حرمان الماهر بيننا من فرص إظهار مهاراتهم.

مرة أخرى ، تهانينا للمجتمع على الاقتراح النظيف الجميل والمناقشة البناءة.

لقد قضيت وقتًا طويلاً في القفز إلى مكتبات أو أجزاء من التعليمات البرمجية غير مألوفة وقراءتها على مدار السنوات القليلة الماضية. على الرغم من الملل ، يوفر if err != nil لغة سهلة القراءة ، وإن كانت مطولة عموديًا. إن روح ما يحاول try() تحقيقه نبيلة ، وأعتقد أن هناك شيئًا يجب القيام به ، لكن هذه الميزة تبدو خاطئة وأن الاقتراح يرى النور مبكرًا جدًا (أي يجب أن يأتي بعد xerr والأدوية حصلت على فرصة للتتبيل في إصدار ثابت لمدة 6-12 شهرًا).

يبدو أن تقديم try() اقتراح نبيل وجدير بالاهتمام (على سبيل المثال ، 29٪ - ~ 40٪ من كشوفات الحساب if هي لفحص if err != nil ). على السطح ، يبدو أن النموذج المعياري المختزل المرتبط بمعالجة الأخطاء سيحسن تجارب المطور. تأتي المقايضة من تقديم try() في شكل حمل معرفي من الحالات الخاصة شبه الدقيقة. تتمثل إحدى أكبر مزايا Go في أنها بسيطة وهناك القليل جدًا من العبء المعرفي المطلوب لإنجاز شيء ما (مقارنة بـ C ++ حيث تكون مواصفات اللغة كبيرة ودقيقة). يعد تقليل مقياس كمي واحد (LoC if err != nil ) مقابل زيادة المقياس الكمي للتعقيد العقلي بمثابة حبة دواء يصعب ابتلاعها (أي الضريبة العقلية على أثمن مورد لدينا ، قوة العقل).

على وجه الخصوص ، الحالات الخاصة الجديدة للطريقة التي يتم التعامل بها مع $ # try() مع go و defer ، ومتغيرات الإرجاع المسماة تجعل try() سحريًا بما يكفي لجعل الكود أقل صريحًا بحيث يتعين على جميع مؤلفي أو قراء كود Go معرفة هذه الحالات الخاصة الجديدة من أجل قراءة أو كتابة Go بشكل صحيح ولم يكن هذا العبء موجودًا من قبل. يعجبني أن هناك حالات خاصة صريحة لهذه المواقف - خاصةً مقابل تقديم شكل من أشكال السلوك غير المحدد ، لكن حقيقة أنها بحاجة إلى الوجود في المقام الأول تشير إلى أن هذا غير مكتمل في الوقت الحالي. إذا كانت الحالات الخاصة تتعلق بأي شيء سوى معالجة الأخطاء ، فقد يكون ذلك مقبولاً ، ولكن إذا كنا نتحدث بالفعل عن شيء يمكن أن يؤثر على ما يصل إلى 40٪ من جميع LoC ، فستحتاج هذه الحالات الخاصة إلى التدريب في المجتمع بأكمله و هذا يرفع تكلفة العبء المعرفي لهذا الاقتراح إلى مستوى عالٍ بما يكفي لتبرير القلق.

يوجد مثال آخر في Go حيث تكون قواعد الحالة الخاصة بالفعل منحدرًا معرفيًا زلقًا ، وهي المتغيرات المثبتة وغير المثبتة. ليس من الصعب فهم الحاجة إلى تثبيت المتغيرات في الممارسة العملية ، ولكن يتم إغفالها نظرًا لوجود سلوك ضمني هنا وهذا يتسبب في عدم تطابق بين المؤلف والقارئ وما يحدث مع الملف التنفيذي المترجم في وقت التشغيل. حتى مع وجود نصوص مثل scopelint يبدو أن العديد من المطورين لا يفهمون هذا المسكت (أو ما هو أسوأ من ذلك ، فهم يعرفون ذلك ولكنهم يفوتونه لأن هذا المسكت قد يفلت من أذهانهم). نشأت بعض أكثر الأخطاء غير المتوقعة وصعوبة في تشخيص أخطاء وقت التشغيل من البرامج العاملة من هذه المشكلة بالذات (على سبيل المثال ، يتم ملء جميع الكائنات N بنفس القيمة بدلاً من التكرار على شريحة والحصول على القيم المميزة المتوقعة). يختلف مجال الفشل من try() عن المتغيرات المثبتة ، ولكن سيكون هناك تأثير على كيفية كتابة الأشخاص للتعليمات البرمجية كنتيجة لذلك.

تحتاج مقترحات IMNSHO و xerr والأدوية العامة إلى وقت للخبز في الإنتاج لمدة 6-12 شهرًا قبل محاولة التغلب على النموذج المعياري من if err != nil . من المحتمل أن تمهد العوامل الوراثية الطريق لمزيد من معالجة الأخطاء الغنية وطريقة اصطلاحية جديدة لمعالجة الأخطاء. بمجرد أن يبدأ التعامل مع الأخطاء الاصطلاحية مع الأدوية الجنسية في الظهور ، عندها وفقط عندها ، هل من المنطقي إعادة النظر في مناقشة حول try() أو أيًا كان.

لا أتظاهر بمعرفة كيف ستؤثر الأدوية الجنيسة على معالجة الأخطاء ، ولكن يبدو لي مؤكدًا أن الأدوية الجنيسة ستُستخدم لإنشاء أنواع غنية من شبه المؤكد أنها ستُستخدم في معالجة الأخطاء. بمجرد تغلغل الأدوية الجنيسة في المكتبات وإضافتها إلى معالجة الأخطاء ، قد تكون هناك طريقة واضحة لإعادة استخدام try() لتحسين تجربة المطور فيما يتعلق بمعالجة الأخطاء.

نقاط القلق التي لدي هي:

  1. try() ليس معقدًا بمعزل عن الآخرين ، لكنه عبء معرفي حيث لم يكن هناك أي شيء سابقًا.
  2. من خلال التحميص في err != nil في السلوك المفترض لـ try() ، تمنع اللغة استخدام err كطريقة لإيصال الحالة أعلى المكدس.
  3. من الناحية الجمالية ، يبدو أن try() هو ذكاء إجباري ولكنه ليس ذكيًا بما يكفي لإرضاء الاختبار الواضح والواضح الذي تتمتع به معظم لغة Go. مثل معظم الأشياء التي تنطوي على معايير ذاتية ، فهذه مسألة ذوق شخصي وخبرة ويصعب تحديدها كمياً.
  4. يبدو أن معالجة الخطأ مع بيانات switch / case وتغليف الأخطاء لم يمسها هذا الاقتراح ، وفرصة ضائعة ، مما يقودني إلى الاعتقاد بأن هذا الاقتراح هو لبنة في جعل مجهول مجهول معروفًا - معروف (أو في أسوأ الأحوال ، معروف - غير معروف).

أخيرًا ، يبدو اقتراح try() وكأنه كسر جديد في السد كان يعيق طوفانًا من الفروق الدقيقة الخاصة باللغة مثل ما هربنا منه بترك C ++ خلفنا.

TL ؛ DR: ليست استجابة #nevertry بقدر ما هي ، "ليس الآن ، ليس بعد ، ودعنا نفكر في هذا مرة أخرى في المستقبل بعد xerr وتنضج الأدوية الجنيسة في النظام البيئي. "

# 32968 المرتبط أعلاه ليس بالضبط اقتراحًا مضادًا كاملاً ، لكنه يبني على خلافي مع القدرة الخطيرة على التداخل التي يمتلكها الماكرو try . على عكس # 32946 ، يعد هذا عرضًا جادًا ، وآمل أن يفتقر إلى عيوب خطيرة (من الأفضل لك رؤيته وتقييمه والتعليق عليه بالطبع). مقتطفات:

  • _ الماكرو check ليس سطرًا واحدًا: فهو يساعد كثيرًا في حالة التكرار
    يجب إجراء عمليات التحقق التي تستخدم نفس التعبير على مقربة شديدة.
  • _إن نسخته الضمنية يتم تجميعها بالفعل في الملعب.

قيود التصميم (التقى)

إنه مدمج ، ولا يتداخل في سطر واحد ، فهو يسمح بتدفقات أكثر من try وليس لديه توقعات حول شكل الكود بداخله. لا يشجع على العودة العارية.

مثال على الاستخدام

// built-in 'check' macro signature: 
func check(Condition bool) {}

check(err != nil) // explicit catch: label.
{
    ucred, err := getUserCredentials(user)
    remote, err := connectToApi(remoteUri)
    err, session, usertoken := remote.Auth(user, ucred)
    udata, err := session.getCalendar(usertoken)

  catch:               // sad path
    ucred.Clear()      // cleanup passwords
    remote.Close()     // do not leak sockets
    return nil, 0, err // dress before leaving
}
// happy path

// implicit catch: label is above last statement
check(x < 4) 
  {
    x, y = transformA(x, z)
    y, z = transformB(x, y)
    x, y = transformC(y, z)
    break // if x was < 4 after any of above
  }

أتمنى أن يساعدك هذا ، استمتع!

لقد قرأت بقدر ما أستطيع لفهم هذا الموضوع. أنا أؤيد ترك الأشياء كما هي بالضبط.

أسبابي:

  1. أنا ، ولا أي شخص قمت بتدريسه ، لم يفهم Go على الإطلاق معالجة الأخطاء
  2. أجد نفسي لا أتخطى مطلقًا أي خطأ لأنه من السهل جدًا القيام بذلك في كل مرة

أيضًا ، ربما أسيء فهم الاقتراح ، ولكن عادةً ما ينتج عن إنشاء try بلغات أخرى أسطر متعددة من التعليمات البرمجية التي قد تؤدي جميعها إلى حدوث خطأ ، وبالتالي تتطلب أنواعًا من الأخطاء. إضافة التعقيد وغالبًا نوعًا ما من هندسة الأخطاء المسبقة وجهود التصميم.

في هذه الحالات (وقد فعلت ذلك بنفسي) ، تتم إضافة كتل محاولة متعددة. الذي يطيل الكود ، ويلقي بظلاله على التنفيذ.

إذا كان تطبيق Go try يختلف عن تطبيق اللغات الأخرى ، فسيظهر المزيد من الالتباس.

اقتراحي هو ترك الخطأ في التعامل مع ما هو عليه

أعلم أن الكثير من الناس قد فكروا في الأمر ، لكني أود أن أضيف نقدًا للمواصفات كما هي.

أكثر جزء من المواصفات يزعجني هو هذين الطلبين:

لذلك نقترح عدم السماح بالمحاولة كوظيفة مستدعاة في تعليمة go.
...
لذلك نقترح عدم السماح بالمحاولة كوظيفة مستدعاه في بيان التأجيل أيضًا.

ستكون هذه أول وظيفة مضمنة يكون هذا صحيحًا (يمكنك حتى تحرير defer و go a panic ) لأنه لا يلزم إهمال النتيجة. إن إنشاء وظيفة مضمنة جديدة تتطلب من المترجم إعطاء اعتبار خاص للتحكم في التدفق يبدو وكأنه طلب كبير ويكسر التماسك الدلالي لـ go. كل رمز مميز لتدفق التحكم في go ليس وظيفة.

الحجة المضادة لشكواي هي أن القدرة على defer و go a panic ربما يكون حادثًا وليس مفيدًا جدًا. ومع ذلك ، فإن وجهة نظري هي أن التماسك الدلالي للوظائف قيد التشغيل قد تم كسره من خلال هذا الاقتراح وليس من المهم أن يكون استخدام defer و go دائمًا منطقيًا. من المحتمل أن يكون هناك الكثير من الوظائف غير المضمنة التي لن يكون لها معنى لاستخدام defer أو go مع ، ولكن لا يوجد سبب واضح ، من الناحية الدلالية ، لماذا لا تكون كذلك. لماذا هذا المبني يحصل على إعفاء نفسه من العقد الدلالي لـ funtions in go؟

أعلم أن griesemer لا تريد إدخال آراء جمالية حول هذا الاقتراح في المناقشة ، لكنني أعتقد أن أحد الأسباب التي تجعل الناس يجدون هذا الاقتراح مثيرًا للاشمئزاز من الناحية الجمالية هو أنهم يشعرون أنه لا يتم إضافته كوظيفة.

يقول الاقتراح:

نقترح إضافة وظيفة مضمنة جديدة تشبه الوظيفة تسمى try with signature (pseudo-code)

func try(expr) (T1, T2, … Tn)

باستثناء أن هذه ليست وظيفة (التي يعترف بها الاقتراح بشكل أساسي). إنه ، بشكل فعال ، ماكرو لمرة واحدة مدمج في مواصفات اللغة (إذا كان سيتم قبوله). هناك عدد قليل من المشاكل مع هذا التوقيع.

  1. ماذا يعني أن تقبل الدالة تعبيرًا عامًا كوسيطة ، ناهيك عن تعبير يسمى. في كل مرة يتم استخدام كلمة "تعبير" في المواصفات ، فهذا يعني شيئًا مثل وظيفة غير مبررة. كيف يمكن اعتبار الوظيفة "المسماة" على أنها تعبير ، في حين أن قيم العودة في كل سياق آخر هي ما يكون نشطًا من الناحية الدلالية. IE نحن نفكر في وظيفة مستدعاه على أنها قيم العودة. الاستثناءات ، بشكل واضح ، هي go و defer ، وكلاهما رمزان خام ليسا دالات مضمنة.

  2. حصل هذا الاقتراح أيضًا على توقيع وظيفي غير صحيح ، أو على الأقل ليس له معنى ، التوقيع الفعلي هو:

func try(R1, R2, ... Rn) ((R|T)1, (R|T)2, ... (R|T)(n-1), ?Rn) 
// where T is the return params of the function that try is being called from
// where `R` is a return value from a function, `Rn` must be an error
// try will return the R values if Rn is nil and not return Tn at all
// if Rn is not nil then the T values will be returned as well as Rn at the end 
  1. لا يتضمن الاقتراح ما يحدث في المواقف التي يتم فيها استدعاء المحاولة بالحجج. ماذا يحدث إذا تم استدعاء try باستخدام الوسيطات:
try(arg1, arg2,..., err)

أعتقد أن سبب عدم معالجة ذلك هو أن try يحاول قبول وسيطة expr والتي تمثل في الواقع عدد n من وسيطات الإرجاع من دالة بالإضافة إلى شيء آخر ، مما يوضح الحقيقة بشكل أكبر أن هذا الاقتراح يكسر التماسك الدلالي لوظيفة ما.

شكواي الأخيرة ضد هذا الاقتراح هي أنه يكسر المعنى الدلالي للوظائف المضمنة. لست غير مبال بفكرة أن الدوال المضمنة تحتاج أحيانًا إلى الإعفاء من القواعد الدلالية للوظائف "العادية" (مثل عدم القدرة على تخصيصها للمتغيرات ، إلخ) ، ولكن هذا الاقتراح ينشئ مجموعة كبيرة من الاستثناءات من " العادية "التي يبدو أنها تحكم الوظائف داخل golang.

هذا الاقتراح يجعل try شيئًا جديدًا لم يكن موجودًا ، إنه ليس رمزًا مميزًا تمامًا وليس وظيفة تمامًا ، كلاهما ، والذي يبدو وكأنه سابقة سيئة لتعيينها من حيث إنشاء التماسك الدلالي طوال الوقت اللغة.

إذا أردنا إضافة شيء جديد في تدفق التحكم ، فأنا أجادل أنه من المنطقي جعله رمزًا خامًا مثل goto ، وآخرون. أعلم أنه ليس من المفترض أن نؤيد مقترحات في هذه المناقشة ، ولكن على سبيل المثال الموجز ، أعتقد أن شيئًا كهذا منطقي أكثر:

f, err := os.Open("/dev/stdout")
throw err

في حين أن هذا يضيف سطرًا إضافيًا من التعليمات البرمجية ، أعتقد أنه يعالج كل مشكلة أثارتها ، كما أنه يزيل النقص الكامل في تواقيع الوظيفة "البديلة" مع try .

تحرير 1 : ملاحظة حول الاستثناءات لحالات defer و go حيث لا يمكن استخدام المحتوى المدمج ، لأنه سيتم تجاهل النتائج ، بينما مع try لا يمكن أن تكون كذلك بالفعل قال أن الوظيفة لها نتائج.

nathanjsweet ، الاقتراح الذي تبحث عنه هو # 32611 :-)

nathanjsweet يتبين أن بعض ما تقوله ليس كذلك. لا تسمح اللغة باستخدام defer أو go مع الوظائف المعلنة مسبقًا append cap complex imag len make new real . كما أنه لا يسمح باستخدام defer أو go بالوظائف المحددة بالمواصفات unsafe.Alignof unsafe.Offsetof unsafe.Sizeof .

شكرًا nathanjsweet على تعليقك الشامل - أشار ianlancetaylor بالفعل إلى أن حججك غير صحيحة من الناحية الفنية. دعني أتوسع قليلاً:

1) ذكرت أن جزء المواصفات الذي لا يسمح بـ try مع go و defer يزعجك أكثر لأن try سيكون أول مدمج أين هذا صحيح. هذا ليس صحيحا. المترجم بالفعل لا يسمح على سبيل المثال ، defer append(a, 1) . وينطبق الشيء نفسه على المكونات الإضافية الأخرى التي تنتج نتيجة يتم إسقاطها بعد ذلك على الأرض. سينطبق هذا القيد أيضًا على try لهذه المسألة (باستثناء عندما لا يُرجع try نتيجة). (السبب في أننا ذكرنا هذه القيود في مستند التصميم هو أن نكون دقيقين قدر الإمكان - فهي حقًا غير ذات صلة من الناحية العملية. أيضًا ، إذا قرأت مستند التصميم بدقة ، فهذا لا يعني أننا لا نستطيع ربح try العمل مع go أو defer - إنه يقترح ببساطة أننا لا نسمح بذلك ؛ غالبًا كإجراء عملي. إنه "طلب كبير" - لاستخدام كلماتك - لكسب try العمل بـ go و defer على الرغم من أنه عديم الفائدة عمليًا.)

try ) تقترح أن يجد بعض الأشخاص أن "مثيرًا للاشمئزاز من الناحية الجمالية" لبعض الناس لأنه ليس من الناحية الفنية وظيفة ، ثم تركز على القواعد الخاصة للتوقيع. ضع في اعتبارك new ، make ، append ، unsafe.Offsetof : لديهم جميعًا قواعد متخصصة لا يمكننا التعبير عنها باستخدام وظيفة Go العادية. انظر إلى unsafe.Offsetof الذي يحتوي بالضبط على نوع المتطلبات النحوية لوسيطته (يجب أن يكون حقلًا هيكليًا!) التي نطلبها من الوسيطة لـ try (يجب أن تكون قيمة واحدة من النوع error أو استدعاء دالة بإرجاع error كنتيجة أخيرة). نحن لا نعبر عن هذه التواقيع رسميًا في المواصفات ، لأي من هذه العناصر المضمنة لأنها لا تتناسب مع الشكلية الحالية - إذا كان الأمر كذلك ، فلن يكون من الضروري أن تكون مدمجة. بدلا من ذلك نعبر عن قواعدهم في النثر. هذا هو السبب في أنها مدمجة والتي تعد بمثابة فتحة الهروب في Go ، حسب التصميم ، من اليوم الأول. لاحظ أيضًا أن مستند التصميم واضح جدًا بشأن هذا الأمر.

3) يتناول الاقتراح أيضًا ما يحدث عندما يتم استدعاء try مع وسيطات (أكثر من واحدة): غير مسموح بها. ينص مستند التصميم صراحة على أن try يقبل تعبير وسيطة وارد (واحد).

4) أنت تذكر أن "هذا الاقتراح يكسر المعنى الدلالي للوظائف المضمنة". لا يوجد مكان تقوم Go فيه بتقييد ما يمكن أن يفعله المدمج وما لا يمكنه فعله. لدينا الحرية الكاملة هنا.

شكرا.

تضمين التغريدة

لاحظ أيضًا أن مستند التصميم واضح جدًا بشأن هذا الأمر.

هل يمكنك أن تشير إلى هذا. لقد فوجئت بقراءة هذا.

أنت تذكر أن "هذا الاقتراح يكسر المعنى الدلالي للوظائف المضمنة". لا يوجد مكان تقوم Go فيه بتقييد ما يمكن أن يفعله المدمج وما لا يمكنه فعله. لدينا الحرية الكاملة هنا.

أعتقد أن هذه نقطة عادلة. ومع ذلك ، أعتقد أن هناك ما تم توضيحه في مستندات التصميم وما يشبه "go" (وهو أمر يتحدث عنه Rob Pike كثيرًا). أعتقد أنه من الإنصاف بالنسبة لي أن أقول إن اقتراح try يوسع الطرق التي تكسر بها الوظائف المضمنة القواعد التي نتوقع أن تتصرف بها الوظائف ، وقد أقرت بأني أفهم سبب ضرورة ذلك للمبنى الآخر ولكني أعتقد في هذه الحالة أن التوسع في كسر القواعد هو:

  1. غير بديهي من بعض النواحي. هذه هي الوظيفة الأولى التي تغير منطق تدفق التحكم بطريقة لا تؤدي إلى فك المكدس (مثل panic و os.Exit do)
  2. استثناء جديد لكيفية عمل اصطلاحات استدعاء الدالة. لقد أعطيت مثال unsafe.Offsetof كحالة حيث يوجد متطلب نحوي لاستدعاء دالة (من المدهش بالنسبة لي أن يتسبب هذا في حدوث خطأ في وقت الترجمة ، ولكن هذه مشكلة أخرى) ، ولكن المتطلبات النحوية ، في هذه الحالة ، هو متطلب نحوي مختلف عن الذي ذكرته. يتطلب unsafe.Offsetof وسيطة واحدة ، بينما يتطلب try تعبيرًا يبدو ، في كل سياق آخر ، مثل القيمة التي يتم إرجاعها من دالة (مثل try(os.Open("/dev/stdout")) ) ويمكن افتراضها بأمان في كل سياق آخر لإرجاع قيمة واحدة فقط (إلا إذا كان التعبير يشبه try(os.Open("/dev/stdout")...) ).

nathanjsweet كتب:

لاحظ أيضًا أن مستند التصميم واضح جدًا بشأن هذا الأمر.

هل يمكنك أن تشير إلى هذا. لقد فوجئت بقراءة هذا.

يوجد في قسم "الاستنتاجات" من الاقتراح:

في Go ، تعتبر العناصر المدمجة هي آلية الهروب اللغوي المختارة للعمليات غير المنتظمة بطريقة ما ولكنها لا تبرر بناء جملة خاصًا.

أنا مندهش لأنك فاتتك ذلك ؛-)

ngrilly لا أعني في هذا الاقتراح ، أعني في مواصفات لغة go. كان لدي انطباع بأن griesemer كان يقول أن استدعاءات مواصفة لغة go المدمجة تعمل كآلية مفيدة على وجه التحديد لكسر الاصطلاح النحوي.

تضمين التغريدة

غير بديهي من بعض النواحي. هذه هي الوظيفة الأولى التي تغير منطق تدفق التحكم بطريقة لا تؤدي إلى فك المكدس (مثل الذعر ونظام التشغيل الخروج)

لا أعتقد أن os.Exit يزيل المكدس بأي معنى مفيد. يقوم بإنهاء البرنامج على الفور دون تشغيل أي وظائف مؤجلة. يبدو لي أن os.Exit هو الشيء الغريب الموجود هنا ، حيث يقوم كل من panic و try بتشغيل وظائف مؤجلة والانتقال إلى أعلى المكدس.

أوافق على أن os.Exit هو الشخص الغريب ، ولكن يجب أن يكون على هذا النحو. os.Exit توقف جميع goroutines ؛ لن يكون من المنطقي تشغيل الوظائف المؤجلة فقط من goroutine الذي يستدعي os.Exit . يجب إما تشغيل جميع الوظائف المؤجلة ، أو لا شيء. ومن الأسهل بكثير عدم تشغيل أي شيء.

تم تنفيذ tryhard على قاعدة الكود لدينا وهذا ما حصلنا عليه:

--- stats ---
  15298 (100.0% of   15298) func declarations
   3026 ( 19.8% of   15298) func declarations returning an error
  33941 (100.0% of   33941) statements
   7765 ( 22.9% of   33941) if statements
   3747 ( 48.3% of    7765) if <err> != nil statements
    131 (  3.5% of    3747) <err> name is different from "err"
   1847 ( 49.3% of    3747) return ..., <err> blocks in if <err> != nil statements
   1900 ( 50.7% of    3747) complex error handler in if <err> != nil statements; cannot use try
     19 (  0.5% of    3747) non-empty else blocks in if <err> != nil statements; cannot use try
   1789 ( 47.7% of    3747) try candidates

أولاً ، أريد أن أوضح أنه نظرًا لأن Go (قبل 1.13) يفتقر إلى السياق في الأخطاء ، فقد طبقنا نوع الخطأ الخاص بنا الذي ينفذ واجهة error ، تم الإعلان عن بعض الوظائف كإرجاع foo.Error بدلاً من error ، ويبدو أن هذا المحلل لم يلحظ ذلك ، لذا فإن هذه النتائج ليست "عادلة".

كنت في معسكر "نعم! لنفعل هذا" ، وأعتقد أنها ستكون تجربة ممتعة لـ 1.13 أو 1.14 بيتا ، لكني قلق من _ " 47.7٪ ... جرب المرشحين" _. هذا يعني الآن أن هناك طريقتان للقيام بالأشياء ، والتي لا أحبها. ومع ذلك ، هناك أيضًا طريقتان لإنشاء مؤشر ( new(Foo) مقابل &Foo{} ) بالإضافة إلى طريقتين لإنشاء شريحة أو خريطة باستخدام make([]Foo) و []Foo{} .

أنا الآن في معسكر "لنجرب هذا": ^) ونرى ما يفكر فيه المجتمع. ربما سنغير أنماط الترميز لدينا لتصبح كسولة ونتوقف عن إضافة سياق ، ولكن ربما لا بأس بذلك إذا حصلت الأخطاء على سياق أفضل من التضمين xerrors الذي يأتي على أي حال.

شكرًا ، Goodwine على تقديم المزيد من البيانات الملموسة!

(جانبا ، قمت بإجراء تغيير طفيف على tryhard الليلة الماضية لذلك يقسم "معالج الأخطاء المعقدة" إلى عددين: المعالجات المعقدة ، وإرجاع النموذج return ..., expr حيث كان الأخير قيمة النتيجة ليست <err> . يجب أن يوفر هذا بعض البصيرة الإضافية.)

ماذا عن تعديل الاقتراح ليكون متغيرًا بدلاً من حجة التعبير الغريبة هذه؟

هذا من شأنه أن يحل الكثير من المشاكل. في حالة رغبة الأشخاص في إرجاع الخطأ فقط ، فإن الشيء الوحيد الذي قد يتغير هو المتغير الصريح ... . على سبيل المثال:

try(os.Open("/dev/stdout")...)

ومع ذلك ، يمكن للأشخاص الذين يريدون موقفًا أكثر مرونة أن يفعلوا شيئًا مثل:

f, err := os.Open("/dev/stdout")
try(WrapErrorf(err, "whatever wrap does: %v"))

شيء واحد تقوم به هذه الفكرة هو جعل كلمة try أقل ملاءمة ، لكنها تحافظ على التوافق مع الإصدارات السابقة.

nathanjsweet كتب:

لا أقصد في هذا الاقتراح ، أعني في مواصفات لغة go.

فيما يلي المقتطفات التي كنت تبحث عنها في مواصفات اللغة:

في قسم "عبارات التعبير":

الوظائف المضمنة التالية غير مسموح بها في سياق البيان: append cap complex imag len make new real unsafe.Alignof unsafe.Offsetof unsafe.Sizeof

في قسمي "بيانات التشغيل" و "تأجيل البيانات":

يتم تقييد استدعاءات الوظائف المضمنة مثل عبارات التعبير.

في قسم "الوظائف المضمنة":

لا تحتوي الوظائف المضمنة على أنواع Go القياسية ، لذلك يمكن أن تظهر فقط في تعبيرات الاتصال ؛ لا يمكن استخدامها كقيم وظيفية.

nathanjsweet كتب:

كان لدي انطباع بأن griesemer كان يقول أن استدعاءات مواصفة لغة go المدمجة تعمل كآلية مفيدة على وجه التحديد لكسر الاصطلاح النحوي .

لا تكسر الدالات المضمنة اصطلاحات Go النحوية (الأقواس ، والفواصل بين الوسيطات ، وما إلى ذلك). يستخدمون نفس بناء الجملة مثل الوظائف المعرفة من قبل المستخدم ، لكنهم يسمحون بأشياء لا يمكن القيام بها في الوظائف المعرفة من قبل المستخدم.

nathanjsweet تم اعتبار ذلك بالفعل (في الواقع كان سهوًا) ولكنه يجعل try غير قابل للتوسيع. راجع https://go-review.googlesource.com/c/proposal/+/181878 .

بشكل عام ، أعتقد أنك تركز نقدك على الشيء الخطأ: القواعد الخاصة للحجة try هي في الحقيقة ليست مشكلة - فعليًا لكل عنصر مضمّن قواعد خاصة.

شكرًا griesemer على العمل على هذا الأمر وأخذ الوقت للرد على مخاوف المجتمع. أنا متأكد من أنك قد أجبت على الكثير من نفس الأسئلة في هذه المرحلة. أدرك أنه من الصعب حقًا حل هذه المشكلات والحفاظ على التوافق مع الإصدارات السابقة في نفس الوقت. شكرا!

nathanjsweet بخصوص تعليقك هنا :

راجع قسم الاستنتاج الذي يتحدث بشكل بارز عن دور العناصر المضمنة في Go.

فيما يتعلق بتعليقاتك على try تمديد العناصر المضمنة بطرق مختلفة: نعم ، المطلب الذي يضعه unsafe.Offsetof في وسيطته مختلف عن المتطلب $ try . لكن كلاهما يتوقع تعبيرًا نحويًا. كلاهما له بعض القيود الإضافية على هذا التعبير. يتناسب شرط try بسهولة مع بنية Go بحيث لا تحتاج إلى تعديل أي من أدوات تحليل الواجهة الأمامية. أفهم أنني أشعر بأنني غير معتاد بالنسبة لك ، لكن هذا ليس هو نفسه سبب تقني ضده.

يحسبgriesemer أحدث _tryhard_ "معالجات الأخطاء المعقدة" ولكن ليس "معالجات الأخطاء أحادية العبارة". هل يمكن أن يضاف ذلك؟

networkimprov ما هو معالج الخطأ أحادي العبارة؟ كتلة if تحتوي على بيان واحد بعدم الإرجاع؟

griesemer ، معالج أخطاء العبارة الواحدة عبارة عن كتلة if err != nil تحتوي على _any_ عبارة واحدة ، بما في ذلك الإرجاع.

تضمين التغريدة يتم الآن تقسيم "معالجات معقدة" إلى "بيان واحد ثم فرع" و "معقد ثم فرع".

ومع ذلك ، لاحظ أن هذه الأعداد قد تكون مضللة: على سبيل المثال ، تتضمن هذه الأعداد أي عبارة if تتحقق من أي متغير مقابل لا شيء (إذا كان -err="" وهو الآن الإعداد الافتراضي لـ tryhard ). يجب أن أصلح هذا. باختصار ، كما هو الحال مع tryhard يبالغ في تقدير عدد فرص معالج البيانات المعقدة أو المفردة بالكثير. على سبيل المثال ، راجع archive/tar/common.go ، السطر 701.

يوفرnetworkimprov tryhard الآن تعدادات أكثر دقة حول سبب عدم كون التحقق من الخطأ مرشحًا لـ try . لم يتغير العدد الإجمالي لـ try ، لكن عدد الفرص لمزيد من المعالجات الفردية والمعقدة أصبح الآن أكثر دقة (وأقل بنسبة 50٪ تقريبًا مما كان عليه من قبل ، لأنه قبل أي معقد then تم اعتبار فرع لكشف حساب if طالما أن if يحتوي على شيك <varname> != nil ، سواء كان يتضمن فحصًا للأخطاء أم لا).

إذا أراد أي شخص تجربة try بطريقة عملية أكثر بقليل ، فقد قمت بإنشاء ملعب WASM هنا مع تنفيذ نموذج أولي:

https://ccbrown.github.io/wasm-go-playground/experimental/try-builtin/

وإذا كان أي شخص مهتمًا بالفعل بتجميع الكود محليًا مع المحاولة ، فلدي Go fork مع ما أعتقد أنه تنفيذ كامل الوظائف / محدث هنا: https://github.com/ccbrown/go/pull/1

أنا أحب "حاول". أجد إدارة الحالة المحلية للخطأ ، واستخدام: = مقابل = مع الخطأ ، جنبًا إلى جنب مع الواردات المرتبطة ، ليكون بمثابة إلهاء منتظم. أيضًا ، لا أرى هذا على أنه إنشاء طريقتين لفعل الشيء نفسه ، مثل حالتين ، واحدة حيث تريد تمرير خطأ دون التصرف بناءً عليه ، والأخرى حيث تريد صراحة التعامل معها في وظيفة الاستدعاء على سبيل المثال تسجيل.

قمت بتشغيل tryhard مقابل مشروع داخلي صغير عملت عليه منذ أكثر من عام. يحتوي الدليل المعني على رمز لثلاثة خوادم (أفترض "الخدمات المصغرة") ، وزاحف يعمل بشكل دوري كوظيفة cron ، وعدد قليل من أدوات سطر الأوامر. كما أن لديها اختبارات وحدة شاملة إلى حد ما. (FWIW ، تم تشغيل القطع المختلفة بسلاسة لأكثر من عام ، وقد ثبت أنه من السهل تصحيح الأخطاء وحل أي مشكلات قد تنشأ)

ها هي الإحصائيات:

--- stats ---
    370 (100.0% of     370) func declarations
    115 ( 31.1% of     370) func declarations returning an error
   1159 (100.0% of    1159) statements
    258 ( 22.3% of    1159) if statements
    123 ( 47.7% of     258) if <err> != nil statements
     64 ( 52.0% of     123) try candidates
      0 (  0.0% of     123) <err> name is different from "err"
--- non-try candidates (-l flag lists file positions) ---
     54 ( 43.9% of     123) { return ... zero values ..., expr }
      2 (  1.6% of     123) single statement then branch
      3 (  2.4% of     123) complex then branch; cannot use try
      1 (  0.8% of     123) non-empty else branch; cannot use try

بعض التعليقات:
1) 50٪ من جميع كشوفات الحساب if في قاعدة الكود هذه تقوم بفحص الأخطاء ، ويمكن أن يحل try محل نصف هذه العبارات. هذا يعني أن ربع جميع كشوفات الحساب if في قاعدة الكود هذه (الصغيرة) هي نسخة مطبوعة من try .

2) يجب أن أشير إلى أن هذا مرتفع بشكل مفاجئ بالنسبة لي ، لأنه قبل أسابيع قليلة من البدء في هذا المشروع ، قرأت عن عائلة من وظائف المساعد الداخلي ( status.Annotate ) التي تعلق على رسالة خطأ ولكنها تحافظ على رمز حالة gRPC. على سبيل المثال ، إذا قمت باستدعاء RPC وقام بإرجاع خطأ برمز الحالة المرتبط بـ PERMISSION_DENIED ، فسيظل الخطأ الذي تم إرجاعه من وظيفة المساعد هذه يحتوي على رمز حالة مرتبط بـ PERMISSION_DENIED (ونظريًا ، إذا تم نشر رمز الحالة المرتبط هذا ، وصولاً إلى معالج RPC ، فسيفشل RPC مع رمز الحالة المرتبط). كنت قد عقدت العزم على استخدام هذه الوظائف لكل شيء في هذا المشروع الجديد. لكن على ما يبدو ، بالنسبة لـ 50٪ من جميع الأخطاء ، قمت ببساطة بنشر خطأ دون توضيح. (قبل تشغيل tryhard ، كنت أتوقع 10٪).

3) يحدث status.Annotate للحفاظ على أخطاء nil (على سبيل المثال ، سيعود $ # status.Annotatef(err, "some message: %v", x) nil iff err == nil ). لقد بحثت في جميع المرشحين الذين لا يجربون من الفئة الأولى ، ويبدو أن الجميع سيكونون قابلين لإعادة الكتابة التالية:

```
// Before
enc, err := keymaster.NewEncrypter(encKeyring)                                                     
if err != nil {                                                                                    
  return status.Annotate(err, "failed to create encrypter")                                        
}

// After
enc, err := keymaster.NewEncrypter(encKeyring)                                                                                                                                                                  
try(status.Annotate(err, "failed to create encrypter"))
```

To be clear, I'm not saying this transformation is always necessarily a good idea, but it seemed worth mentioning since it boosts the count significantly to a bit under half of all `if` statements.

4) يبدو التعليق التوضيحي للخطأ المستند إلى defer متعامدًا إلى حد ما مع try ، لأكون صريحًا ، لأنه سيعمل مع وبدون try . ولكن أثناء البحث في الكود الخاص بهذا المشروع ، نظرًا لأنني كنت أبحث عن كثب في معالجة الأخطاء ، فقد لاحظت العديد من الحالات التي تكون فيها الأخطاء الناتجة عن الاستدعاء أكثر منطقية. على سبيل المثال ، لاحظت عدة حالات من التعليمات البرمجية التي تستدعي عملاء gRPC مثل هذا:

```
resp, err := s.someClient.SomeMethod(ctx, req)
if err != nil {
  return ..., status.Annotate(err, "failed to call SomeMethod")
}
```

This is actually a bit redundant in retrospect, since gRPC already prefixes its errors with something like "/Service.Method to [ip]:port : ".

There was also code that called standard library functions using the same pattern:

```
hreq, err := http.NewRequest("GET", targetURL, nil)
if err != nil {
  return status.Annotate(err, "http.NewRequest failed")
}
```

In retrospect, this code demonstrates two issues: first, `http.NewRequest` isn't calling a gRPC API, so using `status.Annotate` was unnecessary, and second, assuming the standard library also return errors with callee context, this particular use of error annotation was unnecessary (although I am fairly certain the standard library does not consistently follow this pattern).

على أي حال ، اعتقدت أنه كان تمرينًا مثيرًا للاهتمام أن أعود إلى هذا المشروع وأن ننظر بعناية في كيفية تعامله مع الأخطاء.

شيء واحد ، griesemer : هل يحتوي tryhard على المقام الصحيح لـ "غير المرشحين الذين لا يجربون"؟
تحرير: أجبت أدناه ، لقد أخطأت في قراءة الإحصائيات.

تحرير: ما كان من المفترض أن يكون رد فعل تم تحويله إلى اقتراح ، وقد طُلب منا صراحة عدم القيام به هنا. لقد نقلت تعليقي إلى جوهر .

balasanjay شكرًا لتعليقك القائم على الحقائق ؛ هذا مفيد جدا.

بخصوص سؤالك حول tryhard : "غير المرشحين" (مرحبًا باقتراح العنوان الأفضل) هي ببساطة عدد الحالات التي استوفت فيها عبارة if جميع معايير "التحقق من الخطأ" (أي ، كان لدينا ما يشبه تعيينًا لمتغير خطأ <err> ، متبوعًا بفحص if <err> != nil في المصدر) ، ولكن حيث لا يمكننا بسهولة استخدام try بسبب الشفرة الموجودة في كتل if . على وجه التحديد ، بترتيب الظهور في إخراج "غير المرشحين الذين لا يجربون" ، هذه كشوفات if التي تحتوي على كشف return يعرض شيئًا آخر غير <err> في النهاية ، كشوفات حساب if مع كشف حساب واحد أكثر تعقيدًا return (أو غير ذلك) ، و if كشوفات حساب مع عدة كشوفات في الفرع "then" ، و if كشوفات الحساب مع غير فارغ فرع else . قد تحتوي بعض هذه العبارات if على العديد من هذه الشروط راضية في وقت واحد ، لذلك لا تُجمع هذه الأرقام فقط. تهدف إلى إعطاء فكرة عن الخطأ الذي حدث لـ try لتكون قابلة للاستخدام.

لقد أجريت أحدث التعديلات على هذا اليوم (قمت بتشغيل أحدث إصدار). قبل التغيير الأخير ، تم احتساب بعض هذه الشروط حتى لو لم يكن هناك فحص خطأ متضمن ، والذي بدا أنه أقل منطقية لأنه بدا وكأنه try لا يمكن استخدامه في العديد من الحالات بينما في الواقع try لم يكن له معنى في تلك الحالات في المقام الأول.

الأهم من ذلك ، بالنسبة إلى قاعدة بيانات معينة ، أن العدد الإجمالي لمرشحين try لم يتغير مع هذه التحسينات ، نظرًا لأن الشروط ذات الصلة لـ try ظلت كما هي.

إذا كان لديك اقتراح أفضل حول كيفية و / أو ما يجب قياسه ، فسأكون سعيدًا لسماع ذلك. لقد أجريت العديد من التعديلات بناءً على تعليقات المجتمع. شكرا.

subfuzion نشكرك على تعليقك ، لكننا لا نبحث عن مقترحات بديلة. يرجى مراجعة https://github.com/golang/go/issues/32437#issuecomment -501878888. شكرا.

من أجل أن يتم احتسابهم ، بغض النظر عن النتيجة:

أنا من وجهة نظري ، جنبًا إلى جنب مع فريقي ، أنه في حين أن إطار العمل try كما اقترحه Rob هو فكرة معقولة ومثيرة للاهتمام ، فإنه لا يصل إلى المستوى الذي يكون مناسبًا له باعتباره مدمجًا. ستكون حزمة المكتبة القياسية نهجًا أكثر ملاءمة حتى يتم إنشاء أنماط الاستخدام في الممارسة العملية. إذا جاء try إلى اللغة بهذه الطريقة ، فسنستخدمها في عدد من الأماكن المختلفة.

بشكل أكثر عمومية ، فإن تركيبة Go من لغة أساسية مستقرة جدًا ومكتبة قياسية غنية جدًا تستحق الحفاظ عليها. كلما كان فريق اللغة أبطأ في تغيير اللغة الأساسية ، كان ذلك أفضل. يظل خط الأنابيب x -> stdlib نهجًا قويًا لهذا النوع من الأشياء.

griesemer آه ، آسف. لقد أخطأت في قراءة الإحصائيات ، فهي تستخدم عداد "إذا أخطأت! = لا شيء" (123) كمقام ، وليس عداد "تجربة المرشحين" (64) كمقام. سأضرب هذا السؤال.

شكرا!

mattpalmer لقد أثبتت أنماط استخدامmattpalmer وجودها لمدة عقد تقريبًا. إن أنماط الاستخدام الدقيقة هذه هي التي أثرت بشكل مباشر على تصميم try . ما أنماط الاستخدام التي تشير إليها؟

griesemer آسف ، هذا خطأي - ما بدأ في ذهني كشرح ما أزعجني حول try تم تحويله إلى اقتراحه الخاص لتوضيح وجهة نظري حول عدم إضافته. كان ذلك مخالفًا بوضوح للقواعد الأساسية المذكورة (ناهيك عن أنه بخلاف هذا الاقتراح لوظيفة مدمجة جديدة ، فإنه يقدم مشغلًا جديدًا). هل سيكون من المفيد حذف التعليق للحفاظ على انسيابية المحادثة (أم أن هذا يعتبر شكلاً سيئًا)؟

subfuzion لا تقلق بشأن ذلك. إنه اقتراح مثير للجدل وهناك الكثير من المقترحات. كثير منها غريب

لقد كررنا هذا التصميم عدة مرات وطلبنا التعليقات من العديد من الأشخاص قبل أن نشعر بالراحة الكافية لنشره والتوصية بتطويره إلى مرحلة التجربة الفعلية ، لكننا لم نقم بالتجربة بعد. من المنطقي العودة إلى لوحة الرسم إذا فشلت التجربة ، أو إذا أخبرتنا التعليقات مسبقًا أنها ستفشل بشكل واضح.

griesemer ، هل يمكنك توضيح المقاييس المحددة التي سيستخدمها الفريق لإثبات نجاح التجربة أو فشلها؟

@انا و

لقد سألت هذا عن rsc منذ فترة (https://github.com/golang/go/issues/32437#issuecomment-503245958):

rsc
لن يكون هناك نقص في المواقع حيث يمكن توفير هذه الراحة. ما هو المقياس المطلوب لإثبات جوهر الآلية بخلاف ذلك؟ هل توجد قائمة بحالات معالجة الأخطاء المصنفة؟ كيف ستُشتق القيمة من البيانات عندما تكون المشاعر مدفوعة بمعظم العملية العامة؟

كانت الإجابة مقصودة ، لكنها غير ملهمة وتفتقر إلى الجوهر (https://github.com/golang/go/issues/32437#issuecomment-503295558):

يعتمد القرار على مدى نجاح هذا في البرامج الحقيقية. إذا أظهر لنا الأشخاص أن المحاولة غير فعالة في الجزء الأكبر من التعليمات البرمجية الخاصة بهم ، فهذه بيانات مهمة. العملية مدفوعة بهذا النوع من البيانات. إنها ليست مدفوعة بالمشاعر.

تم تقديم مشاعر إضافية (https://github.com/golang/go/issues/32437#issuecomment-503408184):

لقد فوجئت بإيجاد حالة أدى فيها try إلى كود أفضل بشكل واضح ، بطريقة لم تتم مناقشتها من قبل.

في النهاية ، أجبت على سؤالي "هل توجد قائمة بحالات معالجة الأخطاء السرية؟". سيكون هناك بشكل فعال 6 طرق لمعالجة الأخطاء - دليل مباشر ، تمرير يدوي يدوي ، يدوي غير مباشر ، تلقائي مباشر ، تمرير تلقائي ، تلقائي غير مباشر. حاليًا ، من الشائع استخدام وضعين فقط من هذه الأوضاع. تبدو الأنماط غير المباشرة ، التي لديها قدر كبير من الجهد المبذول في تسهيلها ، مانعة بشدة لمعظم Gophers المخضرمين ويبدو أن هذا القلق يتم تجاهله. (https://github.com/golang/go/issues/32437#issuecomment-507332843).

علاوة على ذلك ، اقترحت فحص التحويلات الآلية قبل التحويل لمحاولة ضمان قيمة النتائج (https://github.com/golang/go/issues/32437#issuecomment-507497656). مع مرور الوقت ، لحسن الحظ ، يبدو أن المزيد من النتائج المعروضة لديها استعادات أفضل ، لكن هذا لا يزال لا يعالج تأثير الأساليب غير المباشرة بطريقة رصينة ومنسقة. بعد كل شيء (في رأيي) ، تمامًا كما يجب معاملة المستخدمين على أنهم معادون ، يجب معاملة المطورين على أنهم كسالى.

تمت الإشارة أيضًا إلى فشل النهج الحالي في فقدان المرشحين القيمين (https://github.com/golang/go/issues/32437#issuecomment-507505243).

أعتقد أن الأمر يستحق أن تكون صاخبًا بشأن هذه العملية التي تفتقر عمومًا والصم بشكل ملحوظ.

iand الإجابة التي قدمها rsc لا تزال صالحة. لست متأكدًا من أي جزء من هذه الإجابة "يفتقر إلى الجوهر" أو ما يلزم ليكون "ملهمًا". لكن دعني أحاول إضافة المزيد من "الجوهر":

الغرض من عملية تقييم العرض هو تحديد "ما إذا كان التغيير قد حقق الفوائد المتوقعة أو خلق أي تكاليف غير متوقعة" (الخطوة 5 في العملية).

لقد اجتزنا الخطوة 1: اختار فريق Go مقترحات محددة يبدو أنها تستحق القبول ؛ هذا الاقتراح هو واحد منهم. لم نكن لنختاره إذا لم نفكر فيه بصعوبة كبيرة واعتبرناه مفيدًا. على وجه التحديد ، نعتقد أن هناك قدرًا كبيرًا من النماذج المعيارية في كود Go يتعلق فقط بمعالجة الأخطاء. الاقتراح أيضًا لا يأتي من فراغ - لقد ناقشنا هذا لأكثر من عام في أشكال مختلفة.

نحن حاليًا في الخطوة 2 ، وبالتالي ما زلنا بعيدين بعض الشيء عن القرار النهائي. الخطوة الثانية هي جمع التعليقات والمخاوف - التي يبدو أن هناك الكثير منها. ولكن لنكون واضحين هنا: حتى الآن لم يكن هناك سوى تعليق واحد يشير إلى نقص فني في التصميم ، والذي قمنا بتصحيحه . كان هناك أيضًا عدد غير قليل من التعليقات التي تحتوي على بيانات محددة تستند إلى رمز حقيقي يشير إلى أن try سيقلل بالفعل من الصيغة المعيارية ويبسط الكود ؛ وكانت هناك بعض التعليقات - التي تستند أيضًا إلى بيانات حول الكود الحقيقي - والتي أظهرت أن try لن يساعد كثيرًا. هذه الملاحظات الملموسة ، المستندة إلى البيانات الفعلية ، أو الإشارة إلى أوجه القصور الفنية ، قابلة للتنفيذ ومفيدة للغاية. بالتأكيد سوف نأخذ هذا في الاعتبار.

ثم كان هناك الكم الهائل من التعليقات التي هي في الأساس مشاعر شخصية. هذا أقل قابلية للتنفيذ. هذا لا يعني أننا نتجاهل ذلك. لكن مجرد التزامنا بالعملية لا يعني أننا "صماء".

فيما يتعلق بهذه التعليقات: ربما يوجد اثنان ، وربما ثلاثة عشر معارضًا صريحًا لهذا الاقتراح - أنت تعرف من أنت. إنهم يسيطرون على هذه المناقشة من خلال مشاركات متكررة ، وأحيانًا متعددة في اليوم. هناك القليل من المعلومات الجديدة التي يمكن اكتسابها من هذا. كما أن العدد المتزايد من المشاركات لا يعكس شعورًا "أقوى" من قبل المجتمع ؛ هذا يعني فقط أن هؤلاء الأشخاص أكثر صوتًا من غيرهم.

iand الإجابة التي قدمها rsc لا تزال صالحة. لست متأكدًا من أي جزء من هذه الإجابة "يفتقر إلى الجوهر" أو ما يلزم ليكون "ملهمًا". لكن دعني أحاول إضافة المزيد من "الجوهر":

griesemer ، أنا متأكد من أنه لم يكن مقصودًا ، لكني أود أن أشير إلى أنه لم تكن أي من الكلمات التي نقلتها هي كلماتي ، بل كان أحد المعلقين اللاحقين.

بصرف النظر عن ذلك ، آمل أنه بالإضافة إلى تقليل الصيغة المعيارية وتبسيط نجاح try سيتم الحكم على ما إذا كان يسمح لنا بكتابة كود أفضل وأوضح.

iand في الواقع - كان ذلك مجرد سهو مني. اعتذاري.

نعتقد أن try يسمح لنا بكتابة كود أكثر قابلية للقراءة - والكثير من الأدلة التي تلقيناها من الكود الحقيقي وتجاربنا الخاصة مع tryhard تظهر عمليات تنظيف مهمة. لكن المقروئية أكثر ذاتية ويصعب تحديدها.

تضمين التغريدة

ما أنماط الاستخدام التي تشير إليها؟

أنا أشير إلى أنماط الاستخدام التي ستتطور حول try بمرور الوقت ، وليس نمط عدم التحقق الحالي لمعالجة الأخطاء. إن احتمال إساءة الاستخدام وإساءة الاستخدام أمر غير معروف ، خاصة مع التدفق المستمر للمبرمجين الذين استخدموا إصدارات مختلفة لغويًا من try-catch بلغات أخرى.

كل هذا والاعتبارات المتعلقة بالاستقرار طويل المدى للغة الأساسية تقودني إلى الاعتقاد بأن تقديم هذه الميزة على مستوى حزم x أو المكتبة القياسية (إما كحزمة errors/try أو كـ errors.Try() ) من الأفضل تقديمه على أنه مدمج.

mattparlmer صححني إذا كنت مخطئًا ، لكنني أعتقد أن هذا الاقتراح يجب أن يكون في وقت تشغيل Go من أجل استخدام g ، m's (ضروري لتجاوز تدفق التنفيذ).

@ فابيان و

mattparlmer صححني إذا كنت مخطئًا ، لكنني أعتقد أن هذا الاقتراح يجب أن يكون في وقت تشغيل Go من أجل استخدام g ، m's (ضروري لتجاوز تدفق التنفيذ).

هذا ليس هو الحال؛ كما يلاحظ مستند التصميم ، يمكن تنفيذه كتحويل شجرة بناء جملة وقت التجميع.

هذا ممكن لأن دلالات try يمكن التعبير عنها بالكامل من حيث if و return ؛ لا "يتجاوز تدفق التنفيذ" أكثر من if و return .

إليك تقرير tryhard من قاعدة كود Go من 300 ألف سطر لشركتي:

التشغيل الأولي:

--- stats ---
  13879 (100.0% of   13879) func declarations
   4381 ( 31.6% of   13879) func declarations returning an error
  38435 (100.0% of   38435) statements
   8028 ( 20.9% of   38435) if statements
   4496 ( 56.0% of    8028) if <err> != nil statements
    453 ( 10.1% of    4496) try candidates
      4 (  0.1% of    4496) <err> name is different from "err"
--- non-try candidates (-l flag lists file positions) ---
   3066 ( 68.2% of    4496) { return ... zero values ..., expr }
    356 (  7.9% of    4496) single statement then branch
    345 (  7.7% of    4496) complex then branch; cannot use try
     63 (  1.4% of    4496) non-empty else branch; cannot use try

لدينا اتفاقية لاستخدام حزمة أخطاء juju (https://godoc.org/github.com/juju/errgo) لإخفاء الأخطاء وإضافة معلومات تتبع المكدس إليها ، مما سيمنع حدوث معظم عمليات إعادة الكتابة. هذا يعني أنه من غير المحتمل أن نتبنى try لنفس السبب الذي يجعلنا نتجنب بشكل عام إرجاع الخطأ العاري.

نظرًا لأنه يبدو أنه مقياس مفيد ، فقد أزلت مكالمات errgo.Mask() (التي تعرض الخطأ بدون تعليق توضيحي) وأعدت تشغيل tryhard . هذا تقدير لعدد عمليات التحقق من الأخطاء التي يمكن إعادة كتابتها إذا لم نستخدم errgo:

--- stats ---
  13879 (100.0% of   13879) func declarations
   4381 ( 31.6% of   13879) func declarations returning an error
  38435 (100.0% of   38435) statements
   8028 ( 20.9% of   38435) if statements
   4496 ( 56.0% of    8028) if <err> != nil statements
   3114 ( 69.3% of    4496) try candidates
      7 (  0.2% of    4496) <err> name is different from "err"
--- non-try candidates (-l flag lists file positions) ---
    381 (  8.5% of    4496) { return ... zero values ..., expr }
    358 (  8.0% of    4496) single statement then branch
    345 (  7.7% of    4496) complex then branch; cannot use try
     63 (  1.4% of    4496) non-empty else branch; cannot use try

لذا ، أعتقد أن 70٪ تقريبًا من عمليات إرجاع الخطأ ستكون متوافقة مع try .

أخيرًا ، لا يبدو أن اهتمامي الأساسي بالاقتراح موجود في أي من التعليقات التي قرأتها ولا في ملخصات المناقشة:

يزيد هذا الاقتراح بشكل كبير من التكلفة النسبية لأخطاء التعليقات التوضيحية.

في الوقت الحالي ، تعتبر التكلفة الحدية لإضافة بعض السياق إلى الخطأ منخفضة للغاية ؛ إنها بالكاد أكثر من كتابة سلسلة التنسيق. إذا تم اعتماد هذا الاقتراح ، فإنني أشعر بالقلق من أن المهندسين سيفضلون بشكل متزايد الشكل الجمالي الذي يقدمه try ، لأن كلاهما يجعل شفرتهم "تبدو أكثر أناقة" (وهو أمر يحزنني أن أقول إنه اعتبار لبعض الأشخاص ، في تجربتي) ، ويتطلب الآن كتلة إضافية لإضافة سياق. يمكنهم تبريرها بناءً على حجة "قابلية القراءة" ، وكيف أن إضافة سياق يوسع الطريقة بثلاثة أسطر أخرى ويشتت انتباه القارئ عن النقطة الرئيسية. أعتقد أن قواعد كود الشركة تختلف عن مكتبة Go القياسية ، بمعنى أن تسهيل القيام بالشيء الصحيح له تأثير قابل للقياس على جودة الكود الناتج ، ومراجعات الكود متفاوتة الجودة ، وممارسات الفريق تختلف بشكل مستقل عن بعضها البعض . على أي حال ، كما قلت من قبل ، لا يمكننا دائمًا اعتماد try لقاعدة الرموز الخاصة بنا.

شكرا للنظر

تضمين التغريدة

كل هذا والاعتبارات المتعلقة بالاستقرار طويل المدى للغة الأساسية تقودني إلى الاعتقاد بأن تقديم هذه الميزة على مستوى حزم x أو المكتبة القياسية (إما كحزمة errors/try أو كـ errors.Try() ) من الأفضل تقديمه على أنه مدمج.

لا يمكن تنفيذ try كوظيفة مكتبة ؛ لا توجد طريقة لإرجاع الدالة من المتصل الخاص بها (تمكين ذلك تم اقتراحه كـ # 32473) ، ومثل معظم العناصر المضمنة الأخرى ، لا توجد أيضًا طريقة للتعبير عن توقيع try in Go. حتى مع الأدوية الجنيسة ، من غير المرجح أن يصبح ذلك ممكنًا ؛ راجع الأسئلة الشائعة حول مستند التصميم ، بالقرب من النهاية.

أيضًا ، يتطلب تنفيذ try كوظيفة مكتبة أن يكون لها اسم أكثر تفصيلاً ، والذي يتعارض جزئيًا مع الهدف من استخدامه.

ومع ذلك ، يمكن تطبيقه - وقد تم تنفيذه مرتين - باعتباره معالجًا أوليًا للشفرة المصدر: راجع https://github.com/rhysd/trygo و https://github.com/lunixbochs/og.

يبدو أن حوالي 60٪ من قاعدة التعليمات البرمجية من tegola ستكون قادرة على الاستفادة من هذه الميزة.

إليك ناتج tryhard لمشروع tegola: (http://github.com/go-spatial/tegola)

--- try candidates ---
      1  tegola/atlas/atlas.go:84
      2  tegola/atlas/map.go:232
      3  tegola/atlas/map.go:238
      4  tegola/atlas/map.go:248
      5  tegola/atlas/map.go:253
      6  tegola/basic/geometry_math.go:248
      7  tegola/basic/geometry_math.go:251
      8  tegola/basic/geometry_math.go:268
      9  tegola/basic/geometry_math.go:276
     10  tegola/basic/json_marshal.go:33
     11  tegola/basic/json_marshal.go:153
     12  tegola/basic/json_marshal.go:276
     13  tegola/cache/azblob/azblob.go:54
     14  tegola/cache/azblob/azblob.go:61
     15  tegola/cache/azblob/azblob.go:67
     16  tegola/cache/azblob/azblob.go:74
     17  tegola/cache/azblob/azblob.go:80
     18  tegola/cache/azblob/azblob.go:105
     19  tegola/cache/azblob/azblob.go:109
     20  tegola/cache/azblob/azblob.go:204
     21  tegola/cache/azblob/azblob.go:259
     22  tegola/cache/file/file.go:42
     23  tegola/cache/file/file.go:56
     24  tegola/cache/file/file.go:110
     25  tegola/cache/file/file.go:116
     26  tegola/cache/file/file.go:129
     27  tegola/cache/redis/redis.go:41
     28  tegola/cache/redis/redis.go:46
     29  tegola/cache/redis/redis.go:51
     30  tegola/cache/redis/redis.go:56
     31  tegola/cache/redis/redis.go:70
     32  tegola/cache/redis/redis.go:79
     33  tegola/cache/redis/redis.go:84
     34  tegola/cache/s3/s3.go:85
     35  tegola/cache/s3/s3.go:102
     36  tegola/cache/s3/s3.go:112
     37  tegola/cache/s3/s3.go:118
     38  tegola/cache/s3/s3.go:123
     39  tegola/cache/s3/s3.go:138
     40  tegola/cache/s3/s3.go:164
     41  tegola/cache/s3/s3.go:172
     42  tegola/cache/s3/s3.go:179
     43  tegola/cache/s3/s3.go:284
     44  tegola/cache/s3/s3.go:340
     45  tegola/cmd/tegola/cmd/cache/format.go:97
     46  tegola/cmd/tegola/cmd/cache/seed_purge.go:94
     47  tegola/cmd/tegola/cmd/cache/seed_purge.go:103
     48  tegola/cmd/tegola/cmd/cache/seed_purge.go:170
     49  tegola/cmd/tegola/cmd/cache/tile_list.go:51
     50  tegola/cmd/tegola/cmd/cache/tile_list.go:64
     51  tegola/cmd/tegola/cmd/cache/tile_name.go:35
     52  tegola/cmd/tegola/cmd/cache/tile_name.go:43
     53  tegola/cmd/tegola/cmd/root.go:58
     54  tegola/cmd/tegola/cmd/root.go:61
     55  tegola/cmd/xyz2svg/cmd/draw.go:62
     56  tegola/cmd/xyz2svg/cmd/draw.go:70
     57  tegola/cmd/xyz2svg/cmd/draw.go:214
     58  tegola/config/config.go:96
     59  tegola/internal/env/parse.go:30
     60  tegola/internal/env/parse.go:69
     61  tegola/internal/env/parse.go:116
     62  tegola/internal/env/parse.go:174
     63  tegola/internal/env/parse.go:221
     64  tegola/internal/env/types.go:67
     65  tegola/internal/env/types.go:86
     66  tegola/internal/env/types.go:105
     67  tegola/internal/env/types.go:124
     68  tegola/internal/env/types.go:143
     69  tegola/maths/makevalid/main.go:189
     70  tegola/maths/makevalid/main.go:207
     71  tegola/maths/makevalid/main.go:221
     72  tegola/maths/makevalid/main.go:295
     73  tegola/maths/makevalid/main.go:504
     74  tegola/maths/makevalid/makevalid.go:77
     75  tegola/maths/makevalid/makevalid.go:89
     76  tegola/maths/makevalid/makevalid.go:118
     77  tegola/maths/makevalid/makevalid_test.go:93
     78  tegola/maths/makevalid/makevalid_test.go:163
     79  tegola/maths/makevalid/plyg/ring.go:518
     80  tegola/maths/triangle.go:1023
     81  tegola/mvt/layer.go:73
     82  tegola/mvt/layer.go:79
     83  tegola/mvt/vector_tile/vector_tile.pb.go:64
     84  tegola/provider/gpkg/gpkg.go:138
     85  tegola/provider/gpkg/gpkg.go:223
     86  tegola/provider/gpkg/gpkg_register.go:46
     87  tegola/provider/gpkg/gpkg_register.go:51
     88  tegola/provider/gpkg/gpkg_register.go:186
     89  tegola/provider/gpkg/gpkg_register.go:227
     90  tegola/provider/gpkg/gpkg_register.go:240
     91  tegola/provider/gpkg/gpkg_register.go:245
     92  tegola/provider/gpkg/gpkg_register.go:256
     93  tegola/provider/gpkg/gpkg_register.go:377
     94  tegola/provider/postgis/postgis.go:112
     95  tegola/provider/postgis/postgis.go:117
     96  tegola/provider/postgis/postgis.go:122
     97  tegola/provider/postgis/postgis.go:127
     98  tegola/provider/postgis/postgis.go:136
     99  tegola/provider/postgis/postgis.go:142
    100  tegola/provider/postgis/postgis.go:148
    101  tegola/provider/postgis/postgis.go:153
    102  tegola/provider/postgis/postgis.go:158
    103  tegola/provider/postgis/postgis.go:163
    104  tegola/provider/postgis/postgis.go:181
    105  tegola/provider/postgis/postgis.go:198
    106  tegola/provider/postgis/postgis.go:264
    107  tegola/provider/postgis/postgis.go:441
    108  tegola/provider/postgis/postgis.go:446
    109  tegola/provider/postgis/postgis.go:529
    110  tegola/provider/postgis/postgis.go:559
    111  tegola/provider/postgis/postgis.go:603
    112  tegola/provider/postgis/util.go:31
    113  tegola/provider/postgis/util.go:36
    114  tegola/provider/postgis/util.go:200
    115  tegola/server/bindata/bindata.go:89
    116  tegola/server/bindata/bindata.go:109
    117  tegola/server/bindata/bindata.go:129
    118  tegola/server/bindata/bindata.go:149
    119  tegola/server/bindata/bindata.go:169
    120  tegola/server/bindata/bindata.go:189
    121  tegola/server/bindata/bindata.go:209
    122  tegola/server/bindata/bindata.go:229
    123  tegola/server/bindata/bindata.go:370
    124  tegola/server/bindata/bindata.go:374
    125  tegola/server/bindata/bindata.go:378
    126  tegola/server/bindata/bindata.go:382
    127  tegola/server/bindata/bindata.go:386
    128  tegola/server/bindata/bindata.go:402
    129  tegola/server/middleware_gzip.go:71
    130  tegola/server/middleware_gzip.go:78
    131  tegola/server/server_test.go:85

--- <err> name is different from "err" ---
      1  tegola/basic/json_marshal.go:276

--- { return ... zero values ..., expr } ---
      1  tegola/basic/geometry_math.go:214
      2  tegola/basic/geometry_math.go:222
      3  tegola/basic/geometry_math.go:230
      4  tegola/cache/azblob/azblob.go:131
      5  tegola/cache/azblob/azblob.go:140
      6  tegola/cache/azblob/azblob.go:149
      7  tegola/cache/azblob/azblob.go:171
      8  tegola/cache/file/file.go:47
      9  tegola/cache/s3/s3.go:92
     10  tegola/cmd/internal/register/maps.go:108
     11  tegola/cmd/tegola/cmd/cache/flags.go:20
     12  tegola/cmd/tegola/cmd/cache/tile_name.go:51
     13  tegola/cmd/tegola/cmd/cache/worker.go:112
     14  tegola/cmd/tegola/cmd/cache/worker.go:123
     15  tegola/cmd/tegola/cmd/root.go:73
     16  tegola/cmd/tegola/cmd/root.go:78
     17  tegola/cmd/xyz2svg/cmd/root.go:60
     18  tegola/provider/gpkg/gpkg.go:90
     19  tegola/provider/gpkg/gpkg.go:95
     20  tegola/provider/gpkg/gpkg_register.go:264
     21  tegola/provider/gpkg/gpkg_register.go:297
     22  tegola/provider/gpkg/gpkg_register.go:302
     23  tegola/provider/gpkg/gpkg_register.go:313
     24  tegola/provider/gpkg/gpkg_register.go:328
     25  tegola/provider/postgis/postgis.go:193
     26  tegola/provider/postgis/postgis.go:208
     27  tegola/provider/postgis/postgis.go:222
     28  tegola/provider/postgis/postgis.go:228
     29  tegola/provider/postgis/postgis.go:234
     30  tegola/provider/postgis/postgis.go:243
     31  tegola/provider/postgis/postgis.go:249
     32  tegola/provider/postgis/postgis.go:255
     33  tegola/provider/postgis/postgis.go:304
     34  tegola/provider/postgis/postgis.go:315
     35  tegola/provider/postgis/postgis.go:319
     36  tegola/provider/postgis/postgis.go:364
     37  tegola/provider/postgis/postgis.go:456
     38  tegola/provider/postgis/postgis.go:520
     39  tegola/provider/postgis/postgis.go:534
     40  tegola/provider/postgis/postgis.go:565
     41  tegola/provider/postgis/util.go:108
     42  tegola/provider/postgis/util.go:113
     43  tegola/server/bindata/bindata.go:29
     44  tegola/server/bindata/bindata.go:245
     45  tegola/server/bindata/bindata.go:271
     46  tegola/server/bindata/bindata.go:396

--- single statement then branch ---
      1  tegola/cache/azblob/azblob.go:241
      2  tegola/cache/file/file.go:87
      3  tegola/cache/s3/s3.go:321
      4  tegola/cmd/internal/register/caches.go:18
      5  tegola/cmd/internal/register/providers.go:43
      6  tegola/cmd/internal/register/providers.go:62
      7  tegola/cmd/internal/register/providers.go:75
      8  tegola/config/config.go:192
      9  tegola/config/config.go:207
     10  tegola/config/config.go:217
     11  tegola/internal/env/dict.go:43
     12  tegola/internal/env/dict.go:121
     13  tegola/internal/env/dict.go:197
     14  tegola/internal/env/dict.go:273
     15  tegola/internal/env/dict.go:348
     16  tegola/internal/env/parse.go:79
     17  tegola/internal/env/parse.go:126
     18  tegola/internal/env/parse.go:184
     19  tegola/internal/env/parse.go:231
     20  tegola/maths/makevalid/plyg/ring.go:541
     21  tegola/maths/maths.go:239
     22  tegola/maths/validate/validate.go:49
     23  tegola/maths/validate/validate.go:53
     24  tegola/maths/validate/validate.go:59
     25  tegola/maths/validate/validate.go:69
     26  tegola/mvt/feature.go:94
     27  tegola/mvt/feature.go:99
     28  tegola/mvt/feature.go:592
     29  tegola/mvt/feature.go:603
     30  tegola/mvt/layer.go:90
     31  tegola/mvt/tile.go:48
     32  tegola/provider/postgis/postgis.go:570
     33  tegola/provider/postgis/postgis.go:586
     34  tegola/tile.go:172

--- complex then branch; cannot use try ---
      1  tegola/cache/azblob/azblob.go:226
      2  tegola/cache/file/file.go:78
      3  tegola/cache/file/file.go:122
      4  tegola/cache/s3/s3.go:195
      5  tegola/cache/s3/s3.go:206
      6  tegola/cache/s3/s3.go:219
      7  tegola/cache/s3/s3.go:307
      8  tegola/provider/gpkg/gpkg.go:39
      9  tegola/provider/gpkg/gpkg.go:45
     10  tegola/provider/gpkg/gpkg.go:131
     11  tegola/provider/gpkg/gpkg.go:154
     12  tegola/provider/gpkg/gpkg_register.go:171
     13  tegola/provider/gpkg/gpkg_register.go:195

--- stats ---
   1294 (100.0% of    1294) func declarations
    246 ( 19.0% of    1294) func declarations returning an error
   2693 (100.0% of    2693) statements
    551 ( 20.5% of    2693) if statements
    238 ( 43.2% of     551) if <err> != nil statements
    131 ( 55.0% of     238) try candidates
      1 (  0.4% of     238) <err> name is different from "err"
--- non-try candidates ---
     46 ( 19.3% of     238) { return ... zero values ..., expr }
     34 ( 14.3% of     238) single statement then branch
     13 (  5.5% of     238) complex then branch; cannot use try
      0 (  0.0% of     238) non-empty else branch; cannot use try

والمشروع المصاحب: (http://github.com/go-spatial/geom)

--- try candidates ---
      1  geom/bbox.go:202
      2  geom/encoding/geojson/geojson.go:152
      3  geom/encoding/geojson/geojson.go:157
      4  geom/encoding/wkb/internal/tcase/symbol/symbol.go:73
      5  geom/encoding/wkb/internal/tcase/tcase.go:161
      6  geom/encoding/wkb/internal/tcase/tcase.go:172
      7  geom/encoding/wkb/wkb.go:50
      8  geom/encoding/wkb/wkb.go:110
      9  geom/encoding/wkt/internal/token/token.go:176
     10  geom/encoding/wkt/internal/token/token.go:252
     11  geom/internal/parsing/parsing.go:44
     12  geom/internal/parsing/parsing.go:85
     13  geom/internal/rtreego/rtree_test.go:110
     14  geom/multi_line_string.go:34
     15  geom/multi_polygon.go:35
     16  geom/planar/clip/linestring.go:82
     17  geom/planar/clip/linestring.go:181
     18  geom/planar/clip/point.go:23
     19  geom/planar/intersect/xsweep.go:106
     20  geom/planar/makevalid/makevalid.go:92
     21  geom/planar/makevalid/makevalid.go:191
     22  geom/planar/makevalid/setdiff/polygoncleaner.go:283
     23  geom/planar/makevalid/setdiff/polygoncleaner.go:345
     24  geom/planar/makevalid/setdiff/polygoncleaner.go:543
     25  geom/planar/makevalid/setdiff/polygoncleaner.go:554
     26  geom/planar/makevalid/setdiff/polygoncleaner.go:572
     27  geom/planar/makevalid/setdiff/polygoncleaner.go:578
     28  geom/planar/simplify/douglaspeucker.go:84
     29  geom/planar/simplify/douglaspeucker.go:88
     30  geom/planar/simplify.go:13
     31  geom/planar/triangulate/constraineddelaunay/triangle.go:186
     32  geom/planar/triangulate/constraineddelaunay/triangulator.go:134
     33  geom/planar/triangulate/constraineddelaunay/triangulator.go:138
     34  geom/planar/triangulate/constraineddelaunay/triangulator.go:142
     35  geom/planar/triangulate/constraineddelaunay/triangulator.go:173
     36  geom/planar/triangulate/constraineddelaunay/triangulator.go:176
     37  geom/planar/triangulate/constraineddelaunay/triangulator.go:203
     38  geom/planar/triangulate/constraineddelaunay/triangulator.go:248
     39  geom/planar/triangulate/constraineddelaunay/triangulator.go:396
     40  geom/planar/triangulate/constraineddelaunay/triangulator.go:466
     41  geom/planar/triangulate/constraineddelaunay/triangulator.go:553
     42  geom/planar/triangulate/constraineddelaunay/triangulator.go:583
     43  geom/planar/triangulate/constraineddelaunay/triangulator.go:667
     44  geom/planar/triangulate/constraineddelaunay/triangulator.go:672
     45  geom/planar/triangulate/constraineddelaunay/triangulator.go:677
     46  geom/planar/triangulate/constraineddelaunay/triangulator.go:814
     47  geom/planar/triangulate/constraineddelaunay/triangulator.go:818
     48  geom/planar/triangulate/constraineddelaunay/triangulator.go:823
     49  geom/planar/triangulate/constraineddelaunay/triangulator.go:865
     50  geom/planar/triangulate/constraineddelaunay/triangulator.go:870
     51  geom/planar/triangulate/constraineddelaunay/triangulator.go:875
     52  geom/planar/triangulate/constraineddelaunay/triangulator.go:897
     53  geom/planar/triangulate/constraineddelaunay/triangulator.go:901
     54  geom/planar/triangulate/constraineddelaunay/triangulator.go:907
     55  geom/planar/triangulate/constraineddelaunay/triangulator.go:1107
     56  geom/planar/triangulate/constraineddelaunay/triangulator.go:1146
     57  geom/planar/triangulate/constraineddelaunay/triangulator.go:1157
     58  geom/planar/triangulate/constraineddelaunay/triangulator.go:1202
     59  geom/planar/triangulate/constraineddelaunay/triangulator.go:1206
     60  geom/planar/triangulate/constraineddelaunay/triangulator.go:1216
     61  geom/planar/triangulate/delaunaytriangulationbuilder.go:66
     62  geom/planar/triangulate/incrementaldelaunaytriangulator.go:46
     63  geom/planar/triangulate/incrementaldelaunaytriangulator.go:78
     64  geom/planar/triangulate/quadedge/lastfoundquadedgelocator.go:65
     65  geom/planar/triangulate/quadedge/quadedgesubdivision.go:976
     66  geom/slippy/tile.go:133

--- { return ... zero values ..., expr } ---
      1  geom/internal/parsing/parsing.go:125
      2  geom/planar/triangulate/constraineddelaunay/triangulator.go:428
      3  geom/planar/triangulate/constraineddelaunay/triangulator.go:447
      4  geom/planar/triangulate/constraineddelaunay/triangulator.go:460

--- single statement then branch ---
      1  geom/bbox.go:259
      2  geom/encoding/wkb/internal/decode/decode.go:29
      3  geom/encoding/wkb/internal/decode/decode.go:55
      4  geom/encoding/wkb/internal/decode/decode.go:63
      5  geom/encoding/wkb/internal/decode/decode.go:70
      6  geom/encoding/wkb/internal/decode/decode.go:79
      7  geom/encoding/wkb/internal/decode/decode.go:84
      8  geom/encoding/wkb/internal/decode/decode.go:93
      9  geom/encoding/wkb/internal/decode/decode.go:99
     10  geom/encoding/wkb/internal/decode/decode.go:105
     11  geom/encoding/wkb/internal/decode/decode.go:114
     12  geom/encoding/wkb/internal/decode/decode.go:119
     13  geom/encoding/wkb/internal/decode/decode.go:135
     14  geom/encoding/wkb/internal/decode/decode.go:140
     15  geom/encoding/wkb/internal/decode/decode.go:149
     16  geom/encoding/wkb/internal/decode/decode.go:155
     17  geom/encoding/wkb/internal/decode/decode.go:161
     18  geom/encoding/wkb/internal/decode/decode.go:170
     19  geom/encoding/wkb/internal/decode/decode.go:176
     20  geom/encoding/wkb/internal/tcase/token/token.go:162
     21  geom/encoding/wkt/internal/token/token.go:136

--- complex then branch; cannot use try ---
      1  geom/encoding/wkb/internal/tcase/tcase.go:74
      2  geom/encoding/wkt/internal/symbol/symbol.go:125
      3  geom/planar/intersect/xsweep.go:165
      4  geom/planar/makevalid/makevalid.go:85
      5  geom/planar/makevalid/makevalid.go:172
      6  geom/planar/makevalid/triangulate.go:19
      7  geom/planar/makevalid/triangulate.go:28
      8  geom/planar/makevalid/triangulate.go:36
      9  geom/planar/makevalid/triangulate.go:58
     10  geom/planar/triangulate/constraineddelaunay/triangulator.go:358
     11  geom/planar/triangulate/constraineddelaunay/triangulator.go:373
     12  geom/planar/triangulate/constraineddelaunay/triangulator.go:453
     13  geom/planar/triangulate/constraineddelaunay/triangulator.go:1237
     14  geom/planar/triangulate/constraineddelaunay/triangulator.go:1243
     15  geom/planar/triangulate/constraineddelaunay/triangulator.go:1249

--- stats ---
    820 (100.0% of     820) func declarations
    146 ( 17.8% of     820) func declarations returning an error
   1715 (100.0% of    1715) statements
    391 ( 22.8% of    1715) if statements
    111 ( 28.4% of     391) if <err> != nil statements
     66 ( 59.5% of     111) try candidates
      0 (  0.0% of     111) <err> name is different from "err"
--- non-try candidates ---
      4 (  3.6% of     111) { return ... zero values ..., expr }
     21 ( 18.9% of     111) single statement then branch
     15 ( 13.5% of     111) complex then branch; cannot use try
      0 (  0.0% of     111) non-empty else branch; cannot use try

في موضوع التكاليف غير المتوقعة ، أعيد نشرها من # 32611 ...

أرى ثلاث فئات من التكلفة:

  1. تكلفة المواصفات ، والتي تم تفصيلها في وثيقة التصميم.
  2. تكلفة الأدوات (أي مراجعة البرامج) ، تم استكشافها أيضًا في مستند التصميم.
  3. التكلفة على النظام البيئي ، والتي ذكرها المجتمع بالتفصيل أعلاه وفي # 32825.

إعادة رقم. 1 & 2 ، تكاليف try() متواضعة.

لتبسيط لا. 3 ، يعتقد معظم المعلقين أن try() سيضر بالكود و / أو النظام البيئي للرمز الذي نعتمد عليه ، وبالتالي يقلل من إنتاجيتنا وجودة المنتج. لا ينبغي التقليل من شأن هذا التصور الواسع الانتشار والمنطق جيدًا باعتباره "غير واقعي" أو "جمالي".

تعتبر تكلفة النظام البيئي أكثر أهمية بكثير من تكلفة المواصفات أو الأدوات.

griesemer من الظلم بشكل واضح الادعاء بأن "عشرات المعارضين الصوتيين" هم الجزء الأكبر من المعارضة. مئات الأشخاص علقوا هنا وفي # 32825. لقد أخبرتني في 12 حزيران (يونيو) ، "أدرك أن حوالي ثلثي المستطلعين ليسوا سعداء بالاقتراح". منذ ذلك الحين ، صوَّت أكثر من 2000 شخص على "ترك err != nil بمفرده" مع إعجاب بنسبة 90٪.

gdey هل يمكنك تعديل منشورك ليشمل فقط _stats & non-try المرشحين_؟

robfig ، gdey نشكرك على تقديم هذه البيانات ، وخاصة المقارنة قبل / بعد.

تضمين التغريدة
لقد أضفت بالتأكيد بعض المواد لتوضيح أن مخاوفي (ومخاوف الآخرين) يمكن معالجتها مباشرة. سؤالي ، إذن ، هو ما إذا كان فريق Go يرى إساءة الاستخدام المحتملة للأنماط غير المباشرة (أي العوائد المجردة و / أو طفرة خطأ في نطاق ما بعد الوظيفة عبر التأجيل) كتكلفة تستحق المناقشة خلال الخطوة 5 ، وأن الأمر يستحق اتخاذ إجراءات تجاه التخفيف من حدتها. المزاج الحالي هو أن هذا الجانب الأكثر إرباكًا في الاقتراح يُنظر إليه على أنه ميزة ذكية / جديدة من قبل فريق Go (لا يتم معالجة هذا القلق من خلال تقييم التحولات الآلية ويبدو أنه يتم تشجيعه / دعمه بنشاط. - errd ، في المحادثة ، وما إلى ذلك).

عدل للإضافة ... القلق مع فريق Go الذي يشجع ما يراه المخضرم Gophers على أنه باهظ هو ما قصدته فيما يتعلق بصمم النغمات.
... المراوغة هي تكلفة يشعر بها الكثير منا بقلق عميق على أنها مسألة ألم تجريبي. قد لا يكون شيئًا يمكن قياسه بسهولة (إذا كان معقولًا على الإطلاق) ، ولكن من المخادع اعتبار هذا القلق عاطفيًا بحد ذاته. بدلاً من ذلك ، فإن تجاهل حكمة الخبرة المشتركة لصالح الأرقام البسيطة دون حكم سياقي قوي هو نوع من المشاعر التي أحاول / نحاول العمل ضدها.

networkimprov نعتذر لعدم الوضوح الكافي. ما قلته كان:

ربما يكون هناك اثنان ، وربما ثلاثين معارضًا صريحًا لهذا الاقتراح - أنت تعرف من أنت. إنهم يسيطرون على هذه المناقشة من خلال مشاركات متكررة ، وأحيانًا متعددة في اليوم.

كنت أتحدث عن التعليقات الفعلية (كما في "المشاركات المتكررة") ، وليس الرموز التعبيرية. لا يوجد سوى عدد قليل نسبيًا من الأشخاص ينشرون هنا _ بشكل متكرر_ ، وهو ما أعتقد أنه لا يزال صحيحًا. كما أنني لم أتحدث عن # 32825 ؛ كنت أتحدث عن هذا الاقتراح.

بالنظر إلى الرموز التعبيرية ، فإن الوضع لم يتغير تقريبًا منذ شهر: 1/3 من الرموز التعبيرية تشير إلى رأي إيجابي ، و 2/3 تشير إلى رأي سلبي.

تضمين التغريدة

تذكرت شيئًا ما أثناء كتابة تعليقي أعلاه: بينما يقول مستند التصميم أن try يمكن تنفيذه كتحويل مباشر لشجرة بناء الجملة ، وفي كثير من الحالات يكون هذا هو الحال بوضوح ، هناك بعض الحالات التي لا أفعل فيها ترى طريقة مباشرة للقيام بذلك. على سبيل المثال ، افترض أن لدينا ما يلي:

switch x {
case rand.Int():
  a()
case 5, try(strconv.Atoi(y)):
  b()
}

بالنظر إلى أمر التقييم switch ، لا أرى كيفية رفع strconv.Atoi(y) بشكل طفيف من جملة case مع الحفاظ على الدلالات المقصودة ؛ أفضل ما يمكنني التوصل إليه هو إعادة كتابة switch كسلسلة مكافئة لـ if / else ، مثل هذا:

if x == rand.Int() {
  a()
} else if x == 5 {
  b()
} else if _v, _err := strconv.Atoi(y); _err != nil {
  return _err
} else if x == _v {
  b()
}

(هناك مواقف أخرى يمكن أن يحدث فيها هذا ، لكن هذا أحد أبسط الأمثلة وأول ما يتبادر إلى الذهن.)

في الواقع ، قبل نشر هذا الاقتراح كنت أعمل على مترجم AST لتنفيذ عامل التشغيل check من مسودة التصميم وواجهت هذه المشكلة. ومع ذلك ، كنت أستخدم نسخة مخترقة من حزم stdlib go/* ؛ ربما تم تنظيم الواجهة الأمامية للمجمع بطريقة تجعل ذلك أسهل؟ أو هل فاتني شيء وهناك طريقة مباشرة للقيام بذلك؟

راجع أيضًا https://github.com/rhysd/trygo؛ وفقًا لـ README ، فإنه لا يطبق تعبيرات try ، ويلاحظ بشكل أساسي نفس القلق الذي أثيره هنا ؛ أظن أن هذا قد يكون سبب عدم تنفيذ المؤلف لتلك الميزة.

لا يتم تطوير الكود الاحترافي daved في فراغ - فهناك اصطلاحات محلية ، وتوصيات نمطية ، ومراجعات للكود ، وما إلى ذلك (لقد قلت هذا من قبل). وبالتالي ، لا أفهم سبب "احتمال" إساءة الاستخدام (هذا ممكن ، لكن هذا صحيح بالنسبة لأي بنية لغة).

لاحظ أن استخدام defer لتزيين الأخطاء ممكن مع أو بدون try . هناك بالتأكيد سبب وجيه لوظيفة تحتوي على العديد من عمليات التحقق من الأخطاء ، وكلها تزين الأخطاء بنفس الطريقة ، للقيام بهذه الزخرفة مرة واحدة ، على سبيل المثال باستخدام defer . أو ربما تستخدم وظيفة الغلاف التي تقوم بالزخرفة. أو أي آلية أخرى تناسب مشروع القانون وتوصيات الترميز المحلية. بعد كل شيء ، "الأخطاء هي مجرد قيم" ومن المنطقي تمامًا كتابة الكود الذي يتعامل مع الأخطاء وعوامله.

يمكن أن تكون عمليات الإرجاع العارية مشكلة عند استخدامها بطريقة غير منضبطة. هذا لا يعني أنهم سيئون بشكل عام. على سبيل المثال ، إذا كانت نتائج الدالة صحيحة فقط إذا لم يكن هناك خطأ ، فيبدو أنه من الجيد تمامًا استخدام إرجاع عارية في حالة حدوث خطأ - طالما أننا منضبطون في ضبط الخطأ (لأن قيم الإرجاع الأخرى لا تفعل ذلك) لا يهم في هذه الحالة). try يضمن ذلك بالضبط. لا أرى أي "إساءة" هنا.

dpinela يترجم المحول البرمجي بالفعل عبارة switch مثل بيانك كسلسلة من if-else-if ، لذلك لا أرى مشكلة هنا. أيضًا ، فإن "شجرة بناء الجملة" التي يستخدمها المترجم ليست شجرة بناء الجملة "go / ast". يسمح التمثيل الداخلي للمترجم برمز أكثر مرونة لا يمكن بالضرورة ترجمته مرة أخرى إلى Go.

تضمين التغريدة
نعم ، بالطبع ، كل ما تقوله له أساس. ومع ذلك ، فإن المنطقة الرمادية ليست بالبساطة التي تؤطرها لتكون. عادة ما يتم التعامل مع المرتجعات العارية بحذر شديد من قبل أولئك الذين يعلمون الآخرين منا (نحن ، الذين نسعى جاهدين للنمو / تعزيز المجتمع). أقدر أن stdlib قد تناثر طوال الوقت. ولكن ، عند تعليم الآخرين ، يتم التأكيد دائمًا على العوائد الصريحة. دع الفرد يصل إلى مرحلة النضج الخاص به لكي يتحول إلى النهج الأكثر "خياليًا" ، ولكن تشجيعه منذ البداية سيكون بالتأكيد تعزيزًا لرمز يصعب قراءته (أي العادات السيئة). هذا ، مرة أخرى ، هو الصمم النغمي الذي أحاول تسليط الضوء عليه.

أنا شخصياً لا أرغب في منع الإرجاع المجرد أو التلاعب المؤجل بالقيمة. عندما تكون مناسبة حقًا ، يسعدني أن تتوفر هذه القدرات (على الرغم من أن المستخدمين ذوي الخبرة الآخرين قد يتخذون موقفًا أكثر صرامة). ومع ذلك ، فإن التشجيع على تطبيق هذه الميزات الأقل شيوعًا والهشة عمومًا بطريقة منتشرة هو تمامًا الاتجاه المعاكس الذي تخيلته في أي وقت مضى. هل التغيير الواضح في طبيعة تجنب السحر والأشكال المحفوفة بالمخاطر من المراوغة تحول مقصود؟ هل يجب أن نبدأ أيضًا في التأكيد على استخدام DICs وآليات أخرى يصعب تصحيحها؟

ملاحظة: وقتك هو موضع تقدير كبير. فريقك واللغة لديهما احترامي واهتمامي. لا أتمنى أي حزن لأي شخص في التحدث علانية ؛ آمل أن تسمع طبيعة ما يثير قلقي / قلقي وتحاول رؤية الأشياء من منظور "الخطوط الأمامية".

إضافة بعض التعليقات على تصويتي معارضا.

للاقتراح المحدد قيد البحث:

1) أفضل أن تكون هذه كلمة رئيسية مقابل وظيفة مضمنة لأسباب تم توضيحها مسبقًا للتحكم في التدفق وقابلية قراءة الكود.

2) من الناحية الدلالية ، "المحاولة" هي مانعة الصواعق. وما لم يكن هناك استثناء تم طرحه ، فمن الأفضل إعادة تسمية "try" إلى شيء مثل guard أو ensure .

3) إلى جانب هاتين النقطتين ، أعتقد أن هذا هو أفضل اقتراح رأيته لهذا النوع من الأشياء.

هناك تعليقان إضافيان يوضحان اعتراضي على أي إضافة لمفهوم try/guard/ensure مقابل ترك if err != nil بمفرده:

1) هذا يتعارض مع أحد التفويضات الأصلية لـ golang (على الأقل كما أدركت) ليكون واضحًا وسهل القراءة / الفهم ، مع القليل جدًا من "السحر".

2) سيشجع هذا الكسل في اللحظة المحددة عندما يكون التفكير مطلوبًا: "ما هو أفضل شيء يمكن أن يفعله الكود الخاص بي في حالة حدوث هذا الخطأ؟". هناك العديد من الأخطاء التي يمكن أن تظهر أثناء القيام بأشياء "نموذجية" مثل فتح الملفات ، ونقل البيانات عبر شبكة ، وما إلى ذلك ، بينما قد تبدأ بمجموعة من "المحاولات" التي تتجاهل سيناريوهات الفشل غير الشائعة ، وفي النهاية العديد من هذه " trys "بعيدًا حيث قد تحتاج إلى تنفيذ مهام التراجع / إعادة المحاولة و / أو التسجيل / التتبع و / أو مهام التنظيف. "الأحداث منخفضة الاحتمال" مضمونة على نطاق واسع.

فيما يلي بعض الإحصائيات الخام tryhard . تم التحقق من صحة هذا بشكل طفيف ، لذا لا تتردد في الإشارة إلى الأخطاء. ؛-)

أول 20 حزمة مشهورة على موقع godoc.org

هذه هي المستودعات التي تتوافق مع أول 20 حزمة شائعة على https://godoc.org ، مرتبة حسب النسبة المئوية للمرشح التجريبي. هذا باستخدام الإعدادات الافتراضية tryhard ، والتي من الناحية النظرية يجب أن تستبعد الدلائل vendor .

تبلغ القيمة المتوسطة لمرشحي التجربة عبر عمليات إعادة الشراء العشرين هذه 58٪.

| مشروع | loc | إذا stmts | إذا! = لا شيء (٪ من إذا) | جرب المرشحين (٪ من إذا! = لا شيء) |
| --------- | ----- | --------------- | ----------------- ----- | ---------------
| github.com/google/uuid | 1714 | 12 | 16.7٪ | 0.0٪ |
| github.com/pkg/errors | 1886 | 10 | 0.0٪ | 0.0٪ |
| github.com/aws/aws-sdk-go | 1911309 | 32015 | 9.4٪ | 8.9٪ |
| github.com/jinzhu/gorm | 15246 | 44 | 11.4٪ | 20.0٪ |
| github.com/robfig/cron | 1911 | 20 | 35.0٪ | 28.6٪ |
| github.com/gorilla/websocket | 6959 | 212 | 32.5٪ | 39.1٪ |
| github.com/dgrijalva/jwt-go | 3270 | 118 | 29.7٪ | 40.0٪ |
| github.com/gomodule/redigo | 7119 | 187 | 34.8٪ | 41.5٪ |
| github.com/unixpickle/kahoot-hack | 1743 | 52 | 75.0٪ | 43.6٪ |
| github.com/lib/pq | 13396 | 239 | 30.1٪ | 55.6٪ |
| github.com/sirupsen/logrus | 5063 | 29 | 17.2٪ | 60.0٪ |
| github.com/prometheus/client_golang | 17791 | 194 | 49.0٪ | 62.1٪ |
| github.com/go-redis/redis | 21182 | 326 | 42.6٪ | 73.4٪ |
| github.com/mongodb/mongo-go-driver | 86605 | 2097 | 37.8٪ | 73.9٪ |
| github.com/uber-go/zap | 15363 | 84 | 36.9٪ | 74.2٪ |
| github.com/golang/protobuf | 42959 | 685 | 22.9٪ | 77.1٪ |
| github.com/gin-gonic/gin | 14574 | 96 | 53.1٪ | 86.3٪ |
| github.com/go-pg/pg | 26369 | 831 | 37.7٪ | 86.9٪ |
| github.com/Shopify/sarama | 36427 | 1369 | 68.2٪ | 91.0٪ |
| github.com/stretchr/testify | 13496 | 32 | 43.8٪ | 92.9٪ |

عمود " if stmts " يحسب فقط عبارات if في الوظائف التي تُرجع خطأً ، وهو ما يُبلغ عنه tryhard ، والذي نأمل أن يفسر سبب انخفاضه لشيء مثل gorm .

10 متفرقات. مشاريع Go "الكبيرة"

نظرًا لأن الحزم الشائعة على godoc.org تميل إلى أن تكون حزم مكتبة ، فقد أردت أيضًا التحقق من الإحصائيات لبعض المشاريع الكبيرة أيضًا.

هذه منوعات. المشاريع الكبيرة التي تصادف أن تكون في قمة اهتماماتي (أي لا يوجد منطق حقيقي وراء هذه العشرة). يتم فرز هذا مرة أخرى حسب نسبة مرشح المحاولة.

تبلغ القيمة المتوسطة لمرشحي التجربة عبر هذه المستودعات العشر 59٪.

| مشروع | loc | إذا stmts | إذا! = لا شيء (٪ من إذا) | جرب المرشحين (٪ من إذا! = لا شيء) |
| --------- | ----- | --------------- | ----------------- ----- | --------------------------------- |
| github.com/juju/juju | 1026473 | 26904 | 51.9٪ | 17.5٪ |
| github.com/go-kit/kit | 38949 | 467 | 57.0٪ | 51.9٪ |
| github.com/boltdb/bolt | 12426 | 228 | 46.1٪ | 53.3٪ |
| github.com/hashicorp/consul | 249369 | 5477 | 47.6٪ | 54.5٪ |
| github.com/docker/docker | 251152 | 8690 | 48.7٪ | 56.8٪ |
| github.com/istio/istio | 429636 | 7564 | 40.4٪ | 61.9٪ |
| github.com/gohugoio/hugo | 94875 | 1853 | 42.4٪ | 64.8٪ |
| github.com/etcd-io/etcd | 209603 | 4657 | 38.3٪ | 65.5٪ |
| github.com/kubernetes/kubernetes | 1789172 | 40289 | 43.3٪ | 66.5٪ |
| github.com/cockroachdb/cockroach | 1038529 | 22018 | 39.9٪ | 74.0٪ |


يمثل هذان الجدولان بالطبع عينة فقط من المشاريع مفتوحة المصدر ، والمشروعات المعروفة بشكل معقول فقط. لقد رأيت الناس يفترضون أن قواعد الشفرة الخاصة ستظهر تنوعًا أكبر ، وهناك على الأقل بعض الأدلة على ذلك استنادًا إلى بعض الأرقام التي ينشرها العديد من الأشخاص.

thepudds ، هذا لا يبدو مثل أحدث _tryhard_ ، والذي يعطي "مرشحين بدون تجربة".

networkimprov يمكنني أن أؤكد أنه على الأقل بالنسبة لـ gorm هذه نتائج من أحدث tryhard . ببساطة ، لم يتم الإبلاغ عن "المرشحين الذين لم يجربوا" في الجداول أعلاه.

daved أولاً ، دعني أؤكد لك أنني / نسمعك بصوت عالٍ وواضح. على الرغم من أننا ما زلنا في وقت مبكر من هذه العملية ويمكن أن تتغير الكثير من الأشياء. دعونا لا نقفز البندقية.

أفهم (وأقدر) أن المرء قد يرغب في اختيار نهج أكثر تحفظًا عند تدريس Go. شكرا.

griesemer FYI هذه هي نتائج تشغيل أحدث إصدار من tryhard على 233 ألف سطر من التعليمات البرمجية التي شاركت فيها ، والكثير منها ليس مفتوح المصدر:

--- stats ---
   8760 (100.0% of    8760) functions (function literals are ignored)
   2942 ( 33.6% of    8760) functions returning an error
  22991 (100.0% of   22991) statements in functions returning an error
   5548 ( 24.1% of   22991) if statements
   2929 ( 52.8% of    5548) if <err> != nil statements
    163 (  5.6% of    2929) try candidates
      0 (  0.0% of    2929) <err> name is different from "err"
--- non-try candidates (-l flag lists file positions) ---
   2213 ( 75.6% of    2929) { return ... zero values ..., expr }
    167 (  5.7% of    2929) single statement then branch
    253 (  8.6% of    2929) complex then branch; cannot use try
     14 (  0.5% of    2929) non-empty else branch; cannot use try

يستخدم جزء كبير من الكود مصطلحًا مشابهًا لـ:

 if err != nil {
     return ... zero values ..., errors.Wrap(err)
 }

قد يكون من المثير للاهتمام أن يتمكن tryhard من تحديد متى تستخدم كل هذه التعبيرات في دالة تعبيرًا متطابقًا - أي عندما يكون من الممكن إعادة كتابة الدالة باستخدام معالج defer مشترك واحد.

فيما يلي إحصائيات أداة مساعدة GCP صغيرة لأتمتة إنشاء المستخدم والمشروع:

$ tryhard -r .
--- stats ---
    129 (100.0% of     129) functions (function literals are ignored)
     75 ( 58.1% of     129) functions returning an error
    725 (100.0% of     725) statements in functions returning an error
    164 ( 22.6% of     725) if statements
     93 ( 56.7% of     164) if <err> != nil statements
     64 ( 68.8% of      93) try candidates
      0 (  0.0% of      93) <err> name is different from "err"
--- non-try candidates (-l flag lists file positions) ---
     17 ( 18.3% of      93) { return ... zero values ..., expr }
      7 (  7.5% of      93) single statement then branch
      1 (  1.1% of      93) complex then branch; cannot use try
      0 (  0.0% of      93) non-empty else branch; cannot use try

بعد ذلك ، تقدمت وتحققت من جميع الأماكن في الكود التي لا تزال تتعامل مع متغير err لمعرفة ما إذا كان بإمكاني العثور على أي أنماط ذات معنى.

جمع err s

في مكانين ، لا نريد إيقاف التنفيذ عند الخطأ الأول وبدلاً من ذلك نكون قادرين على رؤية جميع الأخطاء التي حدثت مرة واحدة في نهاية التشغيل. ربما توجد طريقة مختلفة للقيام بذلك تتكامل جيدًا مع try أو تتم إضافة شكل من أشكال الدعم للأخطاء المتعددة إلى Go نفسها.

var errs []error
for _, p := range toDelete {
    fmt.Println("delete:", p.ProjectID)
    if err := s.DeleteProject(ctx, p.ProjectID); err != nil {
        errs = append(errs, err)
    }
}

خطأ في مسؤولية الديكور

بعد قراءة هذا التعليق مرة أخرى ، كان هناك فجأة الكثير من الحالات المحتملة try التي قفزت إلى انتباهي. كلها متشابهة في أن وظيفة الاستدعاء تزين خطأ دالة مستدعاه بالمعلومات التي قد تكون الوظيفة التي تم استدعاؤها قد أضافتها بالفعل إلى الخطأ:

func run() error {
    key := "MY_ENV_VAR"
    client, err := ClientFromEnvironment(key)
    if err != nil {
        // "github.com/pkg/errors"
        return errors.Wrap(err, key)
    }
    // do something with `client`
}

func ClientFromEnvironment(key string) (*http.Client, error) {
    filename, ok := os.LookupEnv(key)
    if !ok {
        return nil, errors.New("environment variable not set")
    }
    return ClientFromFile(filename)
}

نقلا عن الجزء المهم من مدونة Go هنا مرة أخرى للتوضيح:

تقع على عاتق تنفيذ الخطأ مسؤولية تلخيص السياق. الخطأ المُرجع بواسطة تنسيقات os.Open كـ "open / etc / passwd: تم رفض الإذن" وليس فقط "تم رفض الإذن". يفتقد الخطأ الذي تم إرجاعه بواسطة Sqrt معلومات حول الوسيطة غير الصالحة.

مع وضع هذا في الاعتبار ، يصبح الرمز أعلاه الآن:

func run() error {
    key := "MY_ENV_VAR"
    client := try(ClientFromEnvironment(key))
    // do something with `client`
}

func ClientFromEnvironment(key string) (*http.Client, error) {
    filename, ok := os.LookupEnv(key)
    if !ok {
        return nil, fmt.Errorf("environment variable not set: %s", key)
    }
    return ClientFromFile(filename)
}

للوهلة الأولى ، يبدو هذا بمثابة تغيير طفيف ولكن في تقديري ، قد يعني ذلك أن try يحفز بالفعل على دفع خطأ أفضل وأكثر اتساقًا في التعامل مع سلسلة الوظائف وأقرب إلى المصدر أو الحزمة.

ملاحظات نهائية

بشكل عام ، أعتقد أن القيمة التي try على المدى الطويل أعلى من المشكلات المحتملة التي أراها حاليًا ، وهي:

  1. قد "تشعر" الكلمة الرئيسية بتحسن لأن try يغير تدفق التحكم.
  2. يعني استخدام try أنه لم يعد بإمكانك وضع أداة إيقاف التصحيح في حالة return err .

نظرًا لأن هذه المخاوف معروفة بالفعل لفريق Go ، فأنا أشعر بالفضول لمعرفة كيف ستظهر في "العالم الحقيقي". نشكرك على وقتك في قراءة جميع رسائلنا والرد عليها.

تحديث

تم إصلاح توقيع دالة لم يُرجع error من قبل. شكرا لك @ ماجيكال لاكتشاف ذلك!

func main() {
    key := "MY_ENV_VAR"
    client := try(ClientFromEnvironment(key))
    // do something with `client`
}

mrkanister Nitpicking ، لكن لا يمكنك فعليًا استخدام try في هذا المثال لأن main لا يُرجع $ # error .

هذا تعليق تقدير ؛
شكرًا griesemer على البستنة وكل ما تفعله بشأن هذه المشكلة وكذلك في أي مكان آخر.

في حال كان لديك العديد من الخطوط مثل هذه (من https://github.com/golang/go/issues/32437#issuecomment-509974901):

if !ok {
    return nil, fmt.Errorf("environment variable not set: %s", key)
}

يمكنك استخدام دالة مساعدة تقوم بإرجاع خطأ غير صفري فقط إذا تحققت بعض الشروط:

try(condErrorf(!ok, "environment variable not set: %s", key))

بمجرد تحديد الأنماط الشائعة ، أعتقد أنه سيكون من الممكن التعامل مع العديد منها بعدد قليل من المساعدين ، أولاً على مستوى الحزمة ، وربما الوصول في النهاية إلى المكتبة القياسية. Tryhard رائع ، إنه يقوم بعمل رائع ويقدم الكثير من المعلومات الشيقة ، ولكن هناك الكثير.

خط واحد مدمج إذا

كإضافة إلى سطر if المفرد من قبل zeebo وآخرين ، يمكن أن تحتوي عبارة if على نموذج مضغوط يزيل != nil والأقواس المتعرجة:

if err return err
if err return errors.Wrap(err, "foo: failed to boo")
if err return fmt.Errorf("foo: failed to boo: %v", err)

أعتقد أن هذا بسيط وخفيف الوزن ومقروء. يتكون من جزأين:

  1. تحقق عبارات if ضمنيًا من قيم الخطأ لـ nil (أو ربما واجهات بشكل عام). IMHO هذا يحسن قابلية القراءة عن طريق تقليل الكثافة ويكون السلوك واضحًا تمامًا.
  2. أضف دعمًا لـ if variable return ... . نظرًا لأن return قريب جدًا من الجانب الأيسر ، يبدو أنه لا يزال من السهل جدًا قراءة الكود - الصعوبة الإضافية في القيام بذلك هي إحدى الحجج الرئيسية ضد ifs أحادية السطر (؟) يحتوي Go أيضًا على سابقة لتبسيط بناء الجملة على سبيل المثال عن طريق إزالة الأقواس من عبارة if الخاصة به.

النمط الحالي:

a, err := BusinessLogic(state)
if err != nil {
   return nil, err
}

سطر واحد إذا:

a, err := BusinessLogic(state)
if err != nil { return nil, err }

مضغوط من سطر واحد إذا:

a, err := BusinessLogic(state)
if err return nil, err
a, err := BusinessLogic(state)
if err return nil, errors.Wrap(err, "some context")
func (c *Config) Build() error {
    pkgPath, err := c.load()
    if err return nil, errors.WithMessage(err, "load config dir")

    b := bytes.NewBuffer(nil)
    err = templates.ExecuteTemplate(b, "main", c)
    if err return nil, errors.WithMessage(err, "execute main template")

    buf, err := format.Source(b.Bytes())
    if err return nil, errors.WithMessage(err, "format main template")

    target := fmt.Sprintf("%s.go", filename(pkgPath))
    err = ioutil.WriteFile(target, buf, 0644)
    if err return nil, errors.WithMessagef(err, "write file %s", target)

    // ...
}

@ eug48 انظر # 32611

فيما يلي إحصائيات tryhard لـ monorepo (أسطر كود go ، باستثناء كود المورد: 2،282،731):

--- stats ---
 117551 (100.0% of  117551) functions (function literals are ignored)
  35726 ( 30.4% of  117551) functions returning an error
 263725 (100.0% of  263725) statements in functions returning an error
  50690 ( 19.2% of  263725) if statements
  25042 ( 49.4% of   50690) if <err> != nil statements
  12091 ( 48.3% of   25042) try candidates
     36 (  0.1% of   25042) <err> name is different from "err"
--- non-try candidates (-l flag lists file positions) ---
   3561 ( 14.2% of   25042) { return ... zero values ..., expr }
   3304 ( 13.2% of   25042) single statement then branch
   4966 ( 19.8% of   25042) complex then branch; cannot use try
    296 (  1.2% of   25042) non-empty else branch; cannot use try

نظرًا لأن الأشخاص ما زالوا يقترحون بدائل ، أود أن أعرف بمزيد من التفصيل الوظيفة التي يريدها مجتمع Go الأوسع من أي ميزة جديدة مقترحة لمعالجة الأخطاء.

لقد جمعت استبيانًا يسرد مجموعة من الميزات المختلفة ، ووظائف معالجة الأخطاء التي رأيت أشخاصًا يقترحونها. لقد قدمت بعناية أي تسمية أو بناء جملة مقترحة ، وبالطبع حاولت جعل الاستطلاع محايدًا بدلاً من تفضيل آرائي الخاصة.

إذا كان الأشخاص يرغبون في المشاركة ، فإليك الرابط ، الذي تم اختصاره للمشاركة:

https://forms.gle/gaCBgxKRE4RMCz7c7

يجب أن يكون كل من يشارك قادرًا على رؤية نتائج الملخص. ربما هذا قد يساعد في تركيز المناقشة؟

if err := os.Setenv("GO111MODULE", "on"); err != nil {
    return err
}

لا يعمل معالج المؤجل الذي يضيف السياق في هذه الحالة أم لا؟ إذا لم يكن الأمر كذلك ، فسيكون من الجيد جعله أكثر وضوحًا ، إذا كان ذلك ممكنًا لأنه يحدث بسرعة كبيرة ، خاصة وأن هذا هو المعيار القياسي حتى الآن.

أوه ، ويرجى تقديم try ، لقد وجدت الكثير من حالات الاستخدام هنا أيضًا.

--- stats ---
    929 (100.0% of     929) functions (function literals are ignored)
    230 ( 24.8% of     929) functions returning an error
   1480 (100.0% of    1480) statements in functions returning an error
    320 ( 21.6% of    1480) if statements
    206 ( 64.4% of     320) if <err> != nil statements
    109 ( 52.9% of     206) try candidates
      2 (  1.0% of     206) <err> name is different from "err"
--- non-try candidates (-l flag lists file positions) ---
     53 ( 25.7% of     206) { return ... zero values ..., expr }
     18 (  8.7% of     206) single statement then branch
     17 (  8.3% of     206) complex then branch; cannot use try
      2 (  1.0% of     206) non-empty else branch; cannot use try

lpar يمكنك مناقشة البدائل ولكن من فضلك لا تفعل هذا في هذه المسألة. هذا حول عرض try . سيكون أفضل مكان في الواقع أحد القوائم البريدية ، على سبيل المثال go-nuts. إن أداة تعقب المشكلات هي الأفضل حقًا لتتبع ومناقشة قضية معينة بدلاً من المناقشة العامة. شكرا.

fabstu سيعمل المعالج defer في المثال الخاص بك على ما يرام ، سواء مع أو بدون try . توسيع التعليمات البرمجية الخاصة بك باستخدام وظيفة التضمين:

func f() (err error) {
    defer func() {
       if err != nil {
          err = decorate(err, "msg") // here you can modify the result error as you please
       }
    }()
    ...
    if err := os.Setenv("GO111MODULE", "on"); err != nil {
        return err
    }
    ...
}

(لاحظ أن النتيجة err سيتم تعيينها بواسطة return err ؛ و err المستخدم بواسطة return هو الناتج المعلن محليًا مع if - هذه فقط قواعد تحديد النطاق العادية في العمل).

أو ، باستخدام try ، مما يلغي الحاجة إلى المتغير المحلي err :

func f() (err error) {
    defer func() {
       if err != nil {
          err = decorate(err, "msg") // here you can modify the result error as you please
       }
    }()
    ...
    try(os.Setenv("GO111MODULE", "on"))
    ...
}

وعلى الأرجح ، قد ترغب في استخدام إحدى وظائف errors/errd المقترحة :

func f() (err error) {
    defer errd.Wrap(&err, ... )
    ...
    try(os.Setenv("GO111MODULE", "on"))
    ...
}

وإذا لم تكن بحاجة إلى التفاف ، فستكون فقط:

func f() error {
    ...
    try(os.Setenv("GO111MODULE", "on"))
    ...
}

fastu وأخيرًا ، يمكنك استخدام errors/errd أيضًا بدون try وبعد ذلك تحصل على:

func f() (err error) {
    defer errd.Wrap(&err, ... )
    ...
    if err := os.Setenv("GO111MODULE", "on"); err != nil {
        return err
    }
    ...
}

كلما زاد اعتقادي ، زاد إعجابي بهذا الاقتراح.
الشيء الوحيد الذي يزعجني هو استخدام الإرجاع المسمى في كل مكان. هل هي أخيرًا ممارسة جيدة ويجب أن أستخدمها (لم أجربها مطلقًا)؟

على أي حال ، قبل تغيير كل الكود الخاص بي ، هل سيعمل على هذا النحو؟

func f() error {
  var err error
  defer errd.Wrap(&err,...)
  try(...)
}

flibustenet معلمات النتائج المحددة في حد ذاتها ليست ممارسة سيئة على الإطلاق ؛ الاهتمام المعتاد بالنتائج المسماة أنها تتيح naked returns ؛ على سبيل المثال ، يمكن للمرء ببساطة كتابة return بدون الحاجة إلى تحديد النتائج الفعلية _ مع return _. بشكل عام (ولكن ليس دائمًا!) تجعل هذه الممارسة من الصعب قراءة الكود والتعليل بشأنه لأنه لا يمكن للمرء ببساطة النظر إلى عبارة return واستنتاج ماهية النتيجة. على المرء أن يمسح رمز معلمات النتيجة. قد يخطئ المرء في تحديد قيمة النتيجة ، وما إلى ذلك. لذلك في بعض قواعد التعليمات البرمجية ، لا يُنصح بالعودة المجردة.

ولكن ، كما ذكرت من قبل ، إذا كانت النتائج غير صالحة في حالة حدوث خطأ ، فلا بأس من ضبط الخطأ وتجاهل الباقي. يكون الإرجاع المجرد في مثل هذه الحالات على ما يرام تمامًا طالما تم تعيين نتيجة الخطأ باستمرار. سيضمن try ذلك تمامًا.

أخيرًا ، معلمات النتائج المسماة مطلوبة فقط إذا كنت تريد زيادة إرجاع الخطأ بـ defer . يناقش مستند التصميم أيضًا بإيجاز إمكانية توفير مضمّن آخر للوصول إلى نتيجة الخطأ. هذا من شأنه أن يلغي الحاجة إلى الإرجاع المسماة تمامًا.

فيما يتعلق بمثال الكود الخاص بك: لن يعمل هذا كما هو متوقع لأن try _always_ يعيّن _result error_ (وهو غير مسمى في هذه الحالة). لكنك تعلن عن متغير محلي مختلف err ويعمل المتغير $ # errd.Wrap على ذلك المتغير. لن يتم تعيينه بواسطة try .

تقرير تجربة سريعة: أنا أكتب معالج طلب HTTP يبدو كالتالي:

func Handler(w http.ResponseWriter, r *http.Request) {
        ctx := r.Context()
        id := chi.URLParam(r, "id")

        var err error
        // starts as bad request, then it's an internal server error after we parse inputs
        var statusCode = http.StatusBadRequest

        defer func() {
            if err != nil {
                wrap := xerrors.Errorf("handler fail: %w", err)
                logger.With(zap.Error(wrap)).Error("error")
                http.Error(w, wrap.Error(), statusCode)
            }
        }()
        var c Thingie
        err = unmarshalBody(r, &c)
        if err != nil {
            return
        }
        statusCode = http.StatusInternalServerError
        s, err := DoThing(ctx, c)
        if err != nil {
            return
        }
        d, err := DoThingWithResult(ctx, id, s)
        if err != nil {
            return
        }
        data, err := json.Marshal(detail)
        if err != nil {
            return
        }
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(http.StatusCreated)
        _, err = w.Write(data)
        if err != nil {
            return
        }
}

للوهلة الأولى ، يبدو أن هذا هو المرشح المثالي لـ try نظرًا لوجود الكثير من معالجة الأخطاء حيث لا يوجد شيء يمكن فعله باستثناء إرجاع رسالة ، وكل ذلك يمكن تأجيله. لكن لا يمكنك استخدام try لأن معالج الطلب لا يُرجع error . من أجل استخدامه ، يجب أن أقوم بلف الجسم في إغلاق بالتوقيع func() error . هذا يبدو ... غير أنيق وأظن أن الكود الذي يبدو مثل هذا هو نمط شائع إلى حد ما.

تضمين التغريدة

يعمل هذا (https://play.golang.org/p/NaBZe-QShpu):

package main

import (
    "errors"
    "fmt"

    "golang.org/x/xerrors"
)

func main() {
    var err error
    defer func() {
        filterCheck(recover())
        if err != nil {
            wrap := xerrors.Errorf("app fail (at count %d): %w", ct, err)
            fmt.Println(wrap)
        }
    }()

    check(retNoErr())

    n, err := intNoErr()
    check(err)

    n, err = intErr()
    check(err)

    check(retNoErr())

    check(retErr())

    fmt.Println(n)
}

func check(err error) {
    if err != nil {
        panic(struct{}{})
    }
}

func filterCheck(r interface{}) {
    if r != nil {
        if _, ok := r.(struct{}); !ok {
            panic(r)
        }
    }
}

var ct int

func intNoErr() (int, error) {
    ct++
    return 0, nil
}

func retNoErr() error {
    ct++
    return nil
}

func intErr() (int, error) {
    ct++
    return 0, errors.New("oops")
}

func retErr() error {
    ct++
    return errors.New("oops")
}

آه ، أول تصويت سلبي! جيد. دع البراغماتية تتدفق من خلالك.

Ran tryhard على بعض من الأكواد البرمجية الخاصة بي. لسوء الحظ ، تحتوي بعض الحزم الخاصة بي على مرشحين حاولوا 0 على الرغم من كونها كبيرة جدًا لأن الأساليب المستخدمة فيها تستخدم تنفيذ خطأ مخصص. على سبيل المثال ، عند إنشاء الخوادم ، أحب أن تقوم طرق طبقة منطق العمل الخاصة بي بإصدار SanitizedError s فقط بدلاً من error s للتأكد في وقت التجميع من أن أشياء مثل مسارات نظام الملفات أو معلومات النظام لا تسرب إلى المستخدمين في رسائل الخطأ.

على سبيل المثال ، قد تبدو الطريقة التي تستخدم هذا النمط كما يلي:

func (a *App) GetFriendsOfUser(userId model.Id) ([]*model.User, SanitizedError) {
    if user, err := a.GetUserById(userId); err != nil {
        // (*App).GetUserById returns (*model.User, SanitizedError)
        // This could be a try() candidate.
        return err
    } else if user == nil {
        return NewUserError("The specified user doesn't exist.")
    }

    friends, err := a.Store.GetFriendsOfUser(userId)
    // (*Store).GetFriendsOfUser returns ([]*model.User, error)
    // This could be a SQL error or a network error or who knows what.
    return friends, NewInternalError(err)
}

هل هناك أي سبب يمنعنا من تخفيف الاقتراح الحالي للعمل طالما أن قيمة الإرجاع الأخيرة لكل من دالة التضمين وتجربة تعبير الدالة هي نفس النوع؟ هذا من شأنه أن يتجنب أي شيء ملموس -> ارتباك في الواجهة ، ولكنه سيمكن المحاولة في مواقف مثل المذكورة أعلاه.

شكرًا ، jonbodner ، على سبيل المثال الخاص بك. سأكتب هذا الرمز على النحو التالي (بغض النظر عن أخطاء الترجمة):

func Handler(w http.ResponseWriter, r *http.Request) {
    statusCode, err := internalHandler(w, r)
    if err != nil {
        wrap := xerrors.Errorf("handler fail: %w", err)
        logger.With(zap.Error(wrap)).Error("error")
        http.Error(w, wrap.Error(), statusCode)
    }
}

func internalHandler(w http.ResponseWriter, r *http.Request) (statusCode int, err error) {
    ctx := r.Context()
    id := chi.URLParam(r, "id")

    // starts as bad request, then it's an internal server error after we parse inputs
    statusCode = http.StatusBadRequest
    var c Thingie
    try(unmarshalBody(r, &c))

    statusCode = http.StatusInternalServerError
    s := try(DoThing(ctx, c))
    d := try(DoThingWithResult(ctx, id, s))
    data := try(json.Marshal(detail))

    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    try(w.Write(data))

    return
}

يستخدم وظيفتين ولكنه أقصر كثيرًا (29 سطرًا مقابل 40 سطرًا) - واستخدمت تباعدًا لطيفًا - ولا يحتاج هذا الرمز إلى defer . defer وجه الخصوص ، جنبًا إلى جنب مع رمز الحالة الذي يتم تغييره في الطريق إلى الأسفل واستخدامه في defer يجعل متابعة الكود الأصلي أصعب من اللازم. الكود الجديد ، بينما يستخدم النتائج المسماة والعودة المجردة (يمكنك بسهولة استبدال ذلك بـ return statusCode, nil إذا كنت تريد) هو أبسط لأنه يفصل بشكل واضح معالجة الأخطاء من "منطق الأعمال".

فقط أعد نشر تعليقي في عدد آخر https://github.com/golang/go/issues/32853#issuecomment -510340544

أعتقد أنه إذا تمكنا من توفير معلمة أخرى funcname ، فسيكون ذلك رائعًا ، وإلا فإننا ما زلنا لا نعرف الخطأ الذي تم إرجاعه بواسطة الوظيفة.

func foo() error {
    handler := func(err error, funcname string) error {
        return fmt.Errorf("%s: %v", funcname, err) // wrap something
        //return nil // or dismiss
    }

    a, b := try(bar1(), handler) 
    c, d := try(bar2(), handler) 
}

ccbrown أتساءل عما إذا كان مثالك سيكون قابلاً لنفس المعاملة على النحو الوارد أعلاه ؛ على سبيل المثال ، إذا كان من المنطقي تحليل الكود إلى عوامل بحيث يتم تغليف الأخطاء الداخلية مرة واحدة (بواسطة وظيفة مرفقة) قبل أن تخرج (بدلاً من تغليفها في كل مكان). يبدو لي (بدون معرفة الكثير عن نظامك) أن ذلك سيكون مفضلاً لأنه سيجعل التفاف الخطأ مركزيًا في مكان واحد بدلاً من كل مكان.

لكن فيما يتعلق بسؤالك: يجب أن أفكر في جعل try يقبل نوع خطأ أكثر عمومية (وإرجاع واحد أيضًا). لا أرى مشكلة في ذلك في الوقت الحالي (باستثناء أن شرحه أكثر تعقيدًا) - ولكن قد تكون هناك مشكلة بعد كل شيء.

على هذا المنوال ، تساءلنا مبكرًا عما إذا كان من الممكن تعميم try لذلك فهو لا يعمل فقط مع أنواع error ، ولكن أي نوع ، والاختبار err != nil سيفعل ذلك. تصبح x != zero حيث x ما يعادل err (النتيجة الأخيرة) ، و zero القيمة الصفرية المقابلة لنوع x . لسوء الحظ ، هذا لا يعمل مع الحالة الشائعة للمنطق المنطقي (وإلى حد كبير أي نوع أساسي آخر) ، لأن القيمة الصفرية لـ bool هي false و ok != false هي بالضبط على عكس ما نريد اختباره.

lunny لا يقبل الإصدار المقترح من try دالة معالج.

تضمين التغريدة ما هو مؤسف! وإلا يمكنني إزالة github.com/pkg/errors وجميع errors.Wrap .

ccbrown أتساءل عما إذا كان مثالك سيكون قابلاً لنفس المعاملة على النحو الوارد أعلاه ؛ على سبيل المثال ، إذا كان من المنطقي تحليل الكود إلى عوامل بحيث يتم تغليف الأخطاء الداخلية مرة واحدة (بواسطة وظيفة مرفقة) قبل أن تخرج (بدلاً من تغليفها في كل مكان). يبدو لي (بدون معرفة الكثير عن نظامك) أن ذلك سيكون مفضلاً لأنه سيجعل التفاف الخطأ مركزيًا في مكان واحد بدلاً من كل مكان.

griesemer إرجاع try error بدلاً من ذلك إلى وظيفة التضمين يجعل من الممكن نسيان تصنيف كل خطأ على أنه خطأ داخلي ، أو خطأ مستخدم ، أو خطأ في التفويض ، وما إلى ذلك. لن يستحق try تداول هذه الشيكات الخاصة بوقت التجميع لشيكات وقت التشغيل.

أود أن أقول إنني أحب تصميم try ، ولكن لا يزال هناك كشف if # $ 1 $ # $ في معالج defer بينما تستخدم try . لا أعتقد أن هذا سيكون أبسط من كشوفات الحساب if بدون معالج try و defer . ربما فقط استخدام try سيكون أفضل بكثير.

تضمين التغريدة عند العودة إلى الماضي ، أعتقد أن الاسترخاء الذي اقترحته لا ينبغي أن يكون مشكلة. أعتقد أنه يمكننا الاسترخاء في try للعمل مع أي نوع واجهة (ونوع نتيجة مطابق) ، وليس فقط error ، طالما أن الاختبار ذي الصلة يظل x != nil . شيء لتفكر به. يمكن القيام بذلك في وقت مبكر ، أو بأثر رجعي لأنه سيكون تغييرًا متوافقًا مع الإصدارات السابقة على ما أعتقد.

مثال jonbodner ، والطريقة التي أعاد بها griesemer كتابته ، هي بالضبط نوع الكود الذي لدي حيث أود حقًا استخدام try .

هل لا أحد منزعج من هذا النوع من استخدام المحاولة:

البيانات: = try (json.Marshal (التفاصيل))

بغض النظر عن حقيقة أن خطأ التنظيم يمكن أن يؤدي إلى العثور على السطر الصحيح في الكود المكتوب ، أشعر بعدم الارتياح لأنني أعرف أن هذا خطأ عارٍ يتم إرجاعه بدون تضمين رقم سطر / معلومات المتصل. عادةً ما تكون معرفة الملف المصدر واسم الوظيفة ورقم السطر هي ما أقوم بتضمينه عند معالجة الأخطاء. ربما أنا أسيء فهم شيء ما بالرغم من ذلك.

griesemer لم أكن أخطط لمناقشة البدائل هنا. حقيقة أن الجميع يواصلون اقتراح البدائل هي بالضبط سبب اعتقادي أن إجراء استطلاع لمعرفة ما يريده الناس بالفعل سيكون فكرة جيدة. لقد نشرت للتو حول هذا الموضوع هنا لمحاولة التقاط أكبر عدد ممكن من الأشخاص المهتمين بإمكانية تحسين معالجة أخطاء Go.

@ Trende-jp أنا أعتمد حقًا على سياق هذا السطر من التعليمات البرمجية - كل ذلك في حد ذاته لا يمكن الحكم عليه بأي طريقة ذات معنى. إذا كان هذا هو الاستدعاء الوحيد لـ json.Marshal وتريد زيادة الخطأ ، فقد يكون كشف if هو الأفضل. إذا كان هناك الكثير من المكالمات json.Marshal ، فيمكن إضافة سياق إلى الخطأ بشكل جيد باستخدام defer ؛ أو ربما عن طريق تغليف كل هذه المكالمات داخل إغلاق محلي يقوم بإرجاع الخطأ. هناك العديد من الطرق التي يمكن من خلالها تحليل ذلك إلى عوامل إذا لزم الأمر (على سبيل المثال ، إذا كان هناك العديد من هذه الاستدعاءات في نفس الوظيفة). "الأخطاء هي قيم" صحيحة هنا أيضًا: استخدم التعليمات البرمجية لجعل معالجة الأخطاء قابلة للإدارة.

لن يقوم try بحل جميع مشكلات معالجة الأخطاء - هذا ليس القصد. إنها ببساطة أداة أخرى في صندوق الأدوات. وهي ليست آلية جديدة حقًا ، إنها شكل من أشكال السكر النحوي لنمط لاحظناه كثيرًا على مدار عقد تقريبًا. لدينا بعض الأدلة على أنها ستعمل بشكل جيد حقًا في بعض الأكواد ، وأنها أيضًا لن تكون ذات فائدة كبيرة في الكودات الأخرى.

@ Trende-jp

لا يمكن حلها مع defer ؟

defer fmt.HandleErrorf(&err, "decoding %q", path)

يمكن أيضًا حل أرقام الأسطر في رسائل الخطأ كما أوضحت في مدونتي: كيفية استخدام "try" .

@ trende- jpfaiface بالإضافة إلى رقم السطر ، يمكنك تخزين سلسلة الديكور في متغير. سيسمح لك هذا بعزل استدعاء الوظيفة المحدد الذي فشل.

أعتقد حقًا أن هذا لا ينبغي أن يكون وظيفة مضمنة على الإطلاق .

لقد ذكرنا عدة مرات أن panic() و recover() يغيران أيضًا تدفق التحكم. حسنًا ، دعونا لا نضيف المزيد.

كتبnetworkimprov https://github.com/golang/go/issues/32437#issuecomment -498960081 :

لا يقرأ مثل Go.

لا يمكن اقبل المزيد.

إذا كان هناك أي شيء ، فأنا أعتقد أن أي آلية لمعالجة مشكلة الجذر (ولست متأكدًا من وجودها) ، يجب أن يتم تشغيلها بواسطة كلمة رئيسية (أو رمز مفتاح؟).

ما هو شعورك إذا كان go func() سيكون go(func()) ؟

ماذا عن استخدام وظيفة bang (!) بدلاً من وظيفة try . هذا يمكن أن يجعل سلسلة الوظائف ممكنة:

func foo() {
    f := os.Open!("")
    defer f.Close()
    // etc
}

func bar() {
    count := mustErr!().Read!()
}

تضمين التغريدة

ما هو شعورك إذا انتقلت func () إلى (func ())؟

تعال ، سيكون ذلك مقبولًا جدًا.

sylr شكرًا ، لكننا لا نطلب مقترحات بديلة في هذا الموضوع. انظر أيضًا إلى هذا في التركيز.

فيما يتعلق بتعليقك : Go هي لغة عملية - استخدام لغة مدمجة هنا هو اختيار عملي. لها العديد من المزايا على استخدام الكلمة الأساسية كما هو موضح بالتفصيل في مستند التصميم . لاحظ أن try هو ببساطة سكر نحوي لنمط شائع (على عكس go الذي ينفذ ميزة رئيسية لـ Go ولا يمكن تنفيذه مع آليات Go الأخرى) ، مثل append ، copy ، إلخ. يعد استخدام المدمج اختيارًا جيدًا.

(ولكن كما قلت من قبل ، إذا كان هذا هو الشيء الوحيد الذي يمنع try من أن يكون مقبولاً ، فيمكننا التفكير في جعله كلمة رئيسية.)

كنت أفكر للتو في جزء من الكود الخاص بي ، وكيف سيبدو ذلك بـ try :

slurp, err := ioutil.ReadFile(path)
if err != nil {
    return err
}
return ioutil.WriteFile(path, append(copyrightText, slurp...), 0666)

يمكن أن يصبح:

return ioutil.WriteFile(path, append(copyrightText, try(ioutil.ReadFile(path))...), 0666)

لست متأكدًا مما إذا كان هذا أفضل. يبدو أنه يجعل قراءة التعليمات البرمجية أكثر صعوبة. لكن قد يكون الأمر مجرد مسألة التعود عليها.

gbbr لديك خيار هنا. يمكنك كتابتها على النحو التالي:

slurp := try(ioutil.ReadFile(path))
return ioutil.WriteFile(path, append(copyrightText, slurp...), 0666)

الذي لا يزال يوفر لك الكثير من النموذج المعياري ولكنه يجعله أكثر وضوحًا. هذا ليس ملازمًا لـ try . فقط لأنك تستطيع ضغط كل شيء في تعبير واحد لا يعني أنه يجب عليك ذلك. هذا ينطبق بشكل عام.

griesemer هذا المثال _is_ أصيل للمحاولة ، لا يجوز لك إدخال التعليمات البرمجية التي قد تفشل اليوم - فأنت مجبر على معالجة الأخطاء باستخدام التحكم في التدفق. أود الحصول على شيء تم مسحه من https://github.com/golang/go/issues/32825#issuecomment -507099786 / https://github.com/golang/go/issues/32825#issuecomment -507136111 الذي تريده أجاب https://github.com/golang/go/issues/32825#issuecomment -507358397. لاحقًا تمت مناقشة نفس المشكلة مرة أخرى في https://github.com/golang/go/issues/32825#issuecomment -508813236 و https://github.com/golang/go/issues/32825#issuecomment -508937177 - الأخير التي أذكر:

سعيد لأنك قرأت حجتي المركزية ضد المحاولة: التنفيذ ليس مقيدًا بدرجة كافية. أعتقد أن التنفيذ يجب أن يتطابق مع جميع أمثلة استخدام المقترحات الموجزة وسهلة القراءة.

_ أو_ يجب أن يحتوي الاقتراح على أمثلة تطابق التنفيذ بحيث يمكن لجميع الأشخاص الذين يفكرون في ذلك أن يتعرضوا لما سيظهر حتمًا في كود Go. إلى جانب جميع حالات الزاوية التي قد نواجهها عند استكشاف الأخطاء وإصلاحها أقل من البرامج المكتوبة بشكل مثالي ، والتي تحدث في أي لغة / بيئة. يجب أن يجيب على أسئلة مثل كيف ستبدو آثار المكدس مع مستويات متداخلة متعددة ، هل مواقع الأخطاء يمكن التعرف عليها بسهولة؟ ماذا عن قيم الطريقة ، القيم الحرفية للوظيفة المجهولة؟ ما نوع تتبع المكدس الذي ينتجه ما يلي إذا فشل السطر الذي يحتوي على استدعاءات fn ()؟

fn := func(n int) (int, error) { ... }
return try(func() (int, error) { 
    mu.Lock()
    defer mu.Unlock()
    return try(try(fn(111111)) + try(fn(101010)) + try(func() (int, error) {
       // yea...
    })(2))
}(try(fn(1)))

إنني أدرك جيدًا أنه سيكون هناك الكثير من التعليمات البرمجية المعقولة المكتوبة ، لكننا نقدم الآن أداة لم تكن موجودة من قبل: القدرة على كتابة التعليمات البرمجية بدون تدفق تحكم واضح. لذلك أريد أن أبرر لماذا نسمح بذلك في المقام الأول ، فأنا لا أريد أبدًا إضاعة وقتي في تصحيح هذا النوع من التعليمات البرمجية. لأنني أعلم أنني سأفعل ، علمتني التجربة أن شخصًا ما سيفعل ذلك إذا سمحت له بذلك. غالبًا ما يكون هذا الشخص غير مطلع.

يوفر Go أقل الطرق الممكنة للمطورين الآخرين وأنا لإضاعة وقت بعضنا البعض من خلال تقييدنا باستخدام نفس التركيبات الدنيوية. لا أريد أن أفقد ذلك بدون فائدة كبيرة. لا أعتقد أن عبارة "لأن المحاولة يتم تنفيذها كوظيفة" ستكون ذات فائدة كبيرة. هل يمكنك تقديم سبب لماذا هو؟

وجود تتبع مكدس يوضح أين سيكون الفشل أعلاه مفيدًا ، ربما إضافة حرف مركب مع الحقول التي تستدعي هذه الوظيفة في المزيج؟ أطلب هذا لأنني أعرف كيف تبدو تتبعات المكدس اليوم لهذا النوع من المشاكل ، لا يوفر Go معلومات عمود سهلة الهضم في معلومات المكدس فقط عنوان إدخال الوظيفة السداسية العشرية. هناك العديد من الأشياء التي تقلقني بشأن هذا ، مثل تناسق تتبع المكدس عبر البنى ، على سبيل المثال ضع في اعتبارك هذا الرمز:

package main
import "fmt"
func dopanic(b bool) int { if b { panic("panic") }; return 1 }
type bar struct { a, b, d int; b *bar }
func main() {
    fmt.Println(&bar{
        a: 1,
        c: 1,
        d: 1,
        b: &bar{
            a: 1,
            c: 1,
            d: dopanic(true) + dopanic(false),
        },
    })
}

لاحظ كيف فشل الملعب الأول عند الدوبانيك الأيسر ، والثاني على اليمين ، لكن كلاهما يطبع أثر كومة متطابق:
https://play.golang.org/p/SYs1r4hBS7O
https://play.golang.org/p/YMKkflcQuav

panic: panic

goroutine 1 [running]:
main.dopanic(...)
    /tmp/sandbox709874298/prog.go:7
main.main()
    /tmp/sandbox709874298/prog.go:27 +0x40

كنت أتوقع أن تكون الثانية + 0x41 أو بعض الإزاحة بعد 0x40 ، والتي يمكن استخدامها لتحديد المكالمة الفعلية التي فشلت في حالة الذعر. حتى لو حصلنا على الإزاحات السداسية العشرية الصحيحة ، فلن أتمكن من تحديد مكان حدوث الفشل بدون تصحيح أخطاء إضافي. اليوم هذه حالة متطرفة ، ونادرًا ما يواجهها الناس. إذا قمت بإصدار نسخة متداخلة من المحاولة ، فستصبح القاعدة ، حتى أن الاقتراح يتضمن try () + try () strconv يوضح أنه من الممكن والمقبول استخدام هذه الطريقة.

1) بالنظر إلى المعلومات الواردة أعلاه ، ما هي التغييرات التي تريد إجراؤها على عمليات التتبع المكدسة (إن وجدت) حتى لا يزال بإمكاني معرفة مكان فشل الكود الخاص بي؟

2) هل محاولة التعشيش مسموح بها لأنك تعتقد أنها يجب أن تكون كذلك؟ إذا كان الأمر كذلك ، فما الذي تراه فوائد محاولة التعشيش وكيف ستمنع إساءة الاستخدام؟ أعتقد أنه يجب تعديل tryhard لأداء محاولة متداخلة حيث تتصورها على أنها مقبولة حتى يتمكن الأشخاص من اتخاذ قرار أكثر استنارة حول كيفية تأثيرها على الكود ، نظرًا لأننا نحصل حاليًا على أفضل / أكثر أمثلة الاستخدام صرامة. سيعطينا هذا فكرة vet نوع القيود التي سيتم فرضها ، اعتبارًا من الآن ، قلت إن الطبيب البيطري هو الدفاع ضد المحاولات غير المعقولة ، ولكن كيف سيتحقق ذلك؟

3) هل حاول التداخل لأنه يحدث نتيجة للتنفيذ؟ إذا كان الأمر كذلك ، ألا تبدو هذه حجة ضعيفة جدًا لأبرز تغيير في اللغة منذ إصدار Go؟

أعتقد أن هذا التغيير يحتاج إلى مزيد من الدراسة حول محاولة التداخل. في كل مرة أفكر فيها ، تظهر بعض نقاط الألم الجديدة في مكان ما ، أشعر بقلق شديد من أن جميع السلبيات المحتملة لن تظهر حتى يتم الكشف عنها في البرية. يوفر التداخل أيضًا طريقة سهلة لتسريب الموارد كما هو مذكور في https://github.com/golang/go/issues/32825#issuecomment -506882164 وهو أمر غير ممكن اليوم. أعتقد أن قصة "الطبيب البيطري" تحتاج إلى خطة أكثر واقعية مع أمثلة على كيفية تقديمها للتغذية الراجعة إذا تم استخدامها كدفاع ضد المحاولة الضارة () الأمثلة التي قدمتها هنا ، أو يجب أن يوفر التنفيذ أخطاء وقت التجميع للاستخدام خارج أفضل ممارساتك المثالية.

تحرير: سألت في gophers عن هندسة play.golang.org وذكر أحدهم أنه يتم تجميعها عبر NaCl ، لذلك ربما تكون مجرد نتيجة / خطأ في ذلك. لكن يمكنني أن أرى أن هذه مشكلة في قوس آخر ، أعتقد أن الكثير من المشكلات التي يمكن أن تنشأ عن تقديم عوائد متعددة لكل سطر لم يتم استكشافها بالكامل لأن معظم الاستخدامات تتمحور حول استخدام خط واحد سليم ونظيف.

أوه لا ، من فضلك لا تضيف هذا "السحر" إلى اللغة.
هذه لا تبدو مثل بقية اللغة.
أرى بالفعل رمز مثل هذا يظهر في كل مكان.

a, b := try( f() )
if a != 0 && b != "" {
...
}
...

بدلا من

a,b,err := f()
if err != nil {
...
}
...

أو

a,b,_:= f()

كان الأب call if err.... غير طبيعي إلى حد ما في البداية بالنسبة لي ، لكنني الآن معتاد على ذلك
أشعر أنه من الأسهل التعامل مع الأخطاء لأنها قد تصل إلى تدفق التنفيذ بدلاً من كتابة أغلفة / معالجات سيتعين عليها تتبع نوع من الحالات للعمل بمجرد إطلاقها.
وإذا قررت تجاهل الأخطاء لإنقاذ حياة لوحة المفاتيح ، فأنا أدرك أنني سأصاب بالذعر يومًا ما.

حتى أنني غيرت عاداتي في vbscript إلى:

on error resume next
a = f()
if er.number <> 0 then
    ...
end if
...

يعجبني هذا الاقتراح

تتم معالجة جميع المخاوف التي لدي (على سبيل المثال ، من الأفضل أن تكون كلمة رئيسية وليست مضمنة) من خلال المستند المتعمق

إنه ليس مثاليًا بنسبة 100٪ ، لكنه حل جيد بما يكفي أ) يحل مشكلة فعلية و (ب) يفعل ذلك أثناء التفكير في الكثير من القضايا المتخلفة وغيرها

من المؤكد أنه يقوم ببعض "السحر" ولكنه يفعل ذلك أيضًا defer . الاختلاف الوحيد هو الكلمة الرئيسية مقابل المضمنة ، واختيار تجنب كلمة رئيسية هنا أمر منطقي.

أشعر أن جميع التعليقات المهمة ضد اقتراح try() قد تم التعبير عنها بالفعل. لكن دعني أحاول أن ألخص:

1) حاول () نقل تعقيد الكود العمودي إلى الوضع الأفقي
2) مكالمات try () المتداخلة يصعب قراءتها مثل العوامل الثلاثية
3) يقدم تدفق التحكم غير المرئي في "العودة" غير المميز بصريًا (مقارنة بالكتل ذات المسافات البادئة التي تبدأ بـ return الكلمة الرئيسية)
4) يجعل ممارسة التفاف الأخطاء أسوأ (سياق الوظيفة بدلاً من إجراء معين)
5) تقسيم المجتمع وأسلوب الكود (ضد الحكومة)
6) سيجعل devs يعيد كتابة المحاولة () إلى if-err-nil والعكس بالعكس كثيرًا (tryhard مقابل إضافة منطق التنظيف / سجلات إضافية / سياق خطأ أفضل)

VojtechVitek أعتقد أن النقاط التي تطرحها ذاتية ولا يمكن تقييمها إلا بمجرد أن يبدأ الناس في استخدامها بجدية.

ومع ذلك ، أعتقد أن هناك نقطة فنية واحدة لم تتم مناقشتها كثيرًا. نمط استخدام defer للالتفاف / الزخرفة للخطأ له آثار على الأداء تتجاوز التكلفة البسيطة defer نفسها حيث لا يمكن تضمين الدوال التي تستخدم defer .

هذا يعني أن اعتماد try مع التفاف الأخطاء يفرض تكلفتين محتملتين مقارنة بإرجاع خطأ ملفوف مباشرة بعد شيك err != nil :

  1. تأجيل لجميع المسارات من خلال الوظيفة ، حتى الناجحة منها
  2. فقدان البطانة

على الرغم من وجود بعض التحسينات المثيرة للإعجاب في الأداء مقابل defer ، فإن التكلفة لا تزال غير صفرية.

يحتوي try على الكثير من الإمكانات ، لذا سيكون من الجيد أن يقوم فريق Go بإعادة النظر في التصميم للسماح بنوع من الالتفاف عند نقطة الفشل بدلاً من القيام به بشكل استباقي عبر defer .

تحتاج قصة الطبيب البيطري إلى خطة أكثر واقعية

قصة الطبيب البيطري هي قصة خيالية. سيعمل فقط مع الأنواع المعروفة وسيكون عديم الفائدة على الأنواع المخصصة.

مرحبا جميعا،

هدفنا بمقترحات مثل هذا هو إجراء مناقشة على مستوى المجتمع حول الآثار والمقايضات وكيفية المضي قدمًا ، ثم استخدام هذه المناقشة للمساعدة في تحديد المسار إلى الأمام.

بناءً على استجابة المجتمع الساحقة والمناقشة المكثفة هنا ، فإننا نصنف هذا الاقتراح بالرفض قبل الموعد المحدد .

فيما يتعلق بالتعليقات الفنية ، فقد حددت هذه المناقشة بشكل مفيد بعض الاعتبارات المهمة التي فاتناها ، وأبرزها الآثار المترتبة على إضافة مطبوعات تصحيح الأخطاء وتحليل تغطية الكود.

والأهم من ذلك ، لقد سمعنا بوضوح العديد من الأشخاص الذين جادلوا بأن هذا الاقتراح لا يستهدف مشكلة جديرة بالاهتمام. ما زلنا نعتقد أن معالجة الأخطاء في Go ليست مثالية ويمكن تحسينها بشكل هادف ، ولكن من الواضح أننا كمجتمع نحتاج إلى التحدث أكثر عن الجوانب المحددة لمعالجة الأخطاء التي يجب أن نعالجها.

بقدر ما نناقش المشكلة التي يتعين حلها ، حاولنا وضع رؤيتنا للمشكلة في أغسطس الماضي في " نظرة عامة على مشكلة معالجة الخطأ في Go 2 " ، ولكن في وقت لاحق لم نلفت الانتباه الكافي إلى هذا الجزء ولم نشجع بما فيه الكفاية مناقشة حول ما إذا كانت المشكلة المحددة هي المشكلة الصحيحة. قد يكون عرض try حلاً جيدًا للمشكلة الموضحة هناك ، ولكن بالنسبة للكثيرين منكم ، لا يمثل حل المشكلة ببساطة. نحتاج في المستقبل إلى القيام بعمل أفضل للفت الانتباه إلى بيانات المشكلة المبكرة هذه والتأكد من وجود اتفاق واسع النطاق حول المشكلة التي تحتاج إلى حل.

(من الممكن أيضًا أن يكون بيان مشكلة معالجة الخطأ قد تم تغييره بالكامل من خلال نشر مسودة تصميم الأدوية الجنيسة في نفس اليوم.)

فيما يتعلق بالموضوع الأوسع حول ما يجب تحسينه بشأن معالجة أخطاء Go ، سنكون سعداء جدًا لرؤية تقارير الخبرة حول جوانب معالجة الأخطاء في Go التي تمثل أكثر إشكالية بالنسبة لك في قواعد التعليمات البرمجية وبيئات العمل الخاصة بك ومدى تأثير الحل الجيد لديك في التنمية الخاصة بك. إذا كتبت مثل هذا التقرير ، فالرجاء نشر ارتباط على صفحة Go2ErrorHandlingFeedback .

شكراً لكل من شارك في هذه المناقشة ، هنا وفي أي مكان آخر. كما أشار روس كوكس من قبل ، فإن المناقشات على مستوى المجتمع مثل هذه المناقشات مفتوحة المصدر في أفضل حالاتها . نحن نقدر حقًا مساعدة الجميع في دراسة هذا الاقتراح المحدد وبشكل عام في مناقشة أفضل الطرق لتحسين حالة معالجة الأخطاء في Go.

روبرت جريسيمر ، عن لجنة مراجعة العروض.

شكرًا لك ، فريق Go ، على العمل الذي تم إدخاله في اقتراح المحاولة. وشكرًا للمعلقين الذين كافحوا معها واقترحوا البدائل. في بعض الأحيان تأخذ هذه الأشياء حياة خاصة بها. شكرًا لك Go Team على الاستماع والرد بشكل مناسب.

ياي!

شكرًا للجميع على تفكيك هذا الأمر حتى نحصل على أفضل نتيجة ممكنة!

المكالمة قائمة بالمشكلات والتجارب السلبية مع معالجة أخطاء Go. ومع ذلك،
أنا وفريق العمل سعداء جدًا بـ xerrors. as ، xerrors.Is و xerrors.Errorf في الإنتاج. هذه الإضافات الجديدة تغير تمامًا معالجة الأخطاء بطريقة رائعة بالنسبة لنا الآن بعد أن احتضننا التغييرات بالكامل. في الوقت الحالي ، لم نواجه أية مشكلات أو احتياجات لم تتم معالجتها.

griesemer أردت فقط أن أشكرك (وربما العديد من الأشخاص الآخرين الذين عملوا معك) على صبرك وجهودك.

جيد!

griesemer شكرًا لك ولجميع الأشخاص الآخرين في فريق Go على الاستماع بلا كلل إلى جميع التعليقات وطرح جميع آرائنا المتنوعة.

إذن ، ربما يكون الوقت مناسبًا الآن لإغلاق هذا الموضوع والانتقال إلى الأمور المستقبلية؟

griesemer و rsc و @ all ، رائع ، شكرًا للجميع. بالنسبة لي ، إنها مناقشة / تحديد / توضيح رائع. تعزيز جزء ما مثل مشكلة "الخطأ" قيد التنفيذ ، تحتاج إلى مزيد من المناقشة المفتوحة (في الاقتراح والتعليقات ...) لتحديد / توضيح القضايا الأساسية أولاً.

ملاحظة ، فإن x / xerrors جيدة في الوقت الحالي.

(قد يكون من المنطقي قفل هذا الموضوع أيضًا ...)

شكرا للفريق والمجتمع للمشاركة في هذا. أحب عدد الأشخاص الذين يهتمون بـ Go.

آمل حقًا أن يرى المجتمع أولاً الجهد والمهارة التي تم إدخالها في اقتراح المحاولة في المقام الأول ، ثم روح المشاركة التي أعقبت ذلك والتي ساعدتنا في الوصول إلى هذا القرار. إن مستقبل Go مشرق للغاية إذا استطعنا مواكبة ذلك ، خاصة إذا كان بإمكاننا جميعًا الحفاظ على المواقف الإيجابية.

func M () (بيانات ، خطأ) {
أ ، يخطئ 1: = أ ()
ب ، خطأ 2: = ب ()
العودة ب ، لا شيء
} => (إذا كان خطأ 1! = لا شيء) {إرجاع a ، err1}.
(إذا كان خطأ 2! = لا شيء) {إرجاع ب ، يخطئ 2}

حسنًا ... لقد أحببت هذا الاقتراح ولكني أحب الطريقة التي تفاعل بها فريق community and Go وشاركوا في مناقشة بناءة ، على الرغم من أنه كان أحيانًا قاسيًا بعض الشيء.

لدي سؤالان بخصوص هذه النتيجة:
1 / هل "معالجة الأخطاء" ما زالت مجال بحث؟
2 / هل يتم إعادة ترتيب أولويات التحسينات المؤجلة؟

وهذا يثبت مرة أخرى أنه يتم الاستماع إلى مجتمع Go وقدرته على مناقشة مقترحات تغيير اللغة المثيرة للجدل. مثل التغييرات التي تم إجراؤها على اللغة ، فإن التغييرات التي لا تُعد تحسينًا. شكرًا لك ، فريق Go والمجتمع ، على العمل الجاد والمناقشة المتحضرة حول هذا الاقتراح!

ممتاز!

رهيبة , مفيدة للغاية

ربما أكون مرتبطًا جدًا بـ Go ، لكنني أعتقد أنه تم عرض نقطة هنا ، مثل
وصف روس: هناك نقطة حيث المجتمع ليس مجرد
دجاجة مقطوعة الرأس ، إنها قوة يحسب لها حساب
تسخر لمصلحتها.

بفضل التنسيق الذي قدمه فريق Go ، يمكننا ذلك
كلنا فخورون بأننا توصلنا إلى نتيجة ، واحدة يمكننا التعايش معها و
ستعاود الزيارة ، بلا شك ، عندما تكون الظروف أكثر نضجًا.

دعونا نأمل أن يخدمنا الألم الذي نشعر به هنا بشكل جيد في المستقبل
(ألن يكون حزينًا ، وإلا؟).

لوسيو.

أنا لا أوافق على القرار. ومع ذلك ، فإنني أؤيد تمامًا النهج الذي اتخذه فريق go go. إن إجراء مناقشة واسعة من المجتمع والنظر في التعليقات الواردة من المطورين هو ما يعنيه المصدر المفتوح.

أتساءل عما إذا كانت تحسينات التأجيل ستأتي أيضًا. أنا أحب التعليق التوضيحي للأخطاء بها و xerrors معًا كثيرًا جدًا وهي مكلفة للغاية في الوقت الحالي.

pierrec أعتقد أننا بحاجة إلى فهم أوضح للتغييرات في معالجة الأخطاء التي قد تكون مفيدة. ستكون بعض تغييرات قيم الخطأ في الإصدار 1.13 القادم (https://tip.golang.org/doc/go1.13#errors) ، وسنكتسب الخبرة معها. في سياق هذه المناقشة ، رأينا العديد من مقترحات معالجة الأخطاء النحوية ، وسيكون من المفيد إذا كان بإمكان الأشخاص التصويت والتعليق على أي منها يبدو مفيدًا بشكل خاص. بشكل عام ، كما قال griesemer ، ستكون تقارير التجربة مفيدة.

قد يكون من المفيد أيضًا أن نفهم بشكل أفضل إلى أي مدى يمثل معالجة الأخطاء في بناء الجملة مشكلة للأشخاص الجدد في اللغة ، على الرغم من صعوبة تحديد ذلك.

هناك عمل نشط لتحسين أداء defer في https://golang.org/cl/183677 ، وما لم تتم مواجهة عقبة كبيرة ، أتوقع أن يصل هذا إلى الإصدار 1.14.

griesemerianlancetaylorrsc هل ما زلت تخطط لمعالجة الخطأ في معالجة الإسهاب ، مع اقتراح آخر لحل بعض أو كل المشكلات التي أثيرت هنا؟

إذن ، متأخرًا بالنسبة للحزب ، نظرًا لأنه تم رفض هذا بالفعل ، ولكن بالنسبة للمناقشة المستقبلية حول هذا الموضوع ، ماذا عن صيغة العودة المشروطة الثلاثية؟ (لم أر شيئًا مشابهًا لهذا في المسح الذي أجريته للموضوع أو بالنظر إلى عرضه المنشور على موقع Russ Cox على Twitter.) مثال:

f, err := Foo()
return err != nil ? nil, err

إرجاع nil, err إذا كان Error غير صفري ، يستمر التنفيذ إذا كان الخطأ لا شيء. سيكون شكل البيان

return <boolean expression> ? <return values>

وسيكون هذا سكرًا نحويًا من أجل:

if <boolean expression> {
    return <return values>
}

الفوائد الأساسية هي أن هذا أكثر مرونة من الكلمة الرئيسية check أو الوظيفة المضمنة try ، لأنه يمكن أن يؤدي إلى أكثر من الأخطاء (على سبيل المثال ، return err != nil || f == nil ? nil, fmt.Errorf("failed to get Foo") ، على المزيد من مجرد كون الخطأ ليس صفريًا (على سبيل المثال return err != nil && err != io.EOF ? nil, err ) ، وما إلى ذلك ، مع كونه بديهيًا إلى حد ما للفهم عند القراءة (خاصةً لأولئك الذين اعتادوا على قراءة العوامل الثلاثية بلغات أخرى).

كما أنه يضمن أن معالجة الأخطاء _لا تزال تحدث في موقع المكالمة_ ، بدلاً من حدوثها تلقائيًا بناءً على بعض عبارات التأجيل. من أكبر الأمور التي استحوذت عليها فيما يتعلق بالاقتراح الأصلي هو أنه يحاول ، من بعض النواحي ، جعل _المعالجة الفعلية للأخطاء عمليات ضمنية تحدث تلقائيًا عندما يكون الخطأ غير معدوم ، مع عدم وجود إشارة واضحة إلى أن تدفق التحكم سيعود إذا قام استدعاء الوظيفة بإرجاع خطأ غير صفري. إن النقطة_ الكاملة لـ Go باستخدام إرجاع خطأ صريح بدلاً من نظام يشبه الاستثناء هو تشجيع المطورين على التحقق من أخطائهم ومعالجتها بشكل صريح ومتعمد ، بدلاً من مجرد السماح لهم بنشر المكدس ليتم التعامل معهم ، من الناحية النظرية ، في نقطة ما أعلى أعلى. تشير عبارة إرجاع صريحة على الأقل ، إذا كانت مشروطة ، بوضوح إلى ما يحدث.

ngrilly كما قال griesemer ، أعتقد أننا بحاجة إلى فهم أفضل لجوانب معالجة الأخطاء التي يجدها مبرمجو Go الأكثر إشكالية.

عند الحديث شخصيًا ، لا أعتقد أن الاقتراح الذي يزيل قدرًا صغيرًا من الإسهاب يستحق القيام به. بعد كل شيء ، اللغة تعمل بشكل جيد بما فيه الكفاية اليوم. كل تغيير له تكلفة. إذا أردنا إجراء تغيير ، فنحن بحاجة إلى فائدة كبيرة. أعتقد أن هذا الاقتراح قدم فائدة كبيرة في تقليل الإسهاب ، ولكن من الواضح أن هناك شريحة كبيرة من مبرمجي Go يشعرون أن التكاليف الإضافية التي فرضها كانت باهظة للغاية. لا أعرف ما إذا كان هناك حل وسط هنا. ولا أعرف ما إذا كانت المشكلة تستحق المعالجة على الإطلاق.

kaedys هذه المشكلة المغلقة والمطولة للغاية ليست بالتأكيد المكان المناسب لمناقشة صيغ بديلة محددة لمعالجة الأخطاء.

تضمين التغريدة

أعتقد أن هذا الاقتراح قدم فائدة كبيرة في تقليل الإسهاب ، ولكن من الواضح أن هناك شريحة كبيرة من مبرمجي Go يشعرون أن التكاليف الإضافية التي فرضها كانت باهظة للغاية.

أخشى أن يكون هناك تحيز في الاختيار الذاتي. تشتهر Go بمعالجتها المطولة للخطأ وافتقارها إلى الأدوية الجنيسة. يجذب هذا بطبيعة الحال المطورين الذين لا يهتمون بهاتين المشكلتين. في غضون ذلك ، يواصل المطورون الآخرون استخدام لغاتهم الحالية (Java و C ++ و C # و Python و Ruby وما إلى ذلك) و / أو التبديل إلى لغات أكثر حداثة (Rust و TypeScript و Kotlin و Swift و Elixir وما إلى ذلك) بسبب هذا . أعرف العديد من المطورين الذين يتجنبون Go غالبًا لهذا السبب.

أعتقد أيضًا أن هناك تحيزًا تأكيديًا في اللعب. تم استخدام Gophers للدفاع عن معالجة الخطأ المطول وعدم معالجة الخطأ عندما ينتقد الناس Go. هذا يجعل من الصعب تقييم اقتراح بموضوعية مثل المحاولة.

نشر ستيف كلابنيك تعليقًا مثيرًا للاهتمام على Reddit قبل أيام قليلة. كان ضد إدخال ? في Rust ، لأنها كانت "طريقتين لكتابة نفس الشيء" وكانت "ضمنية للغاية". ولكن الآن ، بعد كتابة أكثر من بضعة أسطر من التعليمات البرمجية ، فإن ? هي إحدى ميزاته المفضلة.

ngrilly أتفق مع تعليقاتك. من الصعب للغاية تجنب هذه التحيزات. ما سيكون مفيدًا للغاية هو فهم أوضح لعدد الأشخاص الذين يتجنبون Go بسبب معالجة الخطأ المطول. أنا متأكد من أن الرقم ليس صفريًا ، لكن من الصعب قياسه.

ومع ذلك ، فمن الصحيح أيضًا أن try أدخل تغييرًا جديدًا في تدفق التحكم كان من الصعب رؤيته ، وعلى الرغم من أن try كان يهدف إلى المساعدة في معالجة الأخطاء ، إلا أنه لم يساعد في إضافة التعليقات التوضيحية. .

شكرا على الاقتباس من ستيف كلابنيك. على الرغم من أنني أقدر المشاعر وأوافق عليها ، إلا أنه يجدر النظر في أنه كلغة يبدو أن Rust أكثر استعدادًا إلى حد ما للاعتماد على التفاصيل النحوية أكثر من Go.

بصفتي مؤيدًا لهذا الاقتراح ، أشعر بخيبة أمل بطبيعة الحال لأنه تم سحبه الآن على الرغم من أنني أعتقد أن فريق Go قد فعل الشيء الصحيح في هذه الظروف.

هناك شيء واحد يبدو واضحًا تمامًا الآن وهو أن غالبية مستخدمي Go لا يعتبرون الإسهاب في معالجة الخطأ مشكلة وأعتقد أن هذا شيء سيتعين على بقيتنا التعايش معه حتى لو أدى ذلك إلى تأجيل المستخدمين الجدد المحتملين .

لقد فقدت عدد المقترحات البديلة التي قرأتها ، وفي حين أن بعضها جيد جدًا ، لم أر أيًا اعتقدت أنه يستحق التبني إذا كان try سيعض الغبار. لذا فإن فرصة ظهور بعض المقترحات الوسطية الآن تبدو بعيدة بالنسبة لي.

في ملاحظة أكثر إيجابية ، أشارت المناقشة الحالية إلى الطرق التي يمكن بها تزيين جميع الأخطاء المحتملة في الوظيفة بنفس الطريقة وفي نفس المكان (باستخدام defer أو حتى goto ) التي لم أكن قد فكرت فيها سابقًا وآمل أن يفكر فريق Go على الأقل في تغيير go fmt للسماح بكتابة كشف واحد if في سطر واحد مما سيؤدي على الأقل إلى معالجة الأخطاء _ look_ أكثر إحكاما حتى لو لم يزيل أي معيار معياري.

تضمين التغريدة

1 / هل "معالجة الأخطاء" ما زالت مجال بحث؟

لقد كان ، منذ أكثر من 50 عامًا! لا يبدو أن هناك نظرية شاملة أو حتى دليل عملي لمعالجة الأخطاء بشكل منتظم ومنهجي. في Go Land (كما هو الحال بالنسبة للغات الأخرى) هناك ارتباك حول ماهية الخطأ. على سبيل المثال ، قد يكون EOF حالة استثنائية عندما تحاول قراءة ملف ولكن لماذا يعد خطأ؟ سواء كان هذا خطأ فعليًا أم لا يعتمد حقًا على السياق. وهناك قضايا أخرى من هذا القبيل.

ربما تكون هناك حاجة إلى مناقشة على مستوى أعلى (ولكن ليس هنا).

شكرًا لك griesemerrsc وكل شخص آخر معني بالاقتراح . قالها كثيرون آخرون أعلاه ، وتجدر الإشارة إلى أن جهودك في التفكير في المشكلة ، وكتابة الاقتراح ، ومناقشتها بحسن نية ، هي موضع تقدير. شكرا لك.

تضمين التغريدة

شكرا على الاقتباس من ستيف كلابنيك. على الرغم من أنني أقدر المشاعر وأوافق عليها ، إلا أنه يجدر النظر في أنه كلغة يبدو أن Rust أكثر استعدادًا إلى حد ما للاعتماد على التفاصيل النحوية أكثر من Go.

أوافق بشكل عام على اعتماد Rust أكثر من Go على التفاصيل النحوية ، لكن لا أعتقد أن هذا ينطبق على هذه المناقشة المحددة حول معالجة الخطأ الإسهاب.

الأخطاء هي قيم في Rust كما هي في Go. يمكنك التعامل معها باستخدام تدفق التحكم القياسي ، كما هو الحال في Go. في الإصدارات الأولى من Rust ، كانت هذه هي الطريقة الوحيدة للتعامل مع الأخطاء ، كما هو الحال في Go. ثم قاموا بتقديم الماكرو try! ، والذي يشبه بشكل مدهش اقتراح الوظيفة المضمنة try . أضافوا في النهاية عامل التشغيل ? ، وهو تباين نحوي وتعميم للماكرو try! ، لكن هذا ليس ضروريًا لإثبات فائدة try ، والحقيقة أن مجتمع Rust لا يندم على إضافته.

إنني أدرك جيدًا الاختلافات الهائلة بين Go و Rust ، ولكن فيما يتعلق بموضوع معالجة الخطأ الإسهاب ، أعتقد أن تجربتهم يمكن نقلها إلى Go. طلبات التعليقات والمناقشات المتعلقة بـ try! و ? تستحق القراءة حقًا. لقد فوجئت حقًا بمدى تشابه القضايا والحجج المؤيدة لتغييرات اللغة والمعارضة لها.

griesemer ، لقد أعلنت عن قرار رفض عرض try في شكله الحالي ، لكنك لم تقل ما يخطط فريق Go للقيام به بعد ذلك.

هل ما زلت تخطط لمعالجة الإسهاب في معالجة الأخطاء ، مع اقتراح آخر من شأنه حل المشكلات التي أثيرت في هذه المناقشة (طباعة تصحيح الأخطاء ، وتغطية الكود ، وزخرفة أخطاء أفضل ، وما إلى ذلك)؟

أوافق بشكل عام على اعتماد Rust أكثر من Go على التفاصيل النحوية ، لكن لا أعتقد أن هذا ينطبق على هذه المناقشة المحددة حول معالجة الخطأ الإسهاب.

نظرًا لأن الآخرين لا يزالون يضيفون سنتهم ، أعتقد أنه لا يزال هناك متسع لي لأفعل الشيء نفسه.

على الرغم من أنني أعمل في البرمجة منذ عام 1987 ، إلا أنني أعمل مع Go لمدة عام تقريبًا. منذ حوالي 18 شهرًا عندما كنت أبحث عن لغة جديدة لتلبية احتياجات معينة ، نظرت إلى كل من Go و Rust. قررت الذهاب لأنني شعرت أن كود Go كان أسهل في التعلم والاستخدام ، وأن كود Go كان أكثر قابلية للقراءة لأن Go يبدو أنه يفضل الكلمات لنقل المعنى بدلاً من الرموز المقتضبة.

لذلك سأكون غير سعيد للغاية لرؤية Go أصبح أكثر شبهاً بالصدأ ، بما في ذلك استخدام علامات التعجب ( ! ) وعلامات الاستفهام ( ? ) للإشارة إلى المعنى.

على نفس المنوال ، أعتقد أن إدخال وحدات الماكرو سيغير طبيعة Go وسيؤدي إلى الآلاف من لهجات Go كما هو الحال مع Ruby بشكل فعال. لذلك آمل ألا تتم إضافة وحدات الماكرو Go ، أيضًا ، على الرغم من تخميني أن هناك فرصة ضئيلة لحدوث ذلك ، لحسن الحظ IMO.

جمتك

تضمين التغريدة

ما سيكون مفيدًا للغاية هو فهم أوضح لعدد الأشخاص الذين يتجنبون Go بسبب معالجة الخطأ المطول. أنا متأكد من أن الرقم ليس صفريًا ، لكن من الصعب قياسه.

إنه ليس "مقياسًا" في حد ذاته ، ولكن مناقشة Hacker News هذه توفر عشرات التعليقات من المطورين غير الراضين عن معالجة أخطاء Go نظرًا لإسهابها (وتشرح بعض التعليقات أسبابها وتعطي أمثلة على التعليمات البرمجية): https: //news.ycombinator. كوم / العنصر؟ معرف = 20454966.

بادئ ذي بدء ، أشكر الجميع على التعليقات الداعمة على القرار النهائي ، حتى لو لم يكن هذا القرار مرضيًا للكثيرين. لقد كان هذا حقًا جهدًا جماعيًا ، وأنا سعيد حقًا لأننا تمكنا جميعًا من اجتياز المناقشات المكثفة بطريقة مدنية ومحترمة بشكل عام.

ngrilly أتحدث عن نفسي فقط ، ما زلت أعتقد أنه سيكون من الجيد معالجة الخطأ في معالجة الإسهاب في مرحلة ما. بعد قولي هذا ، لقد كرسنا للتو قدرًا كبيرًا من الوقت والطاقة لهذا على مدار نصف العام الماضي وخاصة الأشهر الثلاثة الماضية ، وكنا سعداء جدًا بالاقتراح ، ومع ذلك فمن الواضح أننا قللنا من أهمية رد الفعل المحتمل تجاهه. من المنطقي الآن التراجع ، واستيعاب الملاحظات وتقطيرها ، ثم اتخاذ قرار بشأن أفضل الخطوات التالية.

أيضًا ، من الناحية الواقعية ، نظرًا لأننا لا نملك موارد غير محدودة ، أرى التفكير في دعم اللغة للتعامل مع الأخطاء يتجه إلى الخلف قليلاً لصالح المزيد من التقدم على الجبهات الأخرى ، وأبرزها العمل على الأدوية الجنيسة ، على الأقل بالنسبة لـ الأشهر القليلة القادمة. قد يكون if err != nil مزعجًا ، لكنه ليس سببًا لاتخاذ إجراء عاجل.

إذا كنت ترغب في مواصلة المناقشة ، أود أن أقترح برفق على الجميع الانتقال من هنا ومواصلة المناقشة في مكان آخر ، في قضية منفصلة (إذا كان هناك اقتراح واضح) ، أو في منتديات أخرى أكثر ملاءمة للمناقشة المفتوحة. هذه القضية مغلقة ، بعد كل شيء. شكرا.

أخشى أن يكون هناك تحيز في الاختيار الذاتي.

أود أن أصوغ مصطلحًا جديدًا هنا والآن: "تحيز منشئ المحتوى". إذا كان شخص ما على استعداد لوضع العمل ، فيجب إعطاؤه فائدة الشك.

من السهل جدًا على معرض الفول السوداني أن يصرخ بصوت عالٍ وواسع في المنتديات غير ذات الصلة كيف يكرهون حلًا مقترحًا لمشكلة ما. من السهل جدًا أيضًا على الجميع كتابة محاولة غير مكتملة من 3 فقرات لحل مختلف (مع عدم تقديم عمل حقيقي على الهامش). إذا وافق المرء على الوضع الراهن ، فلا بأس. نقطة عادلة. إن تقديم أي شيء آخر على أنه حل بدون طرح كامل يمنحك -10 آلاف نقطة.

أنا لا أؤيد المحاولة أو أعارضها ، لكنني أثق في حكم Go Teams بشأن الأمر ، حتى الآن قدم حكمهم بلغة ممتازة ، لذلك أعتقد أن كل ما يقررونه سيعمل معي ، حاول أو لا تحاول ، أنا أعتبر نحن بحاجة إلى أن نفهم كأجانب ، أن القائمين على الصيانة لديهم رؤية أوسع لهذه المسألة. يمكننا مناقشة النحو طوال اليوم. أود أن أشكر كل من عمل أو يحاول تحسين go في الوقت الحالي على جهودهم ، ونحن ممتنون ونتطلع إلى تحسينات جديدة (غير مقلقة للخلف) في مكتبات اللغة ووقت التشغيل إن وجدت مفيد لكم يا رفاق.

من السهل جدًا أيضًا على الجميع كتابة محاولة غير مكتملة من 3 فقرات لحل مختلف (مع عدم تقديم عمل حقيقي على الهامش).

الشيء الوحيد الذي أردت (وعدد من الآخرين) جعل try مفيدًا كان وسيطة اختيارية للسماح له بإرجاع نسخة مغلفة من الخطأ بدلاً من الخطأ الذي لم يتغير. لا أعتقد أن هذا يحتاج إلى قدر كبير من أعمال التصميم.

أوه لا.

أنا أرى. اذهب تريد أن تجعل شيئًا مختلفًا عن اللغات الأخرى.

ربما يجب على شخص ما قفل هذه القضية؟ ربما تكون المناقشة مناسبة بشكل أفضل في مكان آخر.

هذه المشكلة طويلة جدًا لدرجة أن قفلها يبدو غير مجدٍ.

الجميع ، يرجى الانتباه إلى أن هذه المشكلة قد تم إغلاقها ، ومن شبه المؤكد أن التعليقات التي تدلي بها هنا سيتم تجاهلها إلى الأبد. إذا كان هذا مناسبًا لك ، فقم بالتعليق بعيدًا.

في حالة كره شخص ما لكلمة المحاولة التي سمحت له بالتفكير في لغة Java ، C * ، أنصح بعدم استخدام "try" ولكن كلمات أخرى مثل "help" أو "must" أو "checkError" .. (تجاهلي)

في حالة كره شخص ما لكلمة المحاولة التي سمحت له بالتفكير في لغة Java ، C * ، أنصح بعدم استخدام "try" ولكن كلمات أخرى مثل "help" أو "must" أو "checkError" .. (تجاهلي)

ستكون هناك دائمًا كلمات رئيسية ومفاهيم متداخلة لها اختلافات دلالية صغيرة أو كبيرة في اللغات القريبة بشكل معقول من بعضها البعض (مثل لغات عائلة C). يجب ألا تسبب ميزة اللغة تشويشًا داخل اللغة نفسها ، فستحدث دائمًا الاختلافات بين اللغات.

سيئة. هذا هو نمط مناهض ، عدم احترام كاتب ذلك الاقتراح

alersenkevich يرجى أن تكون مهذبا. يرجى الاطلاع على https://golang.org/conduct.

أعتقد أنني سعيد لقرار عدم المضي قدمًا في هذا الأمر. بالنسبة لي ، شعرت وكأنه اختراق سريع لحل مشكلة صغيرة تتعلق بما إذا كان خطأ! = لا شيء في أسطر متعددة. لا نريد أن نطلق العنان للكلمات الرئيسية البسيطة لحل أشياء ثانوية مثل هذه ، هل نحن؟ هذا هو السبب في أن الاقتراح الخاص بوحدات الماكرو الصحية https://github.com/golang/go/issues/32620 يبدو أفضل. يحاول أن يكون حلاً أكثر عمومية لفتح المزيد من المرونة مع المزيد من الأشياء. لا تزال مناقشة التركيب والاستخدام مستمرة هناك ، لذلك لا تفكر فقط إذا كانت وحدات ماكرو C / C ++. النقطة المهمة هي مناقشة طريقة أفضل لعمل وحدات الماكرو. مع ذلك ، يمكنك تنفيذ المحاولة الخاصة بك.

أود الحصول على تعليقات على اقتراح مشابه يعالج مشكلة معالجة الخطأ الحالي https://github.com/golang/go/issues/33161.

بصراحة ، يجب إعادة فتح هذا ، من بين جميع مقترحات معالجة الأخطاء ، هذا هو الأكثر عقلانية.

OneOfOne باحترام ، لا أوافق على إعادة فتح هذا. لقد أثبت هذا الخيط أن هناك قيودًا حقيقية في بناء الجملة. ربما أنت محق في أن هذا هو الاقتراح الأكثر "عقلانية": لكنني أعتقد أن الوضع الراهن لا يزال أكثر عقلانية.

أوافق على أن if err != nil تتم كتابته كثيرًا جدًا في Go- ولكن وجود طريقة فريدة للعودة من وظيفة يؤدي إلى تحسين قابلية القراءة بشكل كبير. بينما يمكنني عمومًا الحصول على المقترحات التي تقلل من الشفرة المعيارية ، يجب ألا تكون التكلفة أبدًا قابلية القراءة IMHO.

أعلم أن الكثير من المطورين يأسفون على التحقق من الخطأ "الطويل" ، ولكن بصراحة غالبًا ما يتعارض الإيجاز مع سهولة القراءة. لدى Go العديد من الأنماط الراسخة هنا وفي أي مكان آخر والتي تشجع على طريقة معينة للقيام بالأشياء ، وفي تجربتي ، فإن النتيجة هي رمز موثوق به يتقدم في العمر. هذا أمر بالغ الأهمية: يجب قراءة رمز العالم الحقيقي وفهمه عدة مرات طوال حياته ، ولكن تتم كتابته مرة واحدة فقط. النفقات المعرفية هي تكلفة حقيقية ، حتى للمطورين ذوي الخبرة.

بدلا من:

f := try(os.Open(filename))

كنت أتوقع:

f := try os.Open(filename)

الجميع ، يرجى الانتباه إلى أن هذه المشكلة قد تم إغلاقها ، ومن شبه المؤكد أن التعليقات التي تدلي بها هنا سيتم تجاهلها إلى الأبد. إذا كان هذا مناسبًا لك ، فقم بالتعليق بعيدًا.
- @ ianlancetaylor

سيكون من الرائع لو تمكنا من استخدام مجموعة من الرموز جنبًا إلى جنب مع الطريقة الحالية للتعامل مع الأخطاء.
شيء من هذا القبيل:

// Generic Error Handler
handler := func(err error) error {
    return fmt.Errorf("We encounter an error: %v", err)  
}
a := "not Integer"
b := "not Integer"

try(handler){
    f := os.Open(filename)
    x := strconv.Atoi(a)
    y, err := strconv.Atoi(b) // <------ If you want a specific error handler
    if err != nil {
        panic("We cannot covert b to int")   
    }
}

يبدو الرمز أعلاه أنظف من التعليق الأولي. أتمنى أن أتمكن من هذا الغرض.

لقد قدمت اقتراحًا جديدًا رقم 35179

val: = حاول f () (يخطئ) {
الذعر (يخطئ)
}

أنا امل ذلك:

i, err := strconv.Atoi("1")
if err {
    println("ERROR")
} else {
    println(i)
}

أو

i, err := strconv.Atoi("1")
if err {
    io.EOF:
        println("EOF")
    io.ErrShortWrite:
        println("ErrShortWrite")
} else {
    println(i)
}

myroid لا أمانع في جعل مثالك الثاني أكثر عمومية في شكل عبارة switch-else :

"اذهب
ط ، يخطئ: = strconv.Atoi ("1")
التبديل يخطئ! يخطئ {
حالة io.EOF:
println ("EOF")
حالة io.ErrShortWrite:
println ("ErrShortWrite")
} آخر {
println (أنا)
}

piotrkowalczuk تبدو شفرتك أفضل بكثير من شفراتي. أعتقد أن الكود يمكن أن يكون أكثر إيجازًا.

i, err := strconv.Atoi("1")
switch err {
case io.EOF:
    println("EOF")
case io.ErrShortWrite:
    println("ErrShortWrite")
} else {
    println(i)
}

هذا لا يعتبر الخيار سيكون هناك عين من نوع مختلف

يجب أن يكون هناك
القضية يخطئ! = لا شيء

بالنسبة للأخطاء التي لم يلتقطها المطور بشكل صريح

في الجمعة ، 8 نوفمبر 2019 ، الساعة 12:06 ، كتب Yang Fan ، [email protected] :

piotrkowalczuk https://github.com/piotrkowalczuk يبدو الرمز الخاص بك كثيرًا
أفضل من خاصتي. أعتقد أن الكود يمكن أن يكون أكثر إيجازًا.

i ، يخطئ: = strconv.Atoi ("1") التبديل يخطأ {حالة io.EOF:
println ("EOF") حالة io.ErrShortWrite:
println ("ErrShortWrite")
} آخر {
println (أنا)
}

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/golang/go/issues/32437؟email_source=notifications&email_token=ABNEY4VH4KS2OX4M5BVH673QSU24DA5CNFSM4HTGCZ72YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63JLNMVX00
أو إلغاء الاشتراك
https://github.com/notifications/unsubscribe-auth/ABNEY4W4XIIHGUGIW2KXRPTQSU24DANCNFSM4HTGCZ7Q
.

لا يحتاج switch إلى else ، فهو يحتوي على default .

لقد فتحت https://github.com/golang/go/issues/39890 الذي يقترح شيئًا مشابهًا لـ Swift guard يجب أن يعالج بعض المخاوف المتعلقة بهذا الاقتراح:

  • تدفق التحكم
  • مكان معالجة الخطأ
  • مقروئية

لم يكتسب الكثير من الزخم ولكن قد يكون محل اهتمام أولئك الذين علقوا هنا.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات