LINUX.ORG.RU

Опубликован почти окончательный драфт генериков в Go

 


0

10

За подробностями в Go-блог (blog.golang.org/why-generics), там есть ссылка на собственно драфт.

Генерики семантически будут наподобие шаблонов C++, т.е. не boxed (как в Java), а value: компилятор будет генерировать копии с конкретными типами.

Синтаксически удалось обойтись введением всего одного нового ключевого слова contract для описания ограничений типа-значения (то есть для создания чего-то вроде метатипов).

В релизе появится всё это не скоро, в Go 2, срок выхода которого неизвестен. Go 1.13 появится на днях, 1.14 — в ноябре, к десятилетию первого публичного бета-релиза.

★★★★

Ответ на: комментарий от hbee

Больше вреда, чем пользы

Исключения вызывают проблему тогда, когда они неправильно используются. В сишном коде очень часто встречается конструкт вида

errno_t err1 = foo (&a);
if (err1) {
    goto label1;
}
errno_t err2 = bar (&b);
if (err2) {
    goto label2;
}

...

label1:
recover1 ();
goto end;

label2:
recover2 ();
goto end;

end:
return;

И если в псевдокоде оно еще немного читаемо, то в реальном приходится прилагать значительные усилия для разбора этой лапши. Не говоря уже об дебаге и в целом безопасности такого кода.

На языки без исключений такие программы транслируются в виде гигантской башни if’ов, часто с приведением ошибок, для которого тоже нужен клей.

В языке же с исключениями это просто

try {
    foo (&a);
    bar (&b);
}
catch (exception1_t e) {
...
}
catch (exception2_t e) {
...
}

При этом такой код может быть проверен статически, во время компиляции.

В Go же только

if 
    if
        if
            if
Siborgium ★★★★★
()
Ответ на: комментарий от hbee

По опыту использования исключений в разных языках стало ясно, что от них больше вреда, чем пользы

Ну и, как замечено выше, такое заявление не соответствует истине. Действительно, многие проекты на С++ избегали и избегают использования исключений, так как исключения в С++ ощутимо медленнее аналогов в других языках. Проблема частично решилась с взрослением языка, когда сформировались правила хорошего тона использования исключений, а до конца будет починена в С++20 (если пропозал Саттера «Lightweight exceptions» будет принят, не знаю его статуса на данный момент, но комитету он понравился).

Siborgium ★★★★★
()
Ответ на: комментарий от hbee

Фича эта (список значений) на самом деле чрезвычайно важна в Go для достижения элегантности обработки ошибок.

А ещё, если пулять как в Си через указатель в аргументах, то в Go получится намного медленнее из-за возни связанной со сборщиком мусора.

package main

import "testing"

func x1(a int) (ax int) {
	return a + 1
}

func p1(a *int) {
	(*a) += 1
}

func x2(a, b int) (ax, bx int) {
	return a + 1, b + 2
}

func p2(a, b *int) {
	(*a) += 1
	(*b) += 2
}

func x3(a, b, c int) (ax, bx, cx int) {
	return a + 1, b + 2, c + 3
}

func p3(a, b, c *int) {
	(*a) += 1
	(*b) += 2
	(*c) += 3
}

func x4(a, b, c, d int) (ax, bx, cx, dx int) {
	return a + 1, b + 2, c + 3, d + 4
}

func p4(a, b, c, d *int) {
	(*a) += 1
	(*b) += 2
	(*c) += 3
	(*d) += 4
}

var ga, gb, gc, gd int

