Ферма

Ферма

Навчальна вправа

Вступ

В ідеальному світі все працює бездоганно. Але в реальному світі щось може піти не так, і те, як ми поводимося в таких ситуаціях, визначає, чи буде наше програмне забезпечення стійким і надійним. Винятки - це ключове поняття в програмуванні, яке дає нам змогу коректно обробляти помилки та непередбачувані ситуації.

Якщо помилку не обробити, вона зупинить програму й виведе повідомлення про помилку. У більшості випадків ми не хочемо, щоб програма зупинялася, коли виникає помилка; натомість ми хочемо обробити цю помилку й далі виконувати програму.

Викидання винятку

У Crystal винятки викидають за допомогою ключового слова raise, якому можна передати або String, або обʼєкт Exception. Якщо їх не обробити, програма зупиниться й надрукує повідомлення про помилку.

raise "This is an error"

У Crystal є кілька вбудованих винятків, як-от ArgumentError, IndexError, KeyError, IOError, SystemCallError, TypeError, ZeroDivisionError та багато інших. Для них потрібно передати повідомлення.

raise ArgumentError.new("This is an argument error")

Обробка винятків

Ми ж не хочемо, щоб наша програма аварійно завершувалася, коли виникає виняток. Тому, коли ми знаємо, що якась частина коду схильна до помилок, ми можемо обгорнути її в блок begin і перехопити виняток блоком rescue. Блок begin позначає початок коду, який може викинути виняток, а блок rescue обробляє цей виняток.

begin
 raise "This is an error"
rescue
 puts "An error occurred!"
end

У блоці rescue також можна вказати змінну, щоб отримати обʼєкт винятку.

begin
 raise "This is an error"
rescue ex
 puts "An error occurred: #{ex.message}"
end

У блоці rescue також можна вказати конкретний тип винятку, щоб перехопити лише його.

begin
 raise ArgumentError.new("This is an argument error")
rescue ArgumentError
 puts "An argument error occurred!"
end

# or

begin
 raise ArgumentError.new("This is an argument error")
rescue ex : ArgumentError
 puts "An argument error occurred: #{ex.message}"
end

Можна використовувати кілька блоків rescue, щоб обробити різні типи винятків. У наведеному нижче прикладі перший блок rescue перехопить ArgumentError, а другий блок rescue перехопить будь-який інший виняток.

begin
 raise ArgumentError.new("This is an argument error")
rescue ArgumentError
 puts "An argument error occurred!"
rescue
 puts "An error occurred!"
end

Блок begin також може мати блок else, який виконується, якщо виняток не виник.

begin
 puts "No error occurred"
rescue
 puts "An error occurred!"
else
 puts "No error occurred"
end

І нарешті, є блок ensure, який виконується завжди, незалежно від того, чи виник виняток.

begin
 raise "This is an error"
rescue
 puts "An error occurred!"
ensure
 puts "This is always executed"
end

Конвенція методів

Деякі методи мають дві версії: одну з ! і одну без. Це може означати дві різні речі. Або метод змінює обʼєкт, або метод може викинути виняток.

Але є й інша конвенція щодо завершення назви методу знаком ?, про яку йдеться в концепції булевих значень (англ. Boolean). Деякі методи типово викидають виняток, але також мають версію, назва якої закінчується на ? і яка повертає nil замість викидання винятку.

Це ідеально підходить, коли ми хочемо уникнути виникнення помилки. Це може допомогти продуктивності, оскільки не потрібно створювати трасування стеку, а за правильного налаштування код може стати безпечнішим.

Власні винятки

Ми також можемо створювати власні винятки, успадковуючи їх від класу Exception. У цьому разі ми можемо за бажанням перевизначити метод initialize, щоб задати повідомлення винятку. Це можна зробити, присвоївши змінній екземпляра з назвою @message потрібне повідомлення.

class MyException < Exception
 def initialize
   @message = "This is my exception"
 end
end

raise MyException.new

Вказівки

Настав день, на який ми так довго чекали, і тепер ми з гордістю володіємо чудовою фермою в Альпах.

Ми досі не любимо прокидатися надто рано, щоб нагодувати своїх корів. Оскільки ми чудові інженери, ми будуємо дозатор корму FEED-M-ALL.

Аби завершити проєкт, залишилося написати фрагмент коду, який обчислює кількість корму, що має дістатися кожній корові. Важливо, щоб кожна корова отримала однакову кількість, адже треба уникати конфліктів. Корови дуже чутливі.

