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

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

Парадокс портабельности

Прежде чем мы углубимся в ересь, позвольте мне уточнить, о чём мы говорим. Непортабельный код — это программное обеспечение, глубоко интегрированное с конкретными платформами, технологиями или экосистемами. Он использует специализированные функции и оптимизации, которые нелегко перенести куда-либо ещё. Это противоположность гипотетическому портабельному утопическому миру, к которому нас часто призывают стремиться.

Проблема одержимости портабельностью заключается в том, что она часто проистекает из страха, унаследованного из 90-х и начала 2000-х годов — времени, когда привязка к платформе действительно означала катастрофу. Помните, когда переключение систем баз данных могло занять месяцы? Когда контроль поставщика был действительно угрожающим? Мы усвоили свои уроки на горьком опыте и теперь преподаём их поколению, для которого эти конкретные проблемы в значительной степени ушли в прошлое.

Стоимость поддержания этой гибкости редко фигурирует в ретроспективах проектов. Она скрывается в:

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

Грязный секрет? Большинство кодов, написанных «портабельно», никогда не покидает платформу, на которой они были созданы. Мы бежим марафон, готовясь к триатлону, в котором никогда не будем участвовать.

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

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

Сценарий первый: система, критичная к производительности

Вы создаёте механизм торговли в реальном времени. Важны микросекунды. Фреймворк, который обещает портабельность между базами данных, облачными провайдерами и операционными системами? Он будет стоить вам этих микросекунд с каждым уровнем абстракции. Тем временем ваш конкурент написал компактный, специализированный код, который использует специфические JSON-операторы PostgreSQL, задействовал оптимизации ядра Linux TCP и глубоко интегрировался с сетевой инфраструктурой AWS.

Они не совершают ошибку. Они делают выбор, который служит их реальной проблемной области.

Сценарий второй: стабильная экосистема

Вы создаёте внутренние инструменты для компании, которая полностью работает на конкретном облачном провайдере. У них есть контракты, опыт и шаблоны интеграции, встроенные в эту экосистему. Написание ORM-слоёв, не зависящих от базы данных, и кода инфраструктуры, нейтрального к поставщику, — это не гибкость, это трата. Это всё равно что проектировать дом так, чтобы его можно было выкорчевать и пересадить на трёх континентах, когда ему нужно работать только в Пало-Альто.

Сценарий третий: стартап с быстрой итерацией

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

Скрытые затраты на портабельность

Давайте подсчитаем, чем вы платите за эту гибкость:

# ПОРТАБЕЛЬНЫЙ (но медленный и сложный)
class DataProvider(ABC):
    @abstractmethod
    def query(self, query_string: str) -> List[Dict]:
        pass
class PostgresDataProvider(DataProvider):
    def query(self, query_string: str) -> List[Dict]:
        # Нормализуем запрос к универсальному SQL
        normalized = self.normalize_query(query_string)
        return execute_generic_query(normalized)
class MongoDataProvider(DataProvider):
    def query(self, query_string: str) -> List[Dict]:
        # Преобразуем в конвейер агрегации
        pipeline = self.convert_to_pipeline(query_string)
        return execute_pipeline(pipeline)
# Использование
provider = get_provider_from_config()
results = provider.query("SELECT * FROM users WHERE age > 25")
# Время: ~45 мс на запрос (с накладными расходами)

Сравните это с:

# НЕПОРТАБЕЛЬНЫЙ (быстрый и понятный)
import psycopg2.extras
def get_active_users(min_age: int):
    with get_db_connection() as conn:
        conn.row_factory = psycopg2.extras.RealDictRow
        cursor = conn.cursor()
        cursor.execute(
            "SELECT * FROM users WHERE age > %s AND status = 'active'",
            (min_age,)
        )
        return cursor.fetchall()
# Время: ~3 мс на запрос (прямой, оптимизированный)

Это не микрооптимизация. Это разница в 15 раз. Умножьте это на тысячи запросов в загруженном приложении, и деградация производительности из-за портабельности станет платой за функции, которая повлияет на пользовательский опыт.

А сложность? Уровень абстракции добавляет умственную нагрузку. Будущим разработчикам нужно понимать не только код, но и особенности уровня абстракции, крайние случаи и сопоставления между общими понятиями и фактическими реализациями. Мы променяли явную сложность на скрытую, а скрытая сложность всегда побеждает с точки зрения фактического бремени поддержки.

Создание непортабельного кода преднамеренно

Если вы собираетесь привязываться, делайте это осознанно. Вот как:

Шаг 1: Документируйте свои ограничения

Запишите, почему вы делаете этот выбор. Не в комментариях к коду, а в вашем README или документации по архитектуре:

## Почему мы используем специфические функции PostgreSQL
- Мы зависим от операторов JSONB для нашей системы рекомендаций
- Оконные функции обеспечивают повышение производительности, которое мы измерили на уровне 40% по сравнению с агрегацией на уровне приложения
- Наша команда обладает глубокими знаниями PostgreSQL и знает его особенности
## Путь миграции (при необходимости)
Мы довольны этим решением, потому что:
1. У нас есть 18 месяцев запаса, прежде чем это станет актуальным
2. Переключение потребует около 3 недель работы, если мы дойдём до этого момента
3. Повышение производительности оправдывает затраты на переключение

Шаг 2: Изолируйте свой специализированный код

Не разбрасывайте логику, специфичную для платформы, по всей кодовой базе. Ограничьте её:

# postgres_specialized.py
# Этот модуль содержит оптимизации, специфичные для PostgreSQL
# НЕ используйте эти функции в коде, предназначенном для работы с базами данных без привязки к конкретной СУБД
from typing import List, Dict, Any
import psycopg2.extras
class PostgresOptimizedQueries:
    """
    Специфичные для PostgreSQL реализации запросов.
    Используются JSONB, оконные функции и другие особенности pg.
    """
    def __init__(self, connection):
        self.conn = connection
    def get_user_stats_with_ranking(self) -> List[Dict[str, Any]]:
        """
        Использует оконные функции и операторы JSONB.
        Не может быть легко перенесён на другие базы данных.
        """
        query = """
        SELECT 
            id,
            name,
            score,
            RANK() OVER (ORDER BY score DESC) as user_rank,
            metadata->'preferences'->>'theme' as theme
        FROM users
        WHERE metadata @> '{"active": true}'::jsonb
        ORDER BY score DESC
        """
        cursor = self.conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
        cursor.execute(query)
        return cursor.fetchall()
    def bulk_upsert_with_conflict_handling(
        self, 
        records: List[Dict]
    ) -> int:
        """
        Использует предложение PostgreSQL ON CONFLICT.
        """
        query = """
        INSERT INTO products (id, name, price, updated_at) 
        VALUES %s
        ON CONFLICT (id) DO UPDATE SET
            name = EXCLUDED.name,
            price = EXCLUDED.price,
            updated_at = EXCLUDED.updated_at
        """
        cursor = self.conn.cursor()
        from psycopg2.extras import execute_values
        execute_values(cursor, query, records, page_size=1000)
        self.conn.commit()
        return cursor.rowcount