func Benchmark(b *testing.B) {

	//

	b.Run("reply-1", func(b *testing.B) {
		for i := 0; i < b.N; i++ {
			ga = x1(i)
		}
		b.ReportAllocs()
	})

	b.Run("pointer-1", func(b *testing.B) {
		for i := 0; i < b.N; i++ {
			p1(&ga)
		}
		b.ReportAllocs()
	})

	//

	b.Run("reply-2", func(b *testing.B) {
		for i := 0; i < b.N; i++ {
			ga, gb = x2(i, i)
		}
		b.ReportAllocs()
	})

	b.Run("pointer-2", func(b *testing.B) {
		for i := 0; i < b.N; i++ {
			p2(&ga, &gb)
		}
		b.ReportAllocs()
	})

	//

	b.Run("reply-3", func(b *testing.B) {
		for i := 0; i < b.N; i++ {
			ga, gb, gc = x3(i, i, i)
		}
		b.ReportAllocs()
	})

	b.Run("pointer-3", func(b *testing.B) {
		for i := 0; i < b.N; i++ {
			p3(&ga, &gb, &gc)
		}
		b.ReportAllocs()
	})

	//

	b.Run("reply-4", func(b *testing.B) {
		for i := 0; i < b.N; i++ {
			ga, gb, gc, gd = x4(i, i, i, i)
		}
		b.ReportAllocs()
	})

	b.Run("pointer-4", func(b *testing.B) {
		for i := 0; i < b.N; i++ {
			p4(&ga, &gb, &gc, &gd)
		}
		b.ReportAllocs()
	})

}
Benchmark/reply-1-4             2000000000               0.39 ns/op            0 B/op          0 allocs/op
Benchmark/pointer-1-4           1000000000               2.13 ns/op            0 B/op          0 allocs/op
Benchmark/reply-2-4             2000000000               0.77 ns/op            0 B/op          0 allocs/op
Benchmark/pointer-2-4           1000000000               2.10 ns/op            0 B/op          0 allocs/op
Benchmark/reply-3-4             2000000000               1.17 ns/op            0 B/op          0 allocs/op
Benchmark/pointer-3-4           2000000000               2.00 ns/op            0 B/op          0 allocs/op
Benchmark/reply-4-4             2000000000               1.60 ns/op            0 B/op          0 allocs/op
Benchmark/pointer-4-4           1000000000               2.13 ns/op            0 B/op          0 allocs/op
anonymous
()

И про что теперь шутить?

a1batross ★★★★★
()
Ответ на: комментарий от eao197

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

Старый джентельменский обычай: давать оппоненту право на ответ. 😁

Deleted
()
Ответ на: комментарий от Siborgium

Проблема с Go вовсе не в том, что он примитивнее аналогов. Проблема в нежелании адептов Go признать это.

На то они и адепты, чтобы раскрыв клювик ловить и ретранслировать любую чепуху от гуру (читай старого маразматика).

bread
()
Ответ на: комментарий от anonymous

ЧСВ один из «цветочков» весьма «пахучего» букета.

Владимир

anonymous
()

компилятор будет генерировать копии с конкретными типами.

Так и знал, что суть го это копипаста.

ya-betmen ★★★★★
()
Ответ на: комментарий от ya-betmen

Так и знал, что суть го это копипаста.

На самом деле Роб Пайк планировал залихватски решить всё генераторами. Вот только генерировать Го код проще не на Го, чем на Го. А это уже противоречит го-гету, как итог, оставляя лишь единственный путь — копипасту.

anonymous
()
Ответ на: комментарий от anonymous

На самом деле Роб Пайк планировал залихватски решить всё генераторами.

Не было такого. Планировалось иное. Мы типа будем как сишка, но будет сахар/«классы» и макросы будут ненужны. Да и в си их почти никто не использует особо. А потом оказалось, что недоязычок ничего не может и срочно нужно что-то менять. И именно тогда появились костыли - кодоген.

Вот только генерировать Го код проще не на Го, чем на Го.

Это ничто не изменит. Причин тут две. Первое - го это страшилище убогое с нулевыми выразительными возможностями. Хотя это всяким рустам и прочим недоязычкам не мешает. Просто ЦА у го не такая бездарная как у раста. Те сожрут любое говно - ведь писать им кроме лабы ничего ненужно. Го-адепты же «пишут» код и реальность сильнее фанатизма.

Второе - это попросту привычки адептов. В основном ЦА го, как и того же раста, - это всякая школота и прочие пистон/пхп-адепты. Очевидно, что если у адепта есть выбор - писать на старом, либо на новом - он выберет то, что ему привычно - старое.

А это уже противоречит го-гету, как итог, оставляя лишь единственный путь — копипасту.

Ещё раз, копипаста - это генерики. Шаблоны не являются копипастой. Это значит, что даже если сейчас нету возможностей проявится этому различие - оно проявится в будущем.

