Типичные ошибки новичков в бэкенд и десктоп-разработке
Бэкенд-разработка
Работа с базами данных
Проблема N+1 запросов. Загрузка коллекции сущностей с последующим ленивым доступом к связям генерирует множество дополнительных SQL-запросов. В ORM (Entity Framework, Hibernate, SQLAlchemy) решение заключается в явной загрузке через Include, JOIN FETCH, selectinload.
Отсутствие индексов. Выборки по колонкам без индексов в WHERE, JOIN, ORDER BY приводят к полному сканированию таблицы. Индексы проектируются под реальные запросы для обеспечения быстрого доступа к данным.
SQL-инъекции. Конкатенация строк уязвима для атак. Параметризованные запросы (@param, ?) обеспечивают безопасность. В ORM при использовании сырых SQL-фрагментов (FromSqlRaw, createNativeQuery) параметры передаются отдельно.
// Уязвимый код
var query = "SELECT * FROM Users WHERE Name = '" + userInput + "'";
// Безопасный код
var query = "SELECT * FROM Users WHERE Name = @name";
var users = connection.Query<User>(query, new { name = userInput });
Игнорирование транзакций. Несколько связанных операций записи выполняются без транзакционной обёртки. Сбой на середине оставляет базу в противоречивом состоянии. Транзакции гарантируют атомарность операций.
Долгие транзакции. Удержание блокировок на время внешних HTTP-вызовов или тяжёлых вычислений исчерпывает пул соединений и вызывает взаимные блокировки (deadlocks). Внешние вызовы выполняются до или после транзакции.
Миграции без контроля. Изменение схемы БД вручную через SQL-скрипты приводит к рассинхронизации схемы и кода. Контролируемые миграции (Flyway, Liquibase, Alembic, EF Migrations) сохраняют историю изменений.
Хранение больших бинарных данных в БД. Файлы и изображения в BLOB-колонках раздувают БД и замедляют резервное копирование. Объектные хранилища (S3, MinIO) предназначены для файлов, в БД сохраняются метаданные и ссылки.
Архитектура и контракты API
Глаголы в URL. Маршруты /getUsers, /createOrder нарушают REST-конвенцию. REST оперирует ресурсами: GET /users, POST /orders.
Отсутствие версионирования. Изменение контракта ломает существующих клиентов. Версия указывается в URL (/v1/users), заголовке или параметре.
Семантика HTTP-статусов. Возврат 200 OK на все случаи с флагом success: false в теле усложняет обработку. Коды ответов являются частью контракта.
| Код | Категория | Назначение |
|---|---|---|
200 OK | Успех | Стандартный ответ на успешный запрос |
201 Created | Успех | Ресурс успешно создан |
400 Bad Request | Ошибка клиента | Синтаксическая ошибка в запросе |
401 Unauthorized | Ошибка клиента | Отсутствует или недействительна аутентификация |
403 Forbidden | Ошибка клиента | Доступ к ресурсу запрещён |
404 Not Found | Ошибка клиента | Ресурс отсутствует |
500 Internal Server Error | Ошибка сервера | Внутренний сбой приложения |
Смешивание бизнес-ошибок и HTTP-ошибок. Ошибка валидации возвращает 500. Клиент отличает внутренние сбои сервера от ошибочных данных пользователя благодаря правильным кодам.
Отсутствие пагинации. Запрос GET /users возвращает миллион записей. Параметры limit/offset или курсорная пагинация ограничивают выборку.
Изменчивые контракты ответа. Разные эндпоинты возвращают поля в разных регистрах и форматах. Единый стандарт именования и форматирования дат обеспечивает предсказуемость.
Аутентификация и авторизация
Самописная криптография. Алгоритмы XOR или Base64 уязвимы. Проверенные алгоритмы включают bcrypt, Argon2, scrypt для паролей и AES-GCM для симметричного шифрования.
Хранение паролей в открытом виде или в MD5. MD5 и SHA-256 без соли взламываются rainbow-таблицами за секунды. Соление и медленные хеш-функции защищают учетные данные.
JWT в роли сессии. JWT отзывается только по истечении срока. Хранение JWT в localStorage уязвимо к XSS. Архитектура с short-lived access и long-lived refresh token в HttpOnly Secure cookie обеспечивает безопасность и возможность отзыва.
Отсутствие RBAC/ABAC. Проверка прав через флаги isAdmin в коде усложняет поддержку. Декларативные политики на уровне middleware централизуют управление доступом.
[Authorize(Roles = "Admin")]
[HttpGet("admin-panel")]
public IActionResult GetAdminData()
{
return Ok("Секретные данные");
}
Передача секретов в URL. Токены в query-параметрах попадают в логи серверов и историю браузера. Токены передаются в заголовках Authorization.
Конкурентность и многопоточность
Состояния гонки (Race conditions). Два потока читают и записывают общий счётчик без синхронизации. Атомарные операции, мьютексы и оптимистичные блокировки в БД (version-колонка) гарантируют целостность.
Взаимные блокировки (Deadlocks). Два потока блокируют ресурсы в разном порядке. Строгий порядок захвата и таймауты предотвращают зависания.
Блокировка общего ресурса. Синхронный вызов внешней API внутри lock останавливает все потоки. Внешние вызовы выполняются за пределами критических секций.
Хаотичное создание потоков. Вызов new Thread() на каждый запрос истощает ресурсы. Пулы потоков и асинхронная модель оптимизируют потребление памяти.
Путаница между параллелизмом и асинхронностью. Асинхронность (I/O-bound) обходится без дополнительных потоков. Параллелизм (CPU-bound) использует несколько ядер для вычислений.
Конфигурация и секреты
Секреты в коде. Пароли БД и API-ключи в репозитории компрометируют систему. Переменные окружения, Vault, AWS Secrets Manager, Azure Key Vault хранят секреты безопасно.
Отсутствие разделения окружений. Единый конфиг для dev, staging и prod приводит к ошибкам. Конфигурация переопределяется по окружению (appsettings.Development.json, .env.development).
Локальные пути в коде. Абсолютные пути C:\Users\dev\uploads работают только на одной машине. Относительные пути и конфигурируемые директории обеспечивают переносимость.
Обработка ошибок и наблюдаемость
Пустые блоки catch. Исключение поглощается без логирования. Логирование с контекстом сохраняет информацию об ошибке.
Исключения в роли управления потоком. Использование throw для обычной логики (UserNotFound) снижает производительность из-за генерации stack trace. Исключения предназначены для исключительных ситуаций.
Логи без структуры. Строки Console.WriteLine("user logged in") усложняют поиск. Structured logging (Serilog, NLog, SLF4J) в JSON-формате с correlation ID позволяет агрегировать логи в ELK или Grafana Loki.
Отсутствие health-check и метрик. Эндпоинт /health, Prometheus-метрики и трейсинг (OpenTelemetry) предоставляют информацию о состоянии приложения.
Кэширование
Кэш без стратегии инвалидации. Устаревшие данные отдаются клиентам. Явная инвалидация при изменении источника (cache-aside, write-through, TTL) поддерживает актуальность.
Кэширование персонализированных данных в общем кэше. Один пользователь получает данные другого. Ключи кэша включают идентификатор субъекта.
Кэширование ошибок. Ответ 500 Internal Server Error сохраняется в кэше и отдаётся пользователям. Ошибочные ответы исключаются из кэширования.
Тестирование
Тесты, зависящие от состояния БД. Порядок запуска влияет на результат. Транзакции с откатом, in-memory БД и тестовые контейнеры (Testcontainers) изолируют тесты.
Тестирование через UI. Вся бизнес-логика проверяется через интеграционные UI-тесты. Пирамида тестов предписывает множество unit-тестов, меньше интеграционных и минимум E2E.
Моки вместо стабов. Подменяется весь внешний мир, проверяются детали реализации. Стабы проверяют поведение системы.
Отсутствие contract-тестов для API. Pact и ContractNet фиксируют контракт между клиентом и сервером.
Архитектурные паттерны
Fat Controller / Fat Service. Контроллер на 500 строк содержит валидацию, бизнес-логику и обращение к БД. Разделение на слои (Controller, Service, Repository) распределяет ответственность.
Прямые зависимости на конкретные реализации. Вызов new SmtpEmailSender() внутри сервиса связывает компоненты. Внедрение через интерфейсы и DI-контейнер ослабляет связность.
Монолит, замаскированный под микросервисы. Несколько сервисов с общей БД образуют распределённый монолит. Микросервисы требуют независимых баз данных.
Преждевременная оптимизация под нагрузку. Kafka и Redis-кластер на проекте с малым трафиком усложняют инфраструктуру. Профилирование и APM выявляют реальные узкие места.
Файлы и ввод-вывод
Открытие файлов без using. Файловые дескрипторы остаются открытыми, приложение падает с ошибкой Too many open files. Конструкции using гарантируют освобождение ресурсов.
Синхронный I/O в асинхронном контексте. File.ReadAllText внутри async-метода блокирует поток. File.ReadAllTextAsync сохраняет асинхронность.
| Метод | Тип выполнения | Влияние на поток |
|---|---|---|
File.ReadAllText | Синхронный | Блокирует поток до завершения чтения |
File.ReadAllTextAsync | Асинхронный | Освобождает поток на время ожидания I/O |
Чтение всего файла в память. Загрузка файла 2 ГБ в byte[] вызывает OutOfMemoryException. Потоковая обработка через Stream читает данные частями.
Десктоп-разработка
Поток интерфейса
Блокировка UI-потока. Тяжёлые вычисления и сетевые запросы в обработчиках кнопок замораживают интерфейс. Task.Run, BackgroundWorker и async/await с возвращением в UI-поток через Dispatcher.Invoke сохраняют отзывчивость.
Обращение к UI-элементам из фонового потока. Доступ к контролам из фонового потока генерирует InvalidOperationException. Маршалинг через Dispatcher.Invoke или Platform.runLater обеспечивает безопасное обновление.
await Task.Run(() =>
{
// Тяжёлые вычисления в фоне
var result = ComputeData();
// Возврат в UI-поток
Application.Current.Dispatcher.Invoke(() =>
{
ResultTextBox.Text = result;
});
});
Отсутствие индикатора прогресса. Долгая операция без обратной связи воспринимается пользователем как зависание. Спиннер или IProgress<T> с процентом выполнения информируют о статусе.
Асинхронность и блокировки
Методы Result и Wait на UI-потоке. Task.Result блокирует поток и в связке с SynchronizationContext приводит к взаимной блокировке. Ключевое слово await предотвращает deadlocks.
Конструкция async void вне обработчиков событий. Исключения из async void лишают разработчика возможности перехвата, они завершают процесс. async Task используется повсеместно, кроме обработчиков событий UI.
Отмена операций. Запущенная задача продолжается после закрытия окна. CancellationToken пробрасывается во все асинхронные вызовы и обрабатывает OperationCanceledException.
Привязка данных и MVVM
Логика в code-behind. Обработчики кнопок в MainWindow.xaml.cs с прямым доступом к контролам нарушают паттерн MVVM. ViewModel, команды (ICommand) и биндинги разделяют логику и представление.
Отсутствие INotifyPropertyChanged. Свойства ViewModel меняются, интерфейс остаётся статичным. Публичные свойства уведомляют об изменении через этот интерфейс.
Прямые ссылки на View из ViewModel. ViewModel содержит знания о конкретных окнах. Диалоги вызываются через IDialogService, навигация через INavigationService.
Утечки памяти через события. Подписка на событие долгоживущего объекта из короткоживущей ViewModel без отписки удерживает её в памяти. Слабые события (WeakEventManager) и отписка в Dispose освобождают ресурсы.
Управление памятью и ресурсами
Удерживаемые IDisposable. Потоки, файлы и HTTP-клиенты остаются в памяти. Конструкция using вызывает Dispose автоматически.
Один HttpClient на запрос. Создание HttpClient на каждый запрос исчерпывает сокеты. IHttpClientFactory или единственный долгоживущий экземпляр управляют пулом соединений.
Утечки через статические коллекции. static List<Window> хранит ссылки на закрытые окна. Статические коллекции выступают корнями для сборщика мусора.
Кэшированные изображения и Bitmap. В WPF и WinForms объекты Bitmap остаются в памяти при замене Image.Source. Явный Dispose освобождает системные ресурсы.
Работа с файловой системой
Запись в Program Files. Запись в директорию установки требует прав администратора. Пользовательские данные сохраняются в %AppData%, ~/.config или Application Support.
Отсутствие проверки путей. Path.Combine(userInput, "file.txt") с ..\.. в userInput открывает доступ за пределы рабочей директории (path traversal). Валидация путей ограничивает доступ.
Жёстко заданные разделители. Символы \ ломают кроссплатформенность. Path.Combine и Path.DirectorySeparatorChar адаптируют пути под ОС.
Гонки при работе с файлами. Чтение файла, который в этот момент пишет другой процесс, вызывает ошибки. Обработка IOException с повторами и файловые блокировки синхронизируют доступ.
Установка и дистрибуция
Отсутствие автообновления. Пользователь использует устаревшую версию с уязвимостями. Squirrel, WinSparkle, ClickOnce, MSIX доставляют обновления автоматически.
Установщик без цифровой подписи. SmartScreen и Gatekeeper блокируют неподписанные установщики. Code signing certificate подтверждает издателя.
Отсутствие корректного удаления. Приложение оставляет следы в реестре и AppData. Деинсталлятор очищает все созданные файлы и записи.
Реестр Windows в роли хранилища настроек. Конфигурация в файлах в %AppData% (JSON, XML) упрощает переносимость и резервное копирование.
Кроссплатформенность
Зависимость от Windows API. P/Invoke на user32.dll и WinAPI-специфичные пути ограничивают запуск одной ОС. Абстракции через RuntimeInformation.IsOSPlatform обеспечивают совместимость.
Шрифты и рендеринг. Шрифт Segoe UI присутствует только в Windows. Fallback-цепочки в стилях подбирают альтернативные системные шрифты.
Различия в DPI. Масштабирование 125%, 150%, 4K-мониторы увеличивают интерфейс. Фиксированные пиксельные размеры элементов обрезают текст. Независимые от DPI единицы (WPF Device-Independent Pixels) адаптируют размеры.
Состояние приложения
Отсутствие сохранения состояния. Пользователь настраивает интерфейс, при следующем запуске настройки сбрасываются. Персистентность сохраняет размеры окон и позиции.
Отсутствие восстановления после сбоя. Приложение падает, пользователь теряет данные. Автосохранение черновиков и journaling восстанавливают контекст при следующем запуске.
Множественный запуск. Пользователь запускает приложение дважды, получает конфликты записи. Mutex с глобальным именем ограничивает запуск единственным экземпляром.
Безопасность десктопа
Хранение токенов в реестре или открытом файле. Защищённые хранилища ОС (Windows DPAPI, macOS Keychain, Linux Secret Service) шифруют учетные данные.
Обфускация в роли защиты. Обфускаторы замедляют реверс-инжиниринг. Критичные секреты хранятся на сервере.
Отсутствие проверки подписи обновлений. MITM-атака подменяет файл обновления. Подпись апдейтов и проверка перед установкой гарантируют подлинность.
Привилегии администратора. Запуск от имени администратора без реальной необходимости нарушает принцип наименьших привилегий. Приложение запрашивает повышение прав только для специфичных действий.
Производительность интерфейса
Тяжёлые списки без виртуализации. ListBox с 10 000 элементов рендерит все элементы сразу и замораживает интерфейс. VirtualizingStackPanel создаёт только видимые элементы.
Частые обновления UI. Обновление прогресс-бара 1000 раз в секунду перегружает поток. Throttling и batch-обновления снижают нагрузку.
Рендеринг в обработчиках событий. Перерисовка сложных Canvas при каждом MouseMove потребляет ресурсы. Дебаунс и off-screen буферы оптимизируют отрисовку.
Утечки GDI. Объекты Graphics, Pen, Brush, Font в GDI+ являются системными хэндлами с лимитом около 10 000 на процесс. Вызов Dispose освобождает хэндлы.
Общие архитектурные просчёты
Логирование PII (персональных данных). Пароли и номера карт попадают в логи. Фильтрация через ILogEventEnricher и маскирование скрывают чувствительные данные.
Отсутствие graceful degradation. Внешний сервис отвечает сбоем, приложение завершает работу. Деградация функциональности и информирование пользователя сохраняют работоспособность базовых функций.
Пропущенные глобальные исключения. События AppDomain.UnhandledException и Dispatcher.UnhandledException остаются без подписки. Подписка на эти события позволяет логировать фатальные сбои.
Отсутствие идемпотентности. Повторный клик по кнопке оплаты создаёт дубликаты транзакций. Идемпотентные операции и токены запросов предотвращают дублирование.
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). Процесс создания и исправления программ. Этапы разработки. Профессиональные практики и культура разработки - стандарты командной работы, ревью и инженерная коммуникация. Отладка - системный процесс поиска и устранения дефектов с использованием инструментов и гипотез. Настройка логирования - уровни, форматы, хранение и маршрутизация логов для разработки и продакшена. В системах CI/CD применяйте скрытые переменные окружения, а не текстовые файлы с данными В данном случае система может автоматически завершить выражение умножения или предложить использование встроенных функций фильтрации списка. Типичные ошибки новичков в веб-разработке. Анализ и оптимизация производительности - профилирование, метрики и устранение узких мест в приложениях. Создание и публикация собственной библиотеки - упаковка, версионирование и распространение через пакетные репозитории. Создание и публикация расширения для VS Code - структура extension-проекта, API, сборка и размещение в Marketplace. Пет-проекты - как планировать этапы, вести backlog и доводить учебный проект до завершенного результата. План развития разработчика - практические проекты, уровни сложности и поэтапное наращивание инженерных навыков.Процесс разработки программного обеспечения
Профессиональные практики и культура разработки
Отладка
Настройка логирования
Безопасность окружения и .env файлы
Использование AI-ассистентов в разработке
Основы веб-разработки и типичные оплошности
Анализ и оптимизация производительности приложений
Создание и публикация собственной библиотеки
Создание и публикация расширения для Visual Studio Code
Пет-проекты
План развития разработчика