shikhalev.*

Последние записи

ТехнологииПрограммированиеRubyАбстракции

2025.12.23 • Иван Шихалев

Паттерн «Фасад» и гем для DSL

Картинка для привлечения внимания

При написании inat-channel я столкнулся вот с какой проблемой: с одной стороны, более-менее сложные действия должны быть декомпозированы, то есть разбиты на модули и отдельные методы в них; с другой — глубокая декомпозиция заставляет писать длинные обращения к методам типа INat­Chan­nel::​Te­le­gram::​send_​ob­ser­va­ti­on, что неудобно, да и не эстетично. По хо­ро­ше­му вообще нужно верхний уровень методов ин­клю­дить и писать send_observation в основной программе, но если писать все как включаемые методы модулей, то во-первых, они все из всех модулей попадут в финале в одно пространство имен, а во-вторых, туда же попадут и приватные методы.

Для подобных случаев и предназначен паттерн «Фасад» — мы создаем отдельный программный модуль — в данном случае это модуль же в терминах Ruby — который содержит только нужные извне методы, делегируя их в основной нормально де­ком­по­зи­ро­ван­ный код. И затем его спокойно ин­клю­дим в ко­де основного скрипта.

Собственно, именно так я и сделал, определив модуль IC и заполняя его методами в тех же файлах, где они определены. Туда же отправились некоторые методы, не нужные вовне, а используемые слабо логически связанными модулями — здесь речь скорее не о логике и отделении фасада, а о сокращении (текстовом) кросс­мо­дуль­ных вызовов. Впрочем, по мере разрастания структуры вопрос, что считать внутренним, а что внешним, становится не очень однозначным.

Подумав немного на эту тему, я решил вынести абстракцию в код и написал is-dsl — гем, упрощающий, а главное — структурирующий делегирование методов и констант фасаду. Подробнее — в README репозитория (есть русская версия), а также в yard-документации. Здесь коротко обозначу основные особенности:

  • Помимо основного модуля фасада формируется теневой модуль — для использования внутри библиотеки. Все, что попадает в основной, попадает и в теневой, обратное неверно. См. shadow-методы.

  • Можно делегировать как статически сингл­тон-методы классов и модулей, так и лениво методы произвольных сингл­тон-объектов, где сам объект создается или получается через вызов блока. См. lazy-ме­то­ды. Предполагается применение с ме­то­дом класса instance в пер­вую очередь.


Планов менять или добавлять что-то в основную функциональность нет, думаю когда-нибудь сделать плагин для YARD, чтобы делегирование методов правильно автоматически документировалось.

DSLgemis-dslпаттерны проектирования

ТехнологииПрограммированиеАбстракции

2013.11.11 • Иван Шихалев

На полях: RTTI для статики

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

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

RTTIоптимизацияразмышлизмыстатическая типизацияязыки программирования

ТехнологииПрограммированиеАбстракции

2013.10.09 • Иван Шихалев

Возвращаясь к размышлизму...

Или снова об оптимизации скриптовых языков. В предыдущей заметке я это всё как-то сумбурно описал. Вкратце: для условного веб-сервиса минута инициализации много «дешевле», чем секунда обработки запроса. Поэтому, проведя собственно инициализацию, имеет смысл оптимизировать с ее учетом дальнейшую работу. И для скриптовых языков это вполне возможно, в отличие от компилируемых. А вот как это мне представляется в плане практики:

  • Составляем граф переходов для загруженной программы. Естественно, учитываем на данном этапе все возможные переходы.

  • Выделяем самый внешние (т.е. не вложенные ни в какие другие) циклы. Получаем точки-входы и точки перед ними, в которые гарантированно не происходит возврата.

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

  • Когда исполнение доходит до такой точки невозврата, включаем оптимизацию дальнейшего кода:

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

Естественно, это всё не отменяет классической алгоритмической оптимизации, JIT-компиляции и т.д. после сокращения всего ненужного. И естественно, всё это не будет работать при «eval(string)» внутри циклов, но так делать в любом случае не стоит…

оптимизацияразмышлизмыскриптовые языкиязыки программирования

ТехнологииПрограммированиеАбстракции

2013.05.17 • Иван Шихалев

Об оптимизациях скриптовых языков...

Подумалось: а ведь скриптовые интерпретируемые языки вполне способны на не­ко­то­рых задачах обгонять компилируемые и сильно, если программу не исполнять, а вычислять как формулу — это же совершенно не обязательно делать по-порядку. Можно «раскрыть скобки», «привести подобные», «сократить»… Соременные компиляторы, собственно, так и делают, но они могут зафиксировать только параметры, известные на этапе компиляции, а для прочих приходится учитывать все возможные комбинации значений, тогда как при ка­ж­дом конкретном запуске программы их подмножество существенно ограничено. Многие вещи — такие как: переменные среды, параметры командной строки и т.д. — определяются при старте и далее могут считаться константами, соответственно, упрощаются выражения от них и т.д.

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

Что для этого нужно? Во-первых, структурированное внутреннее представление, с которым можно манипулировать как с фор­му­лой — это не текст и не байт-код (в Ruby свежих версий именно так). Во-вторых, необходимо как-то определять наличие побочных эффектов (в Ruby это, пожалуй, может быть довольно просто реализовано для собственно ruby-методов, а вот с мо­ду­ля­ми, загружаемыми как .so и .dll будет проблема. Впрочем, достаточно к нескольким системным фунциям типа define_method добавить соответствующие описательные параметры). И в-третьих, желательна жесткая инкапсуляция данных, так что Ruby для этой цели подходит, а вот, скажем, JavaScript — намного хуже.

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

оптимизацияразмышлизмыскриптовые языкиязыки программирования

ТехнологииПрограммированиеАбстракцииМышление

2013.04.26 • Иван Шихалев

Слоган

TDD — выдаем нужду за добродетель. Убедительно.

TDDметодологияподумалосьразработка