shikhalev.*

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

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

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

Об инвалидацию кэша

КДПВ сгенерирована нейросетью по запросу «cache invalidation»

Как известно, в программировании есть только две реально сложные задачи: именование переменных и инвалидация кэша1. С первой мы сделать ничего не можем, со второй, конечно, тоже, но что-то делать приходится…

И вот в процессе большого рефакторинга inat-get я в очередной раз задумался. Суть тут вот в чем: данные, которые требуется получать из API iNa­tu­ra­list — очень большие (зависит от це­лей, конечно, но они могут быть очень большими), и логично их за­кэ­ши­ро­вать в локальной базе данных. Естественно, кэшированные данные нужно обновлять.

Есть прекрасный параметр updated_since в запросах к API, т.е. мы храним у себя информацию о сделанных запросах, и когда нам нужно получить новые данные по тем же условиям, указываем дату/время предыдущего запроса в этом параметре. Но полностью проблему это не решает: updated_since не отменяет (и не должен отменять) все остальные параметры запроса, т.е. те наблюдения, которые выпали из запроса, в выборку не попадут. И останутся в локальной БД в старом состоянии.

Ну, то есть, выбрали мы, например, данные по проекту, который фильтрует наблюдения с quality_grade=research, а потом пришел добрый человек и заметил, что в наблюдении культурное растение. Наблюдение выпадает из проекта, но мы об этом не можем узнать через обновление…

Какие есть пути решения? Вижу несколько вариантов, ни один из которых меня не устраивает полностью…

  1. Автор этой фразы, предположительно, Фил Карлтон (Phil Karlton), ведущий инженер компании Netscape. 


Читать далее »

iNaturalistinat-get

ТехнологииПрограммирование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методологияподумалосьразработка