На щастя, нам не потрібно самостійно виводити всі формули для обчислення кількості корму. Ми використовуємо якусь загадкову зовнішню бібліотеку, яку знайшли в інтернеті. Вона має зробити корів найщасливішими. Бібліотека надає клас, який реалізує такий інтерфейс. Ми будемо покладатися на це у коді, який пишемо самі.

class FodderCalculator
  def fodder_amount! : Number 
  end

  def fattening_factor! : Number
  end
end

Працюючи над кодом, ми вдосконалимо обробку помилок, щоб зробити її надійнішою і такою, яку потім буде легше налагоджувати в повсякденному житті на фермі.

1. Розділіть корм порівну

Насамперед зосередимося на написанні коду, потрібного для обчислення кількості корму на одну корову.

Реалізуйте метод TheFarm.divide_food, який приймає як аргументи обʼєкт FodderCalculator і кількість корів (ціле число). У цьому завданні вважаємо, що передана кількість корів завжди більша за нуль. Метод має повернути кількість корму на одну корову.

Щоб виконати обчислення, спершу треба отримати загальну кількість корму для всіх корів. Для цього викликаємо метод fodder_amount! і передаємо кількість корів. Крім того, потрібен коефіцієнт, на який цю кількість треба помножити. Цей коефіцієнт отримуємо, викликавши метод fattening_factor!. Маючи ці два значення та кількість корів, тепер можемо обчислити кількість корму на одну корову (як float64).

Якщо один із викликаних методів повертає помилку, виконання має зупинитися й повернути 0.

fodder_calculator = FodderCalculator.new(50, 1.5)
TheFarm.divide_food(fodder_calculator, 5)
# => 15.0

fodder_calculator = FodderCalculator.new(50, nil)
TheFarm.divide_food(fodder_calculator, 5)
# => 0

2. Перевірте кількість корів

Працюючи над першим завданням, ми зрозуміли, що зовнішня бібліотека, яку ми використовуємо, не така якісна, як здавалося. Наприклад, вона не вміє належно обробляти некоректні вхідні дані. Ми хочемо обійти це обмеження, додавши перевірку вхідного значення у власному коді.

Напишіть функцію TheFarm.validate_input_and_divide_food з тією самою сигнатурою, що й TheFarm.divide_food вище.

  • Якщо передана кількість корів більша за 0, функція має викликати TheFarm.divide_food і повернути результат цього виклику.
  • Якщо кількість корів дорівнює 0 або менша, функція має згенерувати помилку з повідомленням "Number of cows must be greater than 0".
TheFarm.validate_input_and_divide_food(fodderCalculator, 5)
# => 15

TheFarm.validate_input_and_divide_food(fodderCalculator, -2)
# Error: Number of cows must be greater than 0

3. Покращте обробку помилок

Перевірка кількості корів перед передаванням була слушним кроком, але нас не зовсім тішить неконкретне повідомлення про помилку. Ми вирішуємо зробити краще й створюємо власний клас помилки InvalidCowsError.

Власна помилка має зберігати кількість корів (Int32) і власне повідомлення (String), а метод Expection має серіалізувати дані в такому форматі:

{number of cows} cows are invalid: {custom message}

Озброївшись власною помилкою, реалізуйте функцію TheFarm.validate_number_of_cows, яка приймає кількість корів як ціле число й повертає помилку (або nil).

  • Якщо кількість корів менша за 0, функція повертає InvalidCowsError з власним повідомленням "there are no negative cows".
  • Якщо кількість корів дорівнює 0, функція повертає InvalidCowsError з власним повідомленням "no cows don't need food".
  • В іншому разі функція повертає nil, що означає, що перевірка успішна.
TheFarm.validate_number_of_cows(5)
# => nil

TheFarm.validate_number_of_cows(-5)
# => InvalidCowsError: -5 cows are invalid: there are no negative cows

Після нелегкої роботи зі створення цієї функції перевірки ми помічаємо, що вже вечір, і залишаємо робоче місце, щоб помилуватися заходом сонця над горами. А додавання нової функції перевірки до коду залишаємо на інший день.

Редагувати через GitHub Посилання відкривається в новому вікні або вкладці
Crystal Exercism

Час розпочати Ферма?

Зареєструйтеся на Exercism, щоб вивчати й опановувати Crystal, а також 26 концепцій142 вправи та справжнє наставництво від людей, і все це безкоштовно.