Генерація коду

Генерація коду
Переклад статті від Роба Пайка про створення коду. З офіційного блоґу.
Властивість універсальних обчислень – Т'юрінг повнота – це здатність однієї програми написати іншу програму. Це потужна ідея, яка, як правило, залишається недооціненою. Наприклад, це одна з основних частин, що визначають компілятор. Так само, за цим принципом працює команда для тестування: спочатку скануються пакети, які мають бути протестовані, створюється тимчасова програма, що містить обв'язку для тестів і кастомізована для конкретного пакету, потім ця програма тимчасово компілюється і запускається. Хоча це досить велика послідовність дій, сучасні комп'ютери досить швидкі, щоб виконати її за мілісекунди.
Є багато інших прикладів, коли програми пишуть програми. Yacc, наприклад, читає опис граматики та генерує програму, яка може розбирати цю граматику. "Компілятор" протобафа(Protocol Buffers) читає опис інтерфейсів та генерує визначення структур, методів та інший супутній код. Різні засоби конфігурації працюють аналогічним способом, отримуючи інформацію про оточення та генеруючи різні скрипти, кастомізовані під конкретне оточення.
Програми, які пишуть інші програми - це важливий елемент у розробці програмного забезпечення. Але програми типу Yacc генерують код, який має бути інтегрований у процесі складання, тобто результат може бути скомпільований. Коли використовуються сторонні інструменти складання, такі як Make, це стає простим завданням. Але в Go інструменти збирання отримують всю інформацію тільки з вихідних .go і це стає проблемою. Просто немає механізму для запуску з Yacc за допомогою go інструментів.
Але тепертака нагода з'явилася.
Використовувати go generate досить легко. Для розминки, подивимося, як генерувати Yacc граматики. Припустимо, у вас є вхідний файл gopher.y, який описує граматики для вашої нової мови. Для отримання вихідника .go, що реалізує цю граматику, нам, як правило, потрібно викликати стандартну версію yacc для Go:
Параметр -o визначає ім'я вихідного файлу -p вказує пакет.
Тепер запустимо все це. Заходимо в директорію з проектом і виконуємо go generate потім go build і так далі:
Ось так це працює. Припустимо, що у нас не було жодних помилок, команда go generate запустила yacc, який створить gopher.go. Після цього у нас будуть усі необхідні файли для складання проекту, тестування та нормальної роботи. Після кожної модифікації gopher.y потрібно просто запустити go generate для перегенерування парсера.
Дивіться документацію для більш детального розуміння роботи go generate, включаючи параметри, змінні оточення та інше.
Ця команда не робить нічого, що не можна зробити за допомогою Make або інших інструментів збирання, але тепер це йде в коробці і чудово вписується в Go екосистему. Тільки не забувайте, це інструмент для розробників пакетів, але не для їх користувачів. Крім того, якщо пакет повинен бути отриманий за допомогою go get , як тільки файл буде згенерований і тестований він повинен бути доданий до системи контролю версій та бути доступним для клієнтів.
Тепер спробуємо щось новеньке. Зовсім інший приклад вигоди від go generate – можливість використовувати програму stringer доступну як частину golang.org/x/tools. Ця програма автоматично генерує рядкові методи наборів цілих констант. Вона не є частиною стандартного постачання,але її легко встановити додатково:
Приклад із документації самого stringer. Припустимо, у нас є код, що містить інтові константи, які визначають різні типи таблеток:
Для налагодження непогано було б красиво та інформативно виводити відображати константи, що означає нам потрібен метод з такою сигнатурою:
Який реалізується ось так:
Звичайно, є й інші способи написати цю функцію. Ми можемо використовувати слайс з рядками і Pill як індекси, або карти, або інші способи. Але в будь-якому випадку ми повинні стежити за списком таблеток і за правильністю цієї функції (дві назви парацетамолу зроблять це завдання ще складнішим). Крім того, саме питання який підхід використовувати залежить від типів та значень: знакові та беззнакові, з нуля чи ні і так далі.
Це правило означає, що повинна запуститися програма stringer, яка згенерує метод String для типу Pill . Результат роботи програми запишеться у файл pill_string.go (ця назва за промовчанням, його можна змінити за допомогою прапора -o).
Давайте запустимо це:
Вміст файлу pill_string.go:
Тепер при зміні типу Pill нам просто потрібно буде запустити
для поновлення рядкового методу. І, звичайно, якщо у нас кілька типів повинні бути налаштовані подібним чином, ми також будемо запускати тільки одну команду для оновлення всіх методів String.
Без сумніву, згенерований метод потворний. Це нормально, тому що люди не працюватимуть у рамках цього методу, а машино-генерований код завжди потворний. Всі імена об'єднані разом в один рядок, який зберігається в пам'яті (тільки один рядок для всіх імен, навіть якщо імен буде 100 500 мільйонів). Далі йде масив _Pill_index з індексами для імен, що реалізує простутехнологію, аналогічну map. Зверніть увагу, що _Pill_index саме масив(а не слайс) з елементами типу uint8 - найлегший інтовий тип, достатній для охоплення всього діапазону значень. Якщо у нас буде більше значень або з'являться негативні, тоді, при генерації, тип автоматично заміниться більш відповідним.
Підхід, що реалізується в рядковому методі, що генерується за допомогою stringer, так само може змінюватися в залежності від списку констант. Наприклад, якщо константи невеликі, можна використовувати map . Ось тривіальний приклад для констант ступенів двійки:
Простіше кажучи, автоматична генерація робить роботу швидше і ефективнішою за живу людину.
Є безліч інших способів використання go generate у Go. Це створення юнікод-таблиць у пакеті unicode , створення ефективних методів для кодування та декодування масивів у пакеті encoding/gob , отримання даних для часових зон у пакеті time та багато іншого.
Будь ласка, використовуйте go generate творчо та експериментуйте.
І обов'язково використовуйте інструмент stringer для машинної генерації ефективнішого коду.