Т.е. го-адептам нужно радоваться, что скриптуха пошла по крестовому пути. Хотя у статического языка нету выбора - генерики не могут быть статическими. И как я уже говорил выше - лучше иметь сильную базу, чем слабую.

anonymous
()
Ответ на: комментарий от anonymous

Не было такого. Планировалось иное. Мы типа будем как сишка, но будет сахар/«классы» и макросы будут ненужны. Да и в си их почти никто не использует особо. А потом оказалось, что недоязычок ничего не может и срочно нужно что-то менять. И именно тогда появились костыли - кодоген.

Ну ты и неуч, про генераторы почитай. Потом подходи.

Это ничто не изменит. Причин тут две. Первое - го это страшилище убогое с нулевыми выразительными возможностями. Хотя это всяким рустам и прочим недоязычкам не мешает. Просто ЦА у го не такая бездарная как у раста. Те сожрут любое говно - ведь писать им кроме лабы ничего ненужно. Го-адепты же «пишут» код и реальность сильнее фанатизма.

Это чушь.

Второе - это попросту привычки адептов. В основном ЦА го, как и того же раста, - это всякая школота и прочие пистон/пхп-адепты. Очевидно, что если у адепта есть выбор - писать на старом, либо на новом - он выберет то, что ему привычно - старое.

Кого вообще волнуют адепты, они есть у каждого ЯП, но это не моя сфера интересов. Если тебе интересует препарирование адептов, то делай это отдельно от остальных тем, пожалуйста.

Ещё раз, копипаста - это генерики. Шаблоны не являются копипастой. Это значит, что даже если сейчас нету возможностей проявится этому различие - оно проявится в будущем.

Копипаста, это копирование руками. Может быть с заменой. Остальное уже копипастой не является, хотя в некоторых случаях может и быть её эквивалентом. Подмена терминов не способствует здоровому общению.

anonymous
()
Ответ на: комментарий от Deleted

я не хочу ничего обсуждать в песочнице для хипстеров. есть темы про сишечку. мне пофигу, что там хомячьё про меня за спиной говорит, если честно. в говнотреды меня не надо кастовать.

Iron_Bug ★★★★★
()
Последнее исправление: Iron_Bug (всего исправлений: 1)
Ответ на: комментарий от Iron_Bug

@chinarulezz, @Iron_Bug, глаза протрите, никто вас не обсуждает.

Речь шла о том, что в моем случае смайлодаун понимания в своей любви к чистой сишечке не найдет. Тогда как с Iron_Bug такие шансы у смайлодауна есть.

Чтобы понять такую нехитрую штуку достаточно иметь хоть как-то работающие мозги, в чем упоротых сишников, обычно, очень трудно заподозрить. Очередное тому подтверждение.

eao197 ★★★★★
()
Ответ на: комментарий от anonymous

А ещё, если пулять как в Си через указатель в аргументах, то в Go получится намного медленнее из-за возни связанной со сборщиком мусора.

Спасибо, ценное замечание. В этом, кстати, таится ресурс ускорения Go: улучшить т.н. escape analysis, увеличить долю размещения значений на стеке, а не в куче.

hbee ★★★★
() автор топика
Ответ на: комментарий от Siborgium

Действительно, многие проекты на С++ избегали и избегают использования исключений, так как исключения в С++ ощутимо медленнее аналогов в других языках.

Проблема деградации производительности не главная. Гораздо хуже, что исключения снижают качество обработки ошибок. Грубо говоря, обработка теряет в тонкости, детальности, уместности, потому что в блоки try включаются большие куски логики (а иначе, если городить try/catch вокруг каждого вызова функции, живые позавидуют мёртвым). Разработчик поддаётся искушению «срезать углы», делая только catch(std::exception) и catch(...). В самых клинических случаях ошибка только логируется в надежде разобраться позже.

Go же поощряет обработку максимально близко к месту возникновения ошибки. Пресловутый boilerplate code (if err != nil { do something }) сделан настолько компактным, насколько возможно. Исключения panic и их ловля recover намеренно устроены так, чтобы их использование не было чересчур комфортным и кратким и не создавало соблазна.

