المسارات
/
Julia
Julia
/
التمارين
/
مستشعرات المصنع
مستشعرات المصنع

مستشعرات المصنع

تمرين تعلّمي

مقدمة

يحاول المبرمجون عمومًا كتابة برمجيات مثالية، وغالبًا ما يفشلون.

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

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

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

رأينا في مفهوم العدم أن Julia توفر عدة قيم نائبة للقيم الإشكالية: missing وNaN وInf. وما إذا كانت هذه أفضل من إنهاء البرنامج في موقف معين هو أمر متروك لتقدير المبرمج.

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

أنواع الأخطاء القياسية

بحلول هذه المرحلة من المنهج الدراسي، لا بد أنك رأيت الكثير من رسائل الأخطاء من Julia. على سبيل المثال:

julia> Int(3.14)
ERROR: InexactError: Int64(3.14)

تحويل عدد عشري إلى عدد صحيح يتضمن فقدانًا للدقة، لذا نحصل على InexactError.

InexactError نوع، وهو واحد من عدة أنواع (حاليًا 25) مدمجة في Julia بشكل قياسي. وكلها أنواع فرعية من Exception:

julia> supertype(InexactError)
Exception

throw()

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

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

julia> DomainError(42, "out of range")
DomainError(42, "out of range")

لاستخدام الخطأ، غلّف المنشئ داخل دالة throw():

julia> throw(DomainError(42, "out of range"))
ERROR: DomainError with 42:
out of range

error()

للحصول على حلٍّ سريع، قد تكون دالة error() مريحة. فهي تأخذ سلسلة نصية (أو مكوّنات سلسلة نصية) كوسيط:

julia> happy = false;
julia> happy || error("😞 something went wrong")
ERROR: 😞 something went wrong

أخطاء مخصصة

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

julia> struct MyError <: Exception end

julia> throw(MyError)
ERROR: MyError

التأكيدات

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

رأينا في مفهوم الأنواع أنه يمكننا إضافة تأكيدات النوع، مثل التحقق من نوع الإرجاع لدالة.

julia> 42::Number
42

julia> "two"::Number
ERROR: TypeError: in typeassert, expected Number, got a value of type String

وبشكل أعم، تتيح لنا الماكرو @assert اختبار أي تعبير يُقيَّم إلى قيمة منطقية:

julia> n = 22;
julia> @assert isodd(n) "n must be odd"
ERROR: AssertionError: n must be odd

try...catch

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

افتراضيًا، يُنهي الخطأ الدالة الحالية فورًا، ويُمرَّر الخطأ (مع أي رسالة مفيدة) إلى الدالة المستدعية.

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

وفي أي مرحلة، يمكن اعتراض الخطأ بواسطة كتلة try...catch التي تحاول التعامل معه.

julia> n = -1;
julia> try
           log_n = log(n)
       catch problem
           if problem isa DomainError # number out of range
               # See next section for more on @warn and @info
               @warn "you may have supplied a negative real number: $n"
               @info "trying with complex argument"
               log_n = log(Complex(n))  # fallback calculation

           elseif problem isa MethodError # no idea what n is
               @error "please supply a valid argument"
 
           else
              rethrow() # the error could be anything else
           end
      end
