Skip to content

Development‐Philosophy

Maxim Malyshev edited this page May 23, 2026 · 5 revisions

Философия разработки

Общие соображения

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

Наша команда небольшая, поэтому мы не можем делать всё. Кроме того, есть области, которые не стоит развивать: они мало приносят пользы и создают долговременные затраты на поддержку. Основная задача — расширять набор поддерживаемых паттернов и алгоритмов. Расширение нужно проводить так, чтобы максимально не увязать во второстепенных деталях и не возвращаться назад из‑за поломок или препятствий при добавлении нового паттерна/алгоритма.

Предполагается, что сами алгоритмы в большинстве случаев стабильны: они не ломаются и не требуют вмешательства внутрь. Если всё же необходимо менять алгоритм, заводится отдельная таска с отдельным разработчиком — работа с алгоритмами наиболее когнитивно затратна и должна выполняться основательно.

Принципы

Мы реализуем только «ядро» — минимальный набор функциональности, который:

  1. можно эффективно и однозначно реализовать;
  2. не требует больших дополнительных усилий по поддержке;
  3. попадает в наш фокус (подробности — в соответствующих статьях).

Примеры:

  1. Допустим, мы реализуем исключения для некоторого паттерна. И так получается, что мы не можем однозначно определить записи, которые нужно удалить из конфликтующего набора, чтобы паттерн выполнялся. Так бывает, например, для Denial Constraints.

    Можно было бы предложить пользователю минимальный набор записей для удаления/редактирования, но эта задача — NP‑трудная задача на графах, для которой существуют приближённые алгоритмы.

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

  2. При реализации табличных паттернов иногда возникает соблазн добавить возможность явно указывать, по каким колонкам искать паттерн (например, для функциональных зависимостей). Это — оверинжиниринг, и в него не стоит впадать.

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

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

  3. Сами по себе мы не реализуем теоретические задачи вроде проверки непротиворечивости набора зависимостей, проверок выводимости зависимостей, тривиальности/минимальности зависимостей и т.д.

    Эти задачи интересны, но не входят в текущий фокус проекта (то, как мы представляем Desbordante в статьях). Кроме того, практическая польза от них в наших юзкейcах пока невысока.

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

Итог по всем пунктам: думайте и спрашивайте, перед тем как писать код.

Как писать код

Дублирование структур данных

Мы стремимся дублировать реализацию структур данных и близкой логики. Это облегчает разработку по ряду причин:

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

  2. Многие алгоритмы требуют разных деталей. Например, некоторые графовые паттерны предполагают наличие меток на ребрах, некоторые нет. Причем, одни предполагают наличие нескольких меток, а другие — одной. Универсальная структура замедлит какие-то алгоритмы (данные перестанут ложиться в кеш, например).

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

В общем, очень плохо когда алгоритмы пересекаются по структурам данных. Именно поэтому у нас 5+ реализаций партиций (PLI). Конечно, это методологически неправильно, но сейчас у нас нет возможности вложить столько инженерных усилий, чтобы сделать эту часть нормально. Когда-нибудь, когда будет накоплено достаточно алгоритмов, мы возможно стартуем треки по унификации.

Поэтому копируйте структуры данных, а если сомневаетесь — спросите.

Константность входных данных

В связи с возможностью повторного запуска алгоритма через один и тот же объект, execute не должен портить входные данные. То есть, мы можем несколько раз подряд вызвать execute и не будет такого, что второй и последующие разы будет выдан неверный ответ. Как следствие, данные должны читаться только в load_data, execute не должен читать их повторно, даже и в reset_state.

Clone this wiki locally