hbee ★★★★
() автор топика
Ответ на: комментарий от hbee

Очень бы не помешали примеры. Вот, мол, ваше говно на исключениях в C++/Java/C#/Scala/Kotlin/Ruby/Python и иже с ними, а вот как няшно это же самое выглядит на Go с кодами ошибок.

eao197 ★★★★★
()
Ответ на: комментарий от hbee

if err != nil { do something } побуждает сделать

if err != nil {
    fmt.Printf("Shit happened")
    return nil, err
}

и хорошо, если Printf еще будет.

Более того, отдельные сверхразумы додумались до сделанного вручную таким образом стек трейса, ведь, очевидно, дебаггеры – не Go way.

Siborgium ★★★★★
()
Ответ на: комментарий от eao197

Берём список «top 10 C++ projects on github», берём первый из них — Tensorflow (>130 тыс. stars). И что же видим в первом же попавшемся файле tensorflow/core/util/mkl_util.h? Всё то же самое — гигантский блок try, в котором делается невесть что, и примитивный блок catch — «логируй всё, потом разберёмся!». О кошмарном стиле форматирования уже не говорю, за отступы в два пробела надо заставлять работать на сорокадюймовом мониторе))).

  } catch (mkldnn::error& e) {
    string error_msg = "Status: " + std::to_string(e.status) +
                       ", message: " + string(e.message) + ", in file " +
                       string(__FILE__) + ":" + std::to_string(__LINE__);
    LOG(FATAL) << "Operation received an exception: " << error_msg;
  }
hbee ★★★★
() автор топика
Ответ на: комментарий от Siborgium

отдельные сверхразумы додумались до сделанного вручную таким образом стек трейса, ведь, очевидно, дебаггеры – не Go way

Путаешь, это не стектрейс, а контекст. Стектрейсы другой разговор, легко сделать. А дебаггер, кстати, у Go отличный — Delve.

hbee ★★★★
() автор топика
Ответ на: комментарий от Siborgium

хорошо, если Printf еще будет

Логирование ошибки — уже обработка (когда совсем ничего сделать нельзя, так как потерян контекст, как в вышеприведённом примере с C++). Хороший стиль — исправить, если возможно, на месте, если нет — передать вверх, добавив контекст.

hbee ★★★★
() автор топика
Ответ на: комментарий от hbee

Всё то же самое — гигантский блок try, в котором делается невесть что, и примитивный блок catch — «логируй всё, потом разберёмся!».

Безотносительно того, что вы не дали себе труда разобраться в том, оправдано ли там это или нет, вы еще и не дали себе труда показать, как Go позволит сделать тоже самое, но понятнее и удобнее в сопровождении (хотя бы это, про другие качества говорить пока вообще рано).

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Ну какой ещё труд на лоре)))). А пример ещё будет, я тоже, знаете ли, не сижу все время тут. Спешить вроде некуда.

hbee ★★★★
() автор топика
Ответ на: комментарий от hbee

Ну какой ещё труд на лоре))))

LOR и так стремительно преврашают в УГ персонажи вроде царя, метапрога и смайлодауна. Должны же здесь оставаться люди, с которыми можно предметно поговорить.

Жалоб на исключения много, но, обычно, когда доходишь до конкретики, то выясняется, что нормальных примеров-то и нет. По крайней мере если разговор идет об императивных языках, вроде Go, Java или C++.

А пример ещё будет

OK. Спешить некуда.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Что значит «то же самое»? Реализации tensorflow на Go нет. А тьма примеров хорошей обработки наблюдается прямо в стандартной библиотеке, которая написана весьма ясно и служит примером для подражания. Например:

// ReadDir reads the directory named by dirname and returns
// a list of directory entries sorted by filename.
func ReadDir(dirname string) ([]os.FileInfo, error) {
	f, err := os.Open(dirname)
	if err != nil {
		return nil, err
	}
	list, err := f.Readdir(-1)
	f.Close()
	if err != nil {
		return nil, err
	}
	sort.Slice(list, func(i, j int) bool { return list[i].Name() < list[j].Name() })
	return list, nil
}