┌ Warning: you may have supplied a negative real number: -1
└ @ Main REPL[3]:5
[ Info: trying with complex argument
0.0 + 3.141592653589793im  # success

في المثال أعلاه، تحتاج log(n) إلى أن تكون n قيمة حقيقية موجبة أو أي قيمة مركّبة. وتلتقط try ... catch المشكلات المتعلقة بالقيم الحقيقية السالبة، فتُرجع الإجابة المركّبة الصحيحة iπ بالترميز الرياضي.

إذا مرّرت، على سبيل المثال، وسيطًا من نوع سلسلة نصية، فلا يوجد تعافٍ إلا بمطالبة المستخدم بتصحيحه.

وكحل أخير شامل، أضفنا rethrow() لأي خطأ ليس من نوع DomainError ولا MethodError.

ملاحظة: أحيانًا تكون try...catch هي ما تحتاجه، لكن تجنّب الإفراط في استخدامها. فإذا أمكن استخدام كتلة if...else بدلًا منها، فستكون أكثر كفاءة بكثير من التقاط الاستثناءات.

التسجيل

لاحظ أن دالة error() التي ناقشناها أعلاه لا ينبغي الخلط بينها وبين الماكرو @error.

تولّد الدالة استثناءً، يُمرَّر صعودًا في مكدس الاستدعاءات ما لم يُلتقط.

أما الماكرو @error، إلى جانب نظيراته @debug و@info و@warn، فهو جزء من وحدة Logging، ويهدف إلى توليد رسائل مفيدة دون تغيير مسار البرنامج.

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

julia> @warn "Something looks not quite right"
┌ Warning: Something looks not quite right
└ @ Main REPL[55]:1

julia> @error "Panic!"
┌ Error: Panic!
└ @ Main REPL[56]:1

راجع أيضًا المثال السابق في قسم try...catch.

التعليمات

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

1. تحقّق من مستوى الرطوبة في الغرفة

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

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

يجب أن تتوقف بإطلاق ErrorException (الرسالة الدقيقة غير مهمة، لكن يجب أن تحتوي على مستوى الرطوبة المقاس) إذا تجاوزت النسبة 70%. وإلا، فأضف سجل معلومات بالرسالة "humidity level check passed: h%"، حيث h هي نسبة الرطوبة.

julia> humiditycheck(60)
[ Info: humidity level check passed: 60%
julia> humiditycheck(100)
ERROR: humidity check failed: 100%

2. التحقّق من ارتفاع الحرارة

إيلينا سعيدة جدًا بمهمتك الأولى وتطلب منك أن تتولّى مراقبة درجة حرارة الآلات. وأثناء حديثك مع الفني غريغ، يخبرك أنه إذا تجاوزت حرارة الآلة 500°C، يبدأ الفنيون في القلق من ارتفاع الحرارة.

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

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

  • إذا كان المستشعر معطّلًا، فستكون درجة الحرارة nothing. وفي هذه الحالة، يجب أن تتوقف بإطلاق ArgumentError (الرسالة غير مهمة).
  • وعندما يعمل المستشعر، إذا تجاوزت درجة الحرارة 500°C، فيجب أن تُطلق DomainError يتضمّن درجة الحرارة المقاسة.
  • وإلا فكل شيء على ما يرام، فأضف سجل معلومات بالرسالة "temperature check passed: t °C"، حيث t هي درجة الحرارة.
julia> temperaturecheck(nothing)
ERROR: ArgumentError: sensor is broken

julia> temperaturecheck(800)
ERROR: DomainError with 800:
"overheating detected"

julia> temperaturecheck(500)
[ Info: temperature check passed: 500 °C

3. عرّف خطأً مخصصًا

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

4. راقب الآلة

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

  • تحقّق من الرطوبة ودرجة الحرارة.
  • إذا أطلق فحص الرطوبة ErrorException، فيجب إضافة سجل خطأ بالرسالة "humidity level check failed: h%"، حيث h هي نسبة الرطوبة.
  • وإذا أطلق فحص الحرارة ArgumentError، فيجب إضافة سجل تحذير بالرسالة "sensor is broken".
  • وإذا أطلق فحص الحرارة DomainError، فيجب إضافة سجل خطأ بالرسالة "overheating detected: t °C"، حيث t هي درجة الحرارة.
  • وإذا فشل أحد الفحصين أو كلاهما، فيجب إطلاق MachineError واحدة بعد إضافة السجلات.
  • وإذا كان كل شيء على ما يرام، فلن تُضاف سوى السجلات الصادرة من humiditycheck وtemperaturecheck.

نفّذ دالة machinemonitor() تأخذ الرطوبة ودرجة الحرارة كوسائط.

julia> machinemonitor(42, 450)
[ Info: humidity level check passed: 42%
[ Info: temperature check passed: 450 °C

julia> machinemonitor(42, 550)
[ Info: humidity level check passed: 42%
┌ Error: overheating detected: 550 °C
└ @ Main # output truncated

Error: MachineError

julia> machinemonitor(82, 521)
┌ Error: humidity level check failed: 82%
└ @ Main # output truncated
┌ Error: overheating detected: 521 °C
└ @ Main # output truncated

Error: MachineError

julia> machinemonitor(42, nothing)
[ Info: humidity level check passed: 42%
┌ Warning: sensor is broken
└ @ Main # output truncated

Error: MachineError
تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة
Julia Exercism

مستعد لبدء مستشعرات المصنع؟

سجّل في Exercism لتتعلّم وتتقن Julia عبر 35 مفهومًا128 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.