[БЕЗ_ЗВУКА] В этом видео мы поговорим про кодогенерацию — это второй способ работать с какими-то динамическими значениями и что-то с ними делать. В отличие от рефлексии кодогенерация, она не выполняется в процессе работы программы. Кодогенерацией занимается вообще отдельная программа, которая, как следует из названия, генерирует код для нужных действий, который потом выполняется уже в процессе работы. Как это работает? Итак, перед нами уже знакомый вам код, который распаковывает бинарные данные. Если в прошлый раз мы вызывали функцию, куда передавали структуру, то сейчас мы вызываем метод этой структуры. Но почему-то ругается: метода нет. Где этот должен быть метод? Тут у нас пусто, тут только определение самой структуры. Метод должен быть вот здесь, его сгенерирует вот эта вот программа. Сначала я покажу, как это работает. [ШУМ] Я бы выполнил вот такую команду. В процессе работы программы она разбирает исходник, что-то там смотрит, что-то там делает и пишет код. Пишет код как раз в файл, в отдельный файл. То есть это метод Unpack для этой структуры, который работает с бинарными данными. И тут вызывается уже не тот код в цикле, который видели вы видели в рефлексии, а уже готовый код для каждого поля. Что тут внутри происходит? И вообще, почему это возможно? Дело в том, что программа на Go собирается программой, которая написана на Go, то есть компилятор Go написан теперь уже на Go. Это значит, что нам доступны все средства компилятора, все средства обхода абстрактного синтаксического дерева и все нужные библиотеки для работы с ним. Теперь давайте залезем внутрь программы, которая сгенерировала этот код и я расскажу, как оно работает. Итак, кодегенерация — это процесс написания программы, которая генерирует другую программу. Не пытайтесь сразу понять все, что я вам расскажу сейчас, просто посмотрите и пускай в вашей картине мира появится сам факт того, что так можно было. Итак, сначала я задам несколько шаблонов, при помощи которых я вообще генерирую код, то есть мне нужно туда передать имя и все — и оно уже создаст какой-то текст на выходе. Не буду на этом останавливаться, пойдем дальше. Итак, я при помощи вот этих вот компонентов, я сначала попробую распарсить исходный код, который передан мне первым аргументов в программу. И буду записывать его в файл, который создам [НЕРАЗБОРЧИВО] второго аргумента. Итак, начнем. Сначала я запишу имя пакета в этот файл — там пустые строчки — и необходимые мне импорты, то есть запишу файл руками. Потом начну перебирать синтаксическое дерево в поиске нужной мне вообще структуры, того, что я вообще ищу. Итак, сначала объявление общего вида, то есть там может быть не обязательно объявление, там может быть импорт какой-то или еще что-то другое. И теперь я начну по ним проходиться. Итак, у меня есть объявление — это может быть объявление типа, константы, переменной, — я иду по этим переменным, если они мне не нужны, я скипаю. Теперь я смотрю, если это структура — если не структура, тоже скипаю. Теперь я скипаю структуру, если у нее нет комментов. Почему я так делаю? Смотрите. Вот у меня у структуры я пометил комментарием специальным, что мне нужно генерировать для этой структуры что-то. И еще пометил каким-то тегом поле структуры. Итак, если у структуры нет комментариев, я ее скипаю. Вот, например, структура Avatar — у нее нет комментариев никаких, относящихся к ней, и я не буду для нее ничего генерировать. Дальше я смотрю все комментарии, и если я встречаю нужный мне комментарий, я ставлю флаг. Если вдруг флага нет, я не продолжаю обработку дальше, я просто перехожу к следующему объявлению. Хорошо, теперь непосредственно начался процесс генерации кода для этой структуры и ее полей. Так, немножко информации, что я делаю. Я, вообще, создаю функцию: func, это входящая переменная, тип и Unpack. Дальше внутри этой функции я создаю ридер, и теперь я пройдусь по полям этой структуры. То есть, несмотря на то что это не рантайм, я могу я могу распарсить этот исходный код, точнее, он уже распаршен, когда я здесь. И я могу итерироваться по полям. Отлично. Так, сначала посмотрим тег структуры — вдруг он там есть. И там есть cgen на минус, то есть не надо — я дальше не пойду, значит, это поле можно пропустить. Теперь у меня есть имя поля и тип поля. Пишем, что мы делаем. Смотрим, какой тип. Если это int, я генерирую код для шаблона распаковки int, если это строчка, я генерирую распаковку строчки. А если какое-то новое поле, то я просто ругаюсь и завершаю программу. Отлично, теперь я говорю, что ошибки нет, закрываю функцию, пишу пустую строку и в общем все, то есть вот моя программа отработала. Что она писала во время выполнения? Я ведь там втыкал debug. Итак, импорт мне нужен, я его пропускаю. Я нашел структуру, отлично. Структуру я генерирую метод Unpack, генерирую код для распаковки полей. То есть вот это то, что уже пошло в работу. Дальше там были какие-то другие вещи, структура Avatar комментов не имеет, поэтому я ее пропущу. Объявление переменной мне тоже не интересно, я ее тоже пропущу, это не объявление типа, который я ищу. И объявление функции, короче, мне тоже не интересно, поэтому я ее тоже пропущу. И посмотрим на код. Вот код, который мне сгенерировался для распаковки ID, поля ID. Я тут ставлю уже сразу, в поле присваиваю — не через магию рефлексии, чего-то там, чего-то там, какие-то структуры и прочее что-то сложное. В Login присваиваю строку — как вы помните, строка распаковывается сначала ее длина, а потом сами данные — и флаги. Теперь неплохо бы это запустить и убедить, что работает. Ура! Раньше функции Unpack не было, программа даже не компилировалась. Теперь она скомпилировалась. И все нормально распаковалось. ID распаковался. RealName не нужно распаковывать, потому что там есть метка, что пропустить. Login распаковался и флаги тоже распаковались. Отлично! То есть я написал программу, которая пишет другую программу. Это не то чтобы близко к искусственному интеллекту, но применяется это достаточно широко. Во-первых, это применяется компилятором. Во-вторых, довольно много синтаксических анализаторов используют такой подход. В-третьих, вы можете сгенерировать для какого-то упаковщика-распаковщика код, который будет работать не при помощи рефлексии в рантайме, а сгенерирует вам код, который будет работать быстрее. В чем минус кодогенерации по сравнению с рефлексией? Я напомню, что минус рефлексии в том, что вы выполняете какую-то дополнительную работу за счет создания структуры рефлексии и прочего — выполняете какую-то дополнительную работу в рантайме. В кодогенерации вы не выполняете дополнительную работу в рантайме, потому что у вас уже готовый код, однако в данном случае недостатком является то, что вам нужно писать программу отдельную и отдельно ее отлаживать, что иногда бывает довольно нетривиально. Кодогенерацию хорошо использовать, если у вас какая-то очень большая, очень интенсивная нагрузка, и вы хотите оптимизировать какие-то горячие участки. И раз уже мы заговорили про горячие участки, то в следующем видео мы рассмотрим вопрос о профилировании и сравним, что быстрее: функция Unpack через рефлексию или Unpack, которая получилась кодогенерацией?