Лучше ли было бы обработать обе эти ошибки в гипотетическом блоке catch? Нет, так как природа у них разная (и при возможном в будущем изменении типа ошибки ничего менять у клиента не надо). Читаемость кода отличная, обработка стопроцентная, ничего не пропущено.

hbee ★★★★
() автор топика
Ответ на: комментарий от hbee

Простите, но где здесь обработка ошибок вообще? Вы, видимо, путаете «обработку» ошибок с «доставкой» информации об ошибке. Поскольку шесть строк из вашего примера только тем и занимаются, что доставляют информацию о произошедшей ошибке наверх.

И в языке с исключениями и RAII вы бы записали что-то вроде:

vector<FileInfo> ReadDir(Path dirname) {
   auto list = os.ReadDir(os.Opendir(dirname));
   sort.Slice(list, func(usize i, usize j) bool { return list[i].Name() < list[j].Name() });
   return list;
}

В точности все тоже самое, но в 2.5 раза короче.

eao197 ★★★★★
()
Ответ на: комментарий от vertexua

А что у вас шевелится когда вы смотрите на еще большее количество софта который писали и продолжают писать просто на С, даже без плюсов?

abcq ★★
()
Ответ на: комментарий от abcq

Приведите пример немыслимых задач для си с классами.

Standard Template Library. std::vector, std::list, std::map и вот это вот все.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

стандартной библиотеке уже лет почти столько же сколько и самому языку, основной ее костяк был написан в начале 90х, в чем немыслимость реализации перечисленных типов, даже без классов, не говоря уже о том, что предлагает современный с++? Немыслимо заполучить межзвездные полеты послезавтра или позавчера, а тут как раз таки всё в достаточной мере возможно, раз это уже один раз сделали на примере самого С++. Формулировка какая-то странная.

abcq ★★
()
Ответ на: комментарий от abcq

