Защо Go няма Try-Catch?
Go умишлено не поддържа try-catch, а само синтаксиса panic-recover. Това предизвиква недоволство сред много разработчици, свикнали с обработката на грешки чрез try-catch. Защо тогава не се включва try-catch? Защото try-catch има няколко проблема.
try-catch-finally
Конструкцията try-catch е конструкция за обработка на ситуации на грешки и изключения, които могат да възникнат по време на изпълнение на програмата (runtime). Освен това, в конструкцията finally се поставя код, който трябва да бъде изпълнен безусловно, независимо дали е възникнало изключение или не.
Обработка на грешки и отговорност
През 80-те и 90-те години обработката на грешки беше много проста. Съобщенията за грешки бяха само "дискетата е пълна", "няма дискета в устройството", "няма разрешение за запис на дискетата" и разработчиците от тази епоха, когато възникнеше грешка, я throw-ваха до точката за обработка на грешки, за да я обработят общо. В тази ситуация конструкцията try-catch работеше ефективно.
Но с течение на времето ситуацията се промени. Бизнес логиката стана по-сложна, появиха се бази данни и транзакции, трябваше да се интерпретират многобройни request съобщения чрез извикване на многобройни API-та през мрежата и дори трябваше да се обработват грешки в други нишки, а не в основната, поради появата на конкурентно програмиране.
Грешките станаха толкова сложни, че вече не можеха да бъдат обработвани на едно място и никой не можеше да поеме отговорност за всички грешки. Тук възниква сериозен проблем с try-catch.
Прехвърляне на отговорност от try-catch
Try-catch е, с прости думи, метод, при който субектът, предизвикал грешката, прехвърля отговорността (последващата обработка) за възникването на грешката на някого. Този обект на прехвърляне може да бъде catch конструкцията, неговият родителски метод, родителят на родителя на родителя на родителя... или някой друг. С други думи, в свят, в който обработката на грешки става все по-голяма и по-сложна, методът, избран от try-catch, е именно "някой ще го направи". Да разгледаме следния код.
1try {
2 data = readFile("hello.txt");
3 structuredData = parseData(data);
4 insertDBStatus(structuredData[1]);
5 startHTTPServer(structuredData[2]);
6} catch (Exception e) {
7 e.printStackTrace();
8}
Проблемът с горния код е, че не е ясно кой е субектът, който обработва printStackTrace, и не е известно в кой код е възникнала грешката. Още повече, проблемът става още по-ужасен, колкото повече логика има в try командата. Но парадоксално, колкото по-сложно става развитието, толкова повече разработчиците се пристрастяват към try-catch конструкцията, която прехвърля отговорността, не се замислят за обработката на грешки, чувството за отговорност също намалява и в крайна сметка забравят за същността на обработката на грешки и изключения. И така, как golang реши този проблем?
panic, recover
Едно от предимствата на Go е, че има няколко системи, които не позволяват на разработчиците да поемат по лош път и ги правят добри разработчици. panic - recover също може да се разглежда като пример за това.
На пръв поглед panic и recover не изглеждат различни от try-catch, но се различават по това, че не прехвърлят отговорността навън, когато възникне проблем. Когато възникне panic в Go, е необходимо стойността да бъде решена на мястото, където е възникнал panic, вместо да се връща навън. Това дава на разработчика отговорност за обработката на грешки и го насърчава да помисли по-дълбоко къде, кой и как трябва да обработи тази грешка. Това гарантира автономност на потребителя, като същевременно оставя място за размисъл.
Освен това, изборът на думата panic също е много отличен, за разлика от try-recover, panic-recover дава на разработчика натиск, само с думите си, че трябва да се използва само в ясни ситуации на грешки, а не в ситуации на изключения. Естествено, разработчикът не злоупотребява с recover и го използва само там, където е необходимо. Това е от голяма полза за писането на кратък код в Go.