Язык программирования выбирают не по абстрактному критерию «какой язык лучше», а по тому, какой набор ограничений конкретной системы он должен выдержать.
На практике выбирается даже не язык как синтаксис, а целый технологический контур:
язык + runtime + экосистема библиотек + фреймворки + инструменты + специалисты + эксплуатационная модель
Например, выбор Java обычно означает не только Java, но и JVM, garbage collector, Spring Boot, Maven или Gradle, профилировщики, observability-инструменты, зрелую экосистему драйверов и доступность Java-разработчиков.
Поэтому вопрос:
«На каком языке написать сервис?»
лучше развернуть в вопрос:
«Какой технологический контур позволит системе выполнить требования с минимальным совокупным риском разработки и эксплуатации?»
1. Сначала определить, что именно разрабатывается
Нельзя выбирать язык до определения класса задачи.
Один и тот же язык может быть прекрасен для одного типа системы и неудобен для другого.
Backend бизнес-системы
Например:
- интернет-магазин;
- банковская система;
- система бронирования;
- CRM;
- биллинг;
- документооборот;
- платёжный сервис.
Здесь обычно важны:
- транзакции;
- интеграции;
- поддерживаемость;
- безопасность;
- сложная бизнес-логика;
- долговременная эксплуатация;
- большое количество разработчиков.
Частые варианты:
- Java;
- Kotlin;
- C#;
- Go;
- TypeScript;
- Python.
Для большой бизнес-системы Java или C# часто выбирают не потому, что они «самые быстрые», а потому что их экосистемы хорошо приспособлены к длинной жизни системы: транзакциям, безопасности, миграциям баз данных, observability, тестированию и управлению зависимостями.
Высоконагруженные сетевые сервисы
Например:
- API gateway;
- proxy;
- WebSocket-сервер;
- message broker;
- streaming-система;
- сервис с сотнями тысяч соединений;
- сетевой агент.
Здесь важны:
- потребление памяти на соединение;
- стоимость переключения контекста;
- модель конкурентности;
- производительность runtime;
- управление памятью;
- предсказуемость задержек.
Частые варианты:
- Go;
- Rust;
- C++;
- Java;
- Erlang или Elixir.
Но само наличие большого RPS ещё не означает, что нужен C++ или Rust. Во многих системах предел находится не в языке, а в:
- базе данных;
- внешнем API;
- сети;
- блокировках;
- неверной модели данных;
- последовательной обработке;
- отсутствии кеширования.
Язык становится критичным, когда стоимость runtime действительно занимает существенную часть бюджета производительности.
Data Science и машинное обучение
Здесь часто выбирается Python, хотя вычисления непосредственно выполняются не самим Python, а нативными библиотеками:
- NumPy;
- PyTorch;
- TensorFlow;
- pandas;
- scikit-learn.
Python побеждает не скоростью языка, а экосистемой, скоростью эксперимента и доступностью моделей.
Это хороший пример того, что:
экосистема может быть важнее технических свойств самого языка.
Frontend
Для браузера фактический выбор ограничен:
- JavaScript;
- TypeScript;
- WebAssembly как дополнительный механизм.
В большинстве серьёзных проектов выбирают TypeScript, потому что статическая типизация уменьшает количество ошибок при развитии большого интерфейса.
Но выбор TypeScript не решает архитектуру frontend-приложения. Остаются отдельные решения:
- React, Vue, Angular или другой фреймворк;
- клиентское состояние;
- серверное состояние;
- SSR;
- микрофронтенды;
- формат API;
- стратегия кеширования.
Мобильные приложения
Android
- Kotlin;
- Java;
- иногда кроссплатформенные решения.
iOS
- Swift;
- Objective-C для старого кода.
Кроссплатформенная разработка
- Dart с Flutter;
- TypeScript или JavaScript с React Native;
- Kotlin Multiplatform;
- C# с .NET MAUI.
Здесь выбор зависит не только от языка, но и от того, насколько приложению нужен доступ к нативным функциям платформы.
Системное и встроенное программирование
Например:
- операционные системы;
- драйверы;
- прошивки;
- embedded;
- базы данных;
- браузерные движки;
- криптографические библиотеки.
Здесь важны:
- контроль памяти;
- отсутствие или минимальность runtime;
- размер бинарника;
- предсказуемость выполнения;
- работа без garbage collector;
- доступ к аппаратуре.
Частые варианты:
- C;
- C++;
- Rust;
- иногда Zig.
Скрипты и автоматизация
Например:
- обработка файлов;
- DevOps-скрипты;
- сбор данных;
- небольшие интеграции;
- внутренние инструменты.
Частые варианты:
- Python;
- Bash;
- PowerShell;
- JavaScript или TypeScript;
- Go, если нужен единый переносимый бинарник.
Здесь особенно важна стоимость разработки. Нет смысла строить тяжёлую платформу для скрипта, который запускается раз в сутки и обрабатывает тысячу строк.
2. Определить нефункциональные требования
После класса задачи нужно рассмотреть нефункциональные требования.
Именно они чаще всего определяют язык.
2.1. Производительность
Нужно уточнить, что именно означает «производительность».
Это могут быть разные характеристики:
- throughput, то есть количество операций в секунду;
- latency, то есть время одной операции;
- tail latency, например p95, p99 и p99.9;
- потребление CPU;
- потребление памяти;
- количество одновременных соединений;
- скорость запуска;
- размер исполняемого файла.
Язык может быть хорош по одному параметру и хуже по другому.
Например:
- Java может обеспечивать высокий throughput, но требует настройки JVM и garbage collector;
- Go удобен для большого количества конкурентных сетевых операций;
- Rust позволяет получить высокую производительность с контролем памяти, но увеличивает сложность разработки;
- Python удобен для бизнес-логики и автоматизации, но CPU-intensive вычисления обычно требуют нативных библиотек или отдельных сервисов.
Важно не говорить:
«Нам нужен быстрый язык».
Нужно говорить:
«Нам нужна обработка 20 000 запросов в секунду при p99 менее 100 миллисекунд и лимите памяти 4 гигабайта на экземпляр».
Тогда выбор становится предметным.
2.2. Модель конкурентности
Разные языки предлагают разные модели конкурентного выполнения.
Потоки
Характерны для:
- Java;
- C#;
- C++;
- Rust.
Подход хорошо знаком, но требует понимания:
- блокировок;
- race condition;
- deadlock;
- thread pool;
- синхронизации;
- memory model.
Async/await
Используется в:
- JavaScript и TypeScript;
- Python;
- C#;
- Rust;
- Kotlin;
- Java.
Подходит для I/O-bound нагрузки, но может усложнить:
- трассировку;
- обработку ошибок;
- управление контекстом;
- отладку длинных асинхронных цепочек.
Goroutines
Go предлагает лёгкие конкурентные задачи и каналы.
Это делает сетевые сервисы сравнительно простыми, но не отменяет:
- гонки данных;
- утечки goroutine;
- блокировки каналов;
- неконтролируемое создание конкурентных операций.
Actor model
Используется в Erlang, Elixir, Akka-подобных системах.
Хорошо подходит для:
- большого количества независимых процессов;
- отказоустойчивых систем;
- долгоживущих соединений;
- распределённых приложений.
Выбирать нужно не «самую современную» модель, а модель, соответствующую характеру нагрузки.
2.3. Управление памятью
Garbage collection
Java, C#, Go, JavaScript и Python автоматически управляют памятью.
Преимущества:
- меньше ошибок use-after-free;
- проще разработка;
- меньше ручного контроля;
- выше скорость реализации.
Недостатки:
- дополнительные расходы runtime;
- возможные паузы;
- сложнее полностью контролировать потребление памяти;
- иногда требуется настройка garbage collector.
Ручное или детерминированное управление
C, C++ и Rust дают больший контроль.
Преимущества:
- предсказуемость;
- отсутствие глобальных GC-пауз;
- контроль размещения объектов;
- меньший runtime overhead.
Недостатки:
- более высокая сложность;
- больше требований к разработчикам;
- выше стоимость реализации;
- в C и C++ больше рисков повреждения памяти.
Rust уменьшает часть этих рисков через систему владения, но переносит сложность на этап компиляции и проектирования кода.
2.4. Безопасность
Нужно различать несколько видов безопасности.
Безопасность памяти
Особенно важна для:
- сетевой инфраструктуры;
- браузеров;
- криптографических компонентов;
- операционных систем;
- парсеров недоверенных данных.
Здесь Rust может иметь преимущество перед C и C++, поскольку предотвращает значительную часть ошибок памяти на уровне компилятора.
Безопасность типов
Статическая типизация помогает обнаруживать ошибки до запуска.
Это особенно важно при:
- большом кодовом основании;
- десятках разработчиков;
- сложных доменных моделях;
- масштабном рефакторинге;
- длительной эксплуатации.
Зрелость security-экосистемы
Нужно учитывать:
- библиотеки аутентификации;
- поддержку OAuth 2.0 и OpenID Connect;
- криптографические библиотеки;
- управление секретами;
- сканеры зависимостей;
- скорость закрытия уязвимостей;
- управление версиями зависимостей.
Язык с красивой системой типов может проиграть более зрелому стеку, если для него нет надёжных библиотек нужного класса.
3. Учитывать существующую систему
Это один из самых сильных факторов.
Если система уже написана на Java, добавление одного сервиса на Rust или Elixir должно иметь конкретное обоснование.
Иначе организация получает дополнительную цену:
- отдельный build pipeline;
- другие инструменты;
- новую систему зависимостей;
- другой runtime;
- отдельный способ мониторинга;
- отдельный способ профилирования;
- необходимость искать специалистов;
- новые правила code review;
- разную модель ошибок;
- другую библиотеку observability;
- сложности локального запуска.
Поэтому для существующей системы действует консервативный принцип:
Новый язык должен давать преимущество, достаточное для компенсации постоянной стоимости технологического разнообразия.
Не просто «на этом языке удобнее написать сервис», а:
«Этот компонент обрабатывает недоверенный бинарный протокол, должен работать с минимальным потреблением памяти и требует предсказуемой задержки, поэтому Rust оправдан».
4. Оценить компетенции команды
Язык существует не отдельно от людей.
Можно выбрать технически идеальный язык и провалить проект, потому что:
- команда его не знает;
- никто не умеет профилировать runtime;
- отсутствуют архитектурные практики;
- разработчики копируют шаблоны без понимания;
- невозможно проводить качественный code review;
- ошибки обнаруживаются только в production.
Здесь нужно оценить:
- сколько разработчиков уже знают язык;
- насколько глубоко они его знают;
- кто будет принимать архитектурные решения;
- кто будет поддерживать систему через два года;
- можно ли нанять новых специалистов;
- сколько времени займёт обучение;
- есть ли внутри компании экспертиза по эксплуатации runtime.
Фраза «язык простой» часто обманчива.
Например, синтаксис Go действительно относительно небольшой, но production-разработка требует понимания:
- контекстов;
- отмены операций;
- goroutine leaks;
- поведения каналов;
- interface semantics;
- error wrapping;
- профилирования;
- сборщика мусора;
- scheduler;
- работы с памятью.
Простой синтаксис не означает простую эксплуатацию системы.
5. Оценить рынок специалистов
Для коммерческого продукта важен не только текущий состав команды, но и возможность её изменять.
Нужно понимать:
- насколько легко нанять разработчиков;
- какой у них средний уровень;
- насколько дорог рынок;
- существуют ли специалисты по конкретному стеку;
- насколько язык распространён в регионе;
- есть ли у кандидатов production-опыт;
- легко ли заменить ушедшего разработчика.
Редкий язык может быть оправдан, когда:
- он даёт уникальное техническое преимущество;
- команда уже обладает экспертизой;
- компонент изолирован;
- организация готова инвестировать в обучение;
- риск найма принят сознательно.
Но выбирать редкий язык только потому, что он интеллектуально интересен, означает превращать личное предпочтение архитектора в долгосрочный организационный налог.
6. Проверить зрелость экосистемы
Нужно выяснить, существуют ли качественные решения для конкретного домена.
Например, для backend-сервиса могут понадобиться:
- HTTP-сервер;
- PostgreSQL-драйвер;
- connection pool;
- Kafka-клиент;
- Redis-клиент;
- OpenTelemetry;
- Prometheus;
- tracing;
- structured logging;
- миграции базы данных;
- OAuth 2.0;
- JWT;
- gRPC;
- сериализация;
- circuit breaker;
- retry;
- distributed locking;
- test containers.
Важно не только наличие библиотеки, но и:
- поддерживается ли она;
- когда был последний релиз;
- сколько у неё пользователей;
- есть ли совместимость с актуальными версиями;
- понятна ли модель ошибок;
- поддерживает ли она observability;
- нет ли критических ограничений;
- как быстро исправляются уязвимости.
Экосистема должна проверяться не по количеству пакетов, а по качеству необходимых инфраструктурных компонентов.
7. Оценить скорость разработки
В некоторых продуктах главный риск не runtime-производительность, а то, что продукт вообще не будет создан.
Для MVP могут быть критичны:
- короткий цикл изменения;
- простая разработка API;
- доступность библиотек;
- интерактивная отладка;
- минимальный boilerplate;
- возможность быстро проверять гипотезы.
Поэтому Python, TypeScript, Ruby или Kotlin могут быть рациональнее низкоуровневых языков, даже если те технически производительнее.
Но нужно различать:
Скорость первой реализации
Как быстро команда может выпустить первую версию.
Скорость развития через два года
Насколько легко:
- читать код;
- менять модель;
- проводить рефакторинг;
- находить ошибки;
- обновлять зависимости;
- подключать новых разработчиков.
Динамический язык может ускорить первые месяцы, но создать трудности в большом кодовом основании. Статическая типизация иногда замедляет первые шаги, но ускоряет последующую эволюцию.
Поэтому нужно оптимизировать не только time-to-market, но и:
time-to-safe-change
То есть время, необходимое для безопасного внесения изменения.
8. Оценить эксплуатацию
Язык нужно выбирать с учётом production, а не только разработки.
Нужно проверить:
- как собирается приложение;
- как оно контейнеризуется;
- сколько занимает образ;
- как быстро запускается;
- сколько памяти потребляет;
- как настраивается runtime;
- как снимаются heap dump и thread dump;
- как выполняется профилирование;
- как выглядят stack trace;
- как распространяется trace context;
- как работает graceful shutdown;
- как выполняется обновление;
- как ведёт себя приложение при memory limit в Kubernetes.
Например, контейнерный сервис должен правильно реагировать на:
SIGTERM;- завершение pod;
- закрытие HTTP listener;
- прекращение приёма новых запросов;
- завершение текущих операций;
- остановку Kafka consumer;
- возврат незавершённых сообщений;
- истечение termination grace period.
Если экосистема или команда плохо понимает эти механизмы, формально «рабочий» язык создаст эксплуатационно хрупкую систему.
9. Учесть ограничения платформы
Иногда язык уже почти выбран платформой.
Например:
- браузер означает JavaScript или TypeScript;
- JVM-инфраструктура делает Java или Kotlin естественным выбором;
- .NET-среда делает C# естественным выбором;
- Android тяготеет к Kotlin;
- iOS к Swift;
- embedded может требовать C или C++;
- CUDA-вычисления часто требуют C++ и специализированного стека;
- serverless-платформа может ограничивать runtimes;
- корпоративная платформа может разрешать только утверждённый набор технологий.
Нужно учитывать:
- поддержку операционных систем;
- архитектуры процессора;
- доступность runtime;
- ограничения cloud-провайдера;
- требования сертификации;
- корпоративные security policies;
- лицензирование.
10. Рассчитать полную стоимость владения
Стоимость языка состоит не только из зарплаты разработчиков.
В неё входят:
- разработка;
- обучение;
- найм;
- эксплуатация;
- инфраструктура;
- обновление runtime;
- устранение уязвимостей;
- поддержка библиотек;
- отладка production-инцидентов;
- миграции;
- технологический долг;
- стоимость ухода ключевых специалистов.
Можно условно выразить:
[
TCO =
C_{development}
+
C_{hiring}
+
C_{infrastructure}
+
C_{operations}
+
C_{incidents}
+
C_{evolution}
+
C_{migration}
]
Язык с более высокой производительностью может сократить инфраструктурные расходы, но увеличить стоимость разработки.
И наоборот, язык с быстрым development cycle может требовать больше серверов, но всё равно быть экономически выгоднее.
11. Отдельно определить критические свойства системы
Не нужно сравнивать языки по десяткам критериев одинакового веса.
Нужно найти два или три свойства, которые действительно определяют судьбу проекта.
Например:
Система бронирования отелей
Критические свойства:
- корректность конкурентного бронирования;
- транзакционная модель;
- интеграции;
- поддерживаемость;
- способность команды развивать сложную бизнес-логику.
Здесь Java, Kotlin или C# могут быть естественнее Rust, потому что основная сложность находится в доменной модели и данных, а не в ручном управлении памятью.
WebSocket gateway на миллион соединений
Критические свойства:
- память на соединение;
- модель конкурентности;
- backpressure;
- latency;
- сетевой runtime.
Здесь Go, Rust, Java Netty, Erlang или Elixir могут рассматриваться серьёзнее.
ML-прототип
Критические свойства:
- доступ к моделям;
- скорость эксперимента;
- готовые библиотеки;
- интеграция с GPU.
Здесь Python почти автоматически оказывается в числе главных кандидатов.
Embedded-устройство
Критические свойства:
- ограниченная память;
- отсутствие полноценной операционной системы;
- работа с аппаратными регистрами;
- детерминированное выполнение.
Здесь Python или Java могут просто не соответствовать среде исполнения.
12. Использовать отсечение, а не рейтинг популярности
Неправильный подход:
Составим таблицу из двадцати языков и выставим каждому оценки от одного до десяти.
Проблема в том, что подобная таблица создаёт математический театр: субъективные числа начинают выглядеть как объективный результат.
Полезнее использовать последовательное отсечение.
Шаг 1. Отсечь несовместимые языки
Например:
- нет поддержки целевой платформы;
- нет нужных библиотек;
- неприемлемая модель памяти;
- нет специалистов;
- невозможно пройти сертификацию.
Шаг 2. Определить допустимые кандидаты
Допустим:
- Java;
- Kotlin;
- Go;
- C#.
Шаг 3. Выделить главные различия
Например:
- команда знает Java;
- сервис сильно I/O-bound;
- инфраструктура уже JVM;
- требуется Kafka, PostgreSQL и OpenTelemetry;
- latency не требует low-level runtime;
- нужно выпустить продукт за три месяца.
После этого выбор Java может быть почти очевиден.
Не потому, что Java «лучше Go», а потому что в данной системе стоимость перехода на Go не компенсируется преимуществами Go.
13. Практический алгоритм выбора
Шаг 1. Зафиксировать контекст
- Что строим?
- Greenfield это или существующая система?
- Как долго она будет жить?
- Кто будет её разрабатывать?
- Где она будет работать?
Шаг 2. Зафиксировать функциональные требования
- Какие операции выполняются?
- С какими системами интегрируемся?
- Какие библиотеки и протоколы нужны?
Шаг 3. Зафиксировать нефункциональные требования
- RPS;
- latency;
- p99;
- количество соединений;
- объём памяти;
- безопасность;
- availability;
- время запуска;
- ограничения deployment.
Шаг 4. Определить организационные ограничения
- текущий стек;
- компетенции команды;
- рынок найма;
- сроки;
- бюджет;
- политика компании.
Шаг 5. Сформировать два или три кандидата
Не нужно анализировать все существующие языки.
Шаг 6. Проверить кандидатов прототипом
Нужно реализовать не Hello World, а самую рискованную часть:
- конкурентное обновление;
- работу с Kafka;
- WebSocket-соединения;
- сериализацию;
- CPU-intensive обработку;
- интеграцию с библиотекой;
- запуск в Kubernetes;
- профилирование памяти.
Шаг 7. Провести измерения
- throughput;
- latency;
- память;
- время разработки;
- сложность кода;
- качество tooling;
- удобство диагностики.
Шаг 8. Зафиксировать решение в ADR
ADR должен содержать:
- контекст;
- требования;
- рассмотренные варианты;
- критерии;
- принятое решение;
- причины;
- последствия;
- условия пересмотра.
14. Пример ADR-логики
Допустим, нужно создать backend системы бронирования.
Контекст
- 5 000 запросов в секунду в пике;
- PostgreSQL;
- Kafka;
- Redis;
- сложная бизнес-логика;
- команда из десяти Java-разработчиков;
- Kubernetes;
- срок запуска шесть месяцев.
Кандидаты
- Java;
- Go;
- Rust.
Java
Преимущества:
- команда уже обладает экспертизой;
- зрелая экосистема;
- хорошая поддержка PostgreSQL, Kafka и Redis;
- Spring Security;
- OpenTelemetry;
- высокая скорость разработки.
Недостатки:
- более высокое базовое потребление памяти;
- необходимость настройки JVM;
- возможное влияние garbage collector на latency.
Go
Преимущества:
- простой deployment;
- быстрый запуск;
- удобная модель конкурентности;
- сравнительно небольшой runtime footprint.
Недостатки:
- команда не имеет достаточного опыта;
- часть инфраструктурного кода придётся стандартизировать;
- потребуется новый стек observability и внутренних библиотек;
- переход увеличит срок.
Rust
Преимущества:
- высокая производительность;
- безопасность памяти;
- контроль ресурсов.
Недостатки:
- значительно более высокая стоимость разработки;
- мало специалистов;
- преимущества не соответствуют основной сложности системы;
- основным ограничением, вероятно, станет база данных, а не runtime.
Решение
Java.
Причина
Основной риск системы находится в корректности бизнес-логики, конкурентном бронировании и сроке разработки, а не в CPU или управлении памятью.
Это и есть нормальный выбор языка: не победитель абстрактного турнира, а технология, соответствующая форме задачи.
15. Когда выбирать новый язык оправданно
Новый язык имеет смысл вводить, если выполняется хотя бы одно сильное условие:
- существующий язык принципиально не соответствует платформе;
- нужен другой класс производительности;
- требуется memory safety;
- существующий runtime не выдерживает модель соединений;
- для задачи отсутствует необходимая экосистема;
- новый компонент достаточно изолирован;
- команда сознательно строит новую технологическую компетенцию;
- ожидаемая выгода измерима.
Например:
Java-система использует Rust для отдельного высокопроизводительного парсера недоверенных бинарных данных.
Такое разделение может быть оправдано.
А вот:
Один разработчик любит Rust, поэтому новый сервис будет на Rust
не является архитектурным основанием.
16. Когда не нужно менять язык
Не следует менять язык только потому, что:
- текущий язык кажется скучным;
- другой язык модный;
- появилась новая версия фреймворка;
- кто-то написал впечатляющий benchmark;
- в крупной компании этот язык используется;
- новый язык выглядит архитектурно чище;
- разработчику хочется изучить новую технологию на production-проекте.
Особенно опасны рассказы:
«Компания X использует язык Y, значит и нам нужно».
Компания может использовать язык Y:
- для одного специфического компонента;
- из-за исторических причин;
- потому что у неё тысячи инженеров;
- потому что она разработала собственную инфраструктуру;
- потому что её масштаб на несколько порядков выше.
Копирование языка без копирования условий является подменой контекста вывеской.
17. Полиглотная архитектура
Использование нескольких языков не обязательно плохо.
Оно разумно, когда система действительно состоит из компонентов с разными вычислительными свойствами.
Например:
- Java для бизнес-сервисов;
- Python для ML;
- Rust для производительного агента;
- TypeScript для frontend;
- Kotlin для Android;
- Swift для iOS.
Но полиглотность должна быть управляемой.
Нужно ограничивать:
- количество официально поддерживаемых языков;
- версии runtime;
- набор фреймворков;
- observability-стандарты;
- способы сборки;
- правила безопасности;
- форматы коммуникации между сервисами.
Иначе архитектура превращается в технологический зоопарк, где каждый вольер требует отдельного ветеринара.
18. Краткая карта языков
| Язык | Где особенно силён | Главный компромисс |
|---|---|---|
| Java | Enterprise backend, финтех, сложная бизнес-логика, Kafka-системы | JVM и сравнительно высокий runtime footprint |
| Kotlin | JVM-backend, Android, выразительная доменная модель | Более сложная компиляция, меньше специалистов, чем Java |
| C# | .NET backend, корпоративные системы, Microsoft-экосистема | Привязка к .NET-контексту и его инструментарию |
| Go | Сетевые сервисы, инфраструктура, cloud tooling, простые микросервисы | Более ограниченная выразительность, ручная работа с частью инфраструктурных шаблонов |
| Rust | Системное ПО, performance-critical компоненты, memory safety | Высокая сложность освоения и разработки |
| C++ | Высокопроизводительные движки, real-time, gaming, системное ПО | Большая сложность и риски ошибок памяти |
| Python | ML, automation, data processing, быстрые прототипы | Ограниченная производительность самого runtime и слабее compile-time гарантии |
| TypeScript | Frontend, Node.js backend, full-stack продукты | JavaScript runtime и сложность экосистемы npm |
| JavaScript | Браузер, небольшие Node.js-приложения | Слабее статические гарантии |
| Swift | iOS и macOS | Сильная привязка к экосистеме Apple |
| Dart | Flutter-приложения | Главная ценность связана именно с Flutter |
| Elixir | Высокая конкурентность, WebSocket, отказоустойчивые системы | Более редкий рынок и специфическая модель разработки |
Эта таблица не является рейтингом. Она показывает зоны естественного соответствия.
Главный принцип
Язык нужно выбирать на пересечении четырёх реальностей:
[
LanguageChoice =
SystemRequirements
\cap
PlatformConstraints
\cap
TeamCapabilities
\cap
OperationalEconomics
]
То есть:
- Что должна делать система?
- В каких условиях она должна работать?
- Кто сможет её создать и поддерживать?
- Сколько будет стоить её жизнь, а не только рождение?
Итоговая формулировка для system design:
Я не выбираю язык по популярности или личному предпочтению. Сначала определяю тип нагрузки, требования к latency, throughput, памяти, безопасности и платформе. Затем учитываю существующий стек, компетенции команды, зрелость библиотек, найм и стоимость эксплуатации. После этого формирую несколько допустимых вариантов, проверяю наиболее рискованные предположения прототипом и фиксирую решение в ADR.
Самая важная мысль:
Язык программирования редко определяет архитектуру целиком, но неверно выбранный технологический контур может годами брать с системы налог за каждое изменение.