Ну это вообще жесть какая-то :(

https://rust-embedded.github.io/blog/

Уповаем на вот эту группу. Только в этом году серьезно заработала, они смотрят на проблему всецело и имеют определенную стратегию как сделать Rust очень крутым продуктом для embedded. Потому что пока-что очень много острых уголов

vertexua ★★★★★
()
Ответ на: комментарий от abcq

Задачи в которых шаблоны С++ используются для zero-cost abstractions. То же самое на макросах городить очень тяжело, потому не делается.

vertexua ★★★★★
()

Самый главный вопрос до сих пор не задали. Где реализация на этих шаблонериках Option и Result?

Zenom ★★★
()
Ответ на: комментарий от abcq

Видимо, вы в силу своей молодости просто не можете помнить C++ из девяностых, отсюда и такое непонимание очевидных вещей. Си с классами – это образное название C++ в промежутке между 1985-ым и самым началом 1990-х, когда в языке не было шаблонов вообще. Ну вот совсем. И, соответственно, без шаблонов, т.е. на Си с классами, написать STL было нельзя. Можно было сделать какое-то подобие на макросах. Либо попробовать скопировать классы для коллекций из SmallTalk-а. Но вот обобщенного программирования, как такового не было.

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

eao197 ★★★★★
()
Ответ на: комментарий от hbee

резвычайно важна в Go для достижения элегантности обработки ошибок.

Ииии... В нашем конкурсе оксюморонов с огромным отрывом побеждает элегантная обработка ошибок в Go, с большим отрывом опередив неподкупных постовых и свободу прессы в России, занявшие второе и третье место соответственно.

Zenom ★★★
()
Ответ на: комментарий от urxvt

Не подскажешь где этот вопрос раскрыт авторами? В draft'е есть? Я его пока не осилил.

В дополнение к тому, что написал анонимус про костыли для builtin операторов/функций, конракты не требуют боксинга/анбоксинга значения.

Например, при использовании существующего механизма интерфейсов

func Foo(type T Stringer)(v T) T {
    // do something with v.String()
    return v
}

v := SomeStruct{}
//       ,--- v boxed to Stringer
w := Foo(v)
// `--- result of Foo unboxed from Stringer to SomeStruct
korvin_ ★★★★★
()
Ответ на: комментарий от foror

Амазон в свое время активно использовал постгрескул и кажется ничего не поменялось, видимо их все устраивает, подумайте об этом.

abcq ★★
()

А вообще, очень интересно наблюдать за тем, как Go танцует степ на тех же самых граблях, по которым уже прошлась Java.

Zenom ★★★
()
Ответ на: комментарий от eao197

Хорошо что вы решили еще раз копнуть историю, она как раз расставляет все точки над и. Бьярне ничего не помешало сделать С с классами имея в руках только С. Степанову@Ли ничего не помешало сделать STL имея только С с классами. А теперь подумайте, если есть реальные примеры из жизни, так ли это немыслимо или все же это просто какие-то популистские речи и оправдания. Понятно, что хочется удобный инструмент с мириадами возможностей прямо сейчас, но так вот оголтело заявлять о «немыслимости», это уровень базарных сплетен.

abcq ★★
()
Ответ на: комментарий от Zenom

Ну скоро они заново придумают джаву или раст, надо только подождать немного.

abcq ★★
()
Ответ на: комментарий от abcq

Степанову@Ли ничего не помешало сделать STL имея только С с классами.

У Степанова был уже не Си с классами, а C++ с шаблонами. В том-то все и дело.

eao197 ★★★★★
()
Ответ на: комментарий от vertexua

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

abcq ★★
()
Ответ на: комментарий от eao197

Так он у него был не с пустого места, а потому что его сделали, при том за вполне мыслимые сроки простой итерационной шлифовкой кода и идей которые Бьярне и сообщество впитывали как губка, воплощая их в своем языке с каждым новым релизом. Никто не отвергает того, что применение определенных парадигм, на определенном кругу задач, позволяет эти задачи решать быстрее, а если еще и язык содержит в себе поддержку этих парадигм в виде конструкций языка так вообще замечательно. Но «немыслимость», ну я даже не знаю, ну прям очень желтушно, никого не хочу обидеть, но меня прямо порвало как свинью из одноименного мема прошлых лет.

abcq ★★
()
Ответ на: комментарий от abcq

Возможно или не возможно - не важный вопрос для 2019го года. Есть ли смысл, какое соотношение цены к качеству - это важно

vertexua ★★★★★
()
Ответ на: комментарий от abcq

Приведите пример немыслимых задач для си с классами.

Да пожалуйста: boost.msm, boost.multiindex

На макросах такое даже начинать делать бы не стал

annulen ★★★★★
()
Ответ на: комментарий от abcq

Да и умные указатели не получили бы распространения, если бы требовали использования макросов, а современный с++ без них представить сложно

annulen ★★★★★
()
Ответ на: комментарий от hbee

Пресловутый boilerplate code (if err != nil { do something }) сделан настолько компактным, насколько возможно.

Это неправда.

file := os.Open(fileName) or err {
    return nil, errors.Wrap(err, "open config file")
}

против

file, err := os.Open(fileName)
if err != nil {
    return nil, errors.Wrap(err, "open config file")
}

В альтернативном варианте лучше:

  1. Гарантируется типобезопасность
  2. Пространство имён не загрязняется всякими err
  3. Компактнее

Абсолютно аналогично можно было бы ввести понятие nilable типов с аналогичной обработкой (or без err), таким образом полностью исключив класс NPD-ошибок. Для удобства, правда, пришлось бы вводить if и switch операторы для инициализаций:

val := if a < b {
    yield a
} else {
    yield b
}

потому как иначе var a map[string]string вынужден был бы стать var a ?map[string]string с обязательной проверкой

if a != nil {
    
}

Параллельно бы это упростило муторный код для работы с мапами:

a := map[string]string{}
// …
b := a["value"] or {
    yield "default"
}

вместо

a := map[string]string{}
// …
b, ok := a["value"]
if !ok {
    b = "default"
}

PS Любителям исключений: у вас с головой всё в порядке? Из раза в раз получается, что Go-бекенды делаются в два раза быстрее джавовых и при этом в те же два раза быстрее и жрут раз в 8 меньше памяти и изначального намного стабильнее блогодаря как раз явной обработке ошибок. Я в курсе, что для многих задач исключения вполне себе хороший способ, но только не для бекендов.

Joe_Bishop
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.