مسیرها
/
Julia
Julia
/
تمرین‌ها
/
حسگرهای کارخانه
حسگرهای کارخانه

حسگرهای کارخانه

تمرین یادگیری

مقدمه

برنامه‌نویسان معمولاً تلاش می‌کنند نرم‌افزاری بی‌عیب بنویسند و معمولاً موفق نمی‌شوند.

کارها به شکلی غیرمنتظره اشتباه پیش می‌روند و ما باید بتوانیم از پسشان بربیاییم.

بعضی از طراحان زبان معتقدند که اولویت این است هرچه سریع‌تر خطایی را تشخیص دهیم و بعد اجرا را با پیامی روشنگر متوقف کنیم تا دیباگ‌کردن آسان‌تر شود.

زبان‌های علم داده معمولاً رویکرد ظریف‌تری در پیش می‌گیرند. بعضی خطاها آن‌قدر جدی‌اند که توقف فوری لازم است، اما اغلب بهتر است مشکل را علامت بزنیم تا بعداً به آن پرداخته شود و سپس اجرا را ادامه دهیم.

در مفهوم نیستی دیدیم که جولیا جایگزین‌های گوناگونی برای مقادیر مشکل‌دار فراهم می‌کند: missing، NaN و Inf. اینکه این‌ها در موقعیتی خاص رویکرد بهتری از توقف برنامه باشند، به قضاوت برنامه‌نویس بستگی دارد.

یک نکته درباره‌ی نام‌گذاری پیش از ورود به جزئیات: مستندات جولیا دو واژه‌ی «error» و «exception» را تا حد زیادی هم‌معنا در نظر می‌گیرد. متن زیر هم ممکن است به همان اندازه ناهماهنگ باشد.

انواع خطای استاندارد

تا اینجای برنامه‌ی درسی، حتماً پیام‌های خطای زیادی از جولیا دیده‌اید. برای نمونه:

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

تبدیل یک عدد اعشاری به عدد صحیح با از دست رفتن دقت همراه است، بنابراین InexactError می‌گیریم.

InexactError یک نوع است، یکی از چندین نوعی (در حال حاضر ۲۵) که به‌صورت استاندارد در جولیا تعبیه شده‌اند. همه‌ی آن‌ها زیرنوع 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. بررسی سطح رطوبت اتاق

اولین مأموریت شما نوشتن نرم‌افزاری است که سطح رطوبت اتاق تولید را پایش کند. در حال حاضر حسگری به نرم‌افزار شرکت متصل است که به‌طور دوره‌ای درصد رطوبت اتاق را برمی‌گرداند.

شما باید تابعی در نرم‌افزار پیاده‌سازی کنید که اگر درصد رطوبت بیش از حد بالا باشد، خطایی پرتاب کند. اگر رطوبت در سطح قابل قبولی باشد، یک لاگ Info اضافه می‌شود. اسم این تابع باید humiditycheck باشد و درصد رطوبت را به عنوان آرگومان بگیرد.

اگر این درصد از ۷۰٪ فراتر رود، باید با یک ErrorException متوقف شوید (پیام دقیق مهم نیست، اما باید سطح رطوبت اندازه‌گیری‌شده را در خود داشته باشد). در غیر این صورت، یک لاگ Info با پیام "humidity level check passed: h%" اضافه کنید که در آن h درصد رطوبت است.

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

2. بررسی گرمای بیش از حد

النا از اولین مأموریت شما بسیار راضی است و از شما می‌خواهد که به پایش دمای دستگاه‌ها بپردازید. وقتی با یک تکنسین به اسم گرگ گپ می‌زنید، به شما گفته می‌شود که اگر دمای یک دستگاه از ۵۰۰ درجه سلسیوس فراتر رود، تکنسین‌ها نگران گرمای بیش از حد می‌شوند.

دستگاه به حسگری مجهز است که دمای داخلی آن را اندازه می‌گیرد. باید بدانید که این حسگر بسیار حساس است و اغلب خراب می‌شود. در این صورت، تکنسین‌ها باید آن را عوض کنند.

وظیفه‌ی شما پیاده‌سازی تابعی به اسم temperaturecheck است که دما را به عنوان آرگومان می‌گیرد و اگر همه‌چیز خوب باشد لاگی اضافه می‌کند، و اگر حسگر خراب باشد یا دستگاه شروع به گرم‌شدن بیش از حد کند، خطایی پرتاب می‌کند. از آنجا که بعداً باید بسته به نوع خطا واکنش متفاوتی نشان دهید، به سازوکاری نیاز دارید که این دو نوع خطا را از هم تشخیص دهد.

  • اگر حسگر خراب باشد، دما nothing خواهد بود. در این صورت، باید با یک ArgumentError متوقف شوید (پیام مهم نیست).
  • وقتی حسگر کار می‌کند، اگر دما از ۵۰۰ درجه سلسیوس فراتر رود، باید یک DomainError پرتاب کنید که دمای اندازه‌گیری‌شده را در خود داشته باشد.
  • در غیر این صورت همه‌چیز خوب است، پس یک لاگ Info با پیام "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 پرتاب کند، باید یک لاگ Error با پیام "humidity level check failed: h%" اضافه شود که در آن h درصد رطوبت است.
  • اگر بررسی دما یک ArgumentError پرتاب کند، باید یک لاگ Warn با پیام "sensor is broken" اضافه شود.
  • اگر بررسی دما یک DomainError پرتاب کند، باید یک لاگ Error با پیام "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 تمرین و مربی‌گری انسانی واقعی یاد بگیرید و در آن استاد شوید، همه‌ی این‌ها رایگان.