Разработването на софтуер ни кара да разглеждаме сценарии, при които всичко работи добре относно потока на използване на нашите приложения и въвеждането на потребителски данни. Грешки обаче се случват и ние трябва да ги предотвратим, за да направим потребителското изживяване възможно най-добро.

Когато нещо се обърка при програмиране на процедурни езици, грешките се показват на разработчиците. При обектно-ориентираните езици имаме изключения. За да направим нашето приложение по-устойчиво и надеждно, си струва да научим за тях.

Практическо разбиране

По-лесният начин да разберете какво представляват изключения и как работят е да наблюдавате поведението му върху прости програми, като изложбата по-долу:

Докато изпълнявате програма и въвеждате номер, когато бъдете подканени, той обикновено се показва в съобщение. Ако обаче напишем буква, специален знак или нещо друго, което не е цяло число, получаваме следното съобщение:

Exception Unhandled - System.FormatException: 'Input string was not in a correct format.'

Честно казано, приложението чакаше номер, за да продължи изпълнението си. Тъй като това не се случи и не съществуваше програмирано поведение в случай, че беше въведено нещо различно, изпълнението спря и беше хвърлен FormatException.

От това можем да разширим нашите концептуални знания, за да разберем по-добре какво се е случило.

Правилото зад изключенията

Досега знаем две неща за изключенията:

  1. Те се задействат, когато се случи нещо неочаквано от кода
  2. Те спират изпълнението на приложението. В сървърни приложения - като уебсайтове и API - изпълнението на нишката приключва, когато бъде хвърлено

Има няколко вида изключения, всяко за набор от неблагоприятни поведения. .NET Core има много вградени типове. Няколко примера:

IndexOutOfRangeException

  • Когато се опитаме да получим достъп до елемент от итерируем, който не съществува
  • Пример: при достъп до третия индекс на масив от две позиции

NullReferenceException

  • Когато се опитаме да получим достъп до стойност и вместо нея получим null
  • Пример: когато не се инициализира променлива със стойност или обект

ArgumentException

  • Когато получим аргумент в неочакван формат
  • Пример: получаване на телефонен номер с 20 цифри

Всички изключения наследяват от класа Exception. Той съдържа методи за хвърляне и свойство, наречено Message, идентифициращо неговата грешка.

Справяне с несигурността

За да накараме нашия код да работи дори и с неправилни входове, трябва да го подготвим да се справя с трудностите, като използваме блок try-catch.

Въведените стойности не се променят. Така че могат да се случат две различни поведения:

  • Цяло число: блокът try приключва изпълнението си нормално и блокът catch се игнорира
  • Всеки друг вид стойност:извежда се FormatException. Изпълнението прекъсва блока try, прескача в блока catch и след това изпълнява целия му код

Ако има още код за изпълнение, изпълнението продължава във всеки от случаите. Сега, дори при обстоятелства, когато имаме неправилно въвеждане на данни, потребителят няма да види рязко прекъсване.

В случай, че сме чакали появата на повече от един тип изключение, можем да създадем толкова catch блока, колкото са ни необходими. Възможно е да се използва класът Exception, който е валиден за всякакъв вид изключения:

В този случай вторият блок catch ще бъде изпълнен, ако бъде хвърлено изключение, различно от FormatException.

Сега можем да извикаме екземпляра на изключението чрез променливата e, която ни дава достъп до всички негови методи и свойства. Включително свойството Message, което връща своето описание и се прилага за всякакъв вид изключения.

Хвърляне на изключения

Има ситуации, в които искаме нашият код да хвърли изключение. По този начин начинът, по който се справяме с него, е отделен и може да се случи по различни начини в зависимост от това кой го нарича.

Представете си, че искаме да ограничим стойностите, които потребителят може да въвежда с 255, по някаква причина.

Вътре в if хвърлихме нов екземпляр на общ Exception. Той получава в параметъра на своя конструктор string, който ще бъде присвоен на свойството Message. Това е съобщението, което ще видим, в случай че възникне тази грешка.

С кода, който получихме за метода Main(), вече има начин да се справим с това ново правило за валидиране в блока catch на Exception.

Въпреки това, тъй като трябва да добавим повече правила, нашият код ще стане по-малко семантичен. В случай, че не разрешаваме въвеждане на отрицателни стойности, ще е необходим следният код:

Единствената разлика между двете правила е съобщението. Това пречи на четливостта на кода. За щастие има нещо, което можем да направим, за да го подобрим.

Създаване на нашите изключения

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

Това е много полезен инструмент, особено когато кодираме библиотеки, които са предназначени да бъдат използвани от други разработчици.

За да разрешим нашия проблем, можем да приложим персонализирано изключение за всяко от нашите валидации:

Забележете, че презаписваме свойството Message, така че не е необходимо да го предаваме като параметър. Ние също използваме оператора на ламбда израз =>, така че не е необходимо да използваме ключовата дума return.

По този начин и с помощта на едноредови условни изрази, нашата валидация е много по-чиста и идиоматична:

Сега кодът казва какво правим сам.

Също така е интересно да се документира, че методът, който създадохме, може да хвърля изключения, както се прави с вградения код на .NET Core. Така че IntelliSense на IDE ще го предупреди всеки, който внедри този метод след това.

Този код може да бъде подобрен още повече с помощта на ламбда изрази или съпоставяне на шаблони, но би бил тема, която заслужава отделна статия.

Заключение

Изключенията могат да направят справянето с грешките по-лесно и по-елегантно. Примерите в тази статия са написани на C#, но могат да бъдат възпроизведени със синтактични адаптации на Java, JavaScript, PHP, Python и Ruby.

Правилното използване на изключение избягва да попаднем на миризма на код, наречена „магически числа“, и прави нашия код много по-професионален и преносим.