Защо 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 работеше ефективно.
Въпреки това, с течение на времето ситуацията се промени. Бизнес логиката стана по-сложна, появиха се бази данни и транзакции, трябваше да се интерпретират безброй съобщения за заявки чрез извикване на множество 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]); // Стартиране на HTTP сървър
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.