Отдел разработки – Telegram
Отдел разработки
69 subscribers
4 photos
3 links
Заметки о разработке от команды онлайн школы программирования IRON PROGRAMMER
Download Telegram
Channel photo updated
Console.WriteLine("Привет, МИР!");

На связи команда отдела разработки лучшей онлайн школы программирования IRON PROGRAMMER!
Примерно год назад мы начали работу над своим внутренним аналитическим порталом для управления нашими курсами и пришли к выводу, что нам есть, чем поделиться с сообществом. За минувший год мы написали много, очень много строк кода, реализовали разные задачи, использовали множество технологий и продолжаем развиваться и совершенствоваться.
А здесь мы будем рассказывать обо всем интересном и необычном, с чем столкнулись в своей работе. Наверняка что-то из рассказанного нами пригодится кому-то или просто будет интересно посмотреть, как бывает.

Присоединяйтесь!
🔥1🤮1🤡1🤣1
Что мы делаем?

Если кто не знает, IRON PROGRAMMER - онлайн школа программирования. Основной язык программирования, который мы преподаем - C#. Но кроме него в школе есть курсы по Go, Kotlin и... впрочем, когда будут другие языки, мы обязательно об этом расскажем.
Наши курсы на текущий момент размещаются на Степике. Курсов много, курсы разные, но все вместе они дают людям знания, достаточные для того, чтобы стать .Net разработчиком и устроиться на работу. Обо всём, что происходит в школе, а так же отзывы, активности, интервью с учениками и многое-многое другое можно почитать в канале, добавляйтесь, если ещё не.

Расскажем немного непосредственно об отделе разработки. Мы все, а нас сейчас трое, закончили курсы Иосифа (Иосиф Дзеранов - автор и основатель школы). Наша текущая задача заключается в создании и совершенствовании внутреннего портала для аналитики и управления курсами. Что это означает? Курсов в школе много. Их все нужно поддерживать, обновлять, улучшать, отвечать на комментарии и в целом приводить к единому высокому стандарту качества, то, за что многие тысячи наших учеников ценят и любят, с удовольствием проходят наши курсы. До определенного момента можно было выполнять все операции вручную, однако, с течением времени, курсов стало много и появилась необходимость автоматизировать процессы. Собственно, то, чем занимаются программисты в целом - автоматизируют процессы путём написания программных продуктов.
Мы уже разработали и внедрили 10 стандартов для наших курсов и это далеко не предел. О том, что это за стандарты и для чего они, а самое главное, какие технические решения применяли, когда их реализовывали, мы расскажем позже.

Пока же вернёмся к началу. Раз уж мы все тут .Net разработчики и, более того, бекендеры, а особой необходимости создавать мега приложение со сложным интерфейсом не было, начали с относительно простого, и в то же время очевидного решения - MVC приложения. Фронтэндеров у нас нет, а одна мысль о том, что придётся углубляться в дебри UX/UI разработки вызывала неприятные ощущения. Поэтому первые итерации были полностью на MVC с применением минимального количества js-кода. Но, с течением времени скриптов становилось больше, а интерфейсы (под стать задачам) сложнее. И вот уже на многих страницах фронтом заведует vue.js . Неожиданно? Может быть, но для текущих задач его возможностей в связке с ASP приложением более чем достаточно. Кстати, о том, как это всё вместе работает расскажем обязательно в следующих постах.

Итого. Мы пишем приложения на C# и внутри управляем курсами, собираем статистику, проверяем, всё ли хорошо на курсах и даже внедрили свои собственные анкеты (они же формы обратной связи) в наши курсы на Степике. В каждом модуле каждого курса вы можете найти форму обратной связи. Это целиком наша разработка, которая пришла на смену не чему нибудь, а самим гугл-формам! Чем мы, по-честному, гордимся!
👍1🤮1🤡1🤣1
О - #оптимизация

Этой теме посвятим еще много постов, потому что тема важна, нужная, актуальная и иногда больная ))
Предыстория:
Когда пишется проект с нуля и нужно сделать что-то уже вчера, порой реализуются самые простые и неоптимальные решения, которые просто должны выполнять свою функцию. Со временем, проект разрастается и такие вот "что-то" начинают вылезать и мешать.
Проблема:
Был такой метод:
public async Task<List<long>> GetMissingIdsAsync(bool useStartDate)
{
await using var context = await _contextFactory.CreateDbContextAsync();

var missingAttemptIds = new List<long?>();

if (useStartDate)
{
var existingIds = await context.Attempts
.Where(at => at.Time >= _startDate)
.Select(at => at.Id)
.ToHashSetAsync();

var fromSubmissions = await context.Submissions
.Where(s => s.Time >= _startDate)
.Select(s => s.Attempt)
.ToListAsync();
missingAttemptIds.AddRange(fromSubmissions
.Where(id => id.HasValue && !existingIds.Contains((long)id))
.ToList());
}
else
{
var existingIds = await context.Attempts
.Select(at => at.Id)
.ToHashSetAsync();

var fromSubmissions = await context.Submissions
.Select(s => s.Attempt)
.ToListAsync();
missingAttemptIds.AddRange(fromSubmissions
.Where(id => id.HasValue && !existingIds.Contains((long)id))
.ToList());
}

return missingAttemptIds.Select(x => (long)x!).ToList();
}

Пара слов о структуре моделей. Эти модельки приходят из Степика - те самые ваши решения задач - Submissions и попытки Attempts. У каждого решения может быть одна попытка, но несколько решений могут быть для одной попытки. Объяснить это сложно, но так задумано: при отправке нового решения перезаписывается только попытка - она получается одна для одной задачи.
Продолжение...
🔥1
Начало...
Так вот, нам для актуализации данных на своей стороне нужно загружать и попытки и решения. Сначала мы загружаем решения, потом берем список Attempt и загружаем те, которых у нас еще нет.
Метод выше, работал без малого год и проблем не было, потому что работало.
Работало, пока мы не поймали OutOfMemoryException )).
Из-за того, что выгружались списки, состоящие из нескольких миллионов элементов, объем занимаемой этими списками памяти стал критичным и в конечном итоге закончился.
Логирование помогло локализовать проблему достаточно быстро. Но, что не так и что делать?
В изначальном методе мы извлекаем сначала огромное количество значений в один список, потом еще большее число значений во второй список и затем формируем третий. Ужас-ужас. Особенно, если учесть, что речь идет о списках по несколько миллионов значений в каждом. В конечном итоге, размер потребляемой памяти мог запросто занимать ~200 мб.
Решение:
Решение на самом деле простое - перенести вычисления на сторону БД. Там все операции выполнятся быстрее и не займут столько места, плюс нет ограничений CLR на размер памяти, тем более, что ее там столько не потребуется.
    public async Task<List<long>> GetMissingIdsAsync(bool useStartDate)
{
await using var context = await _contextFactory.CreateDbContextAsync();

try
{
var missingAttempts = context.Submissions!
.Where(s => !useStartDate || s.Time >= _startDate)
.Where(s => s.Attempt != null)
.Where(s => !context.Attempts!
.Any(a => a.Id == s.Attempt))
.Select(s => (long)s.Attempt!)
.Distinct();

return await missingAttempts.ToListAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Произошла ошибка при получении id отсутствующих попыток!");
return [];
}
}

Здесь мы формируем список id прямо в БД и получаем уникальные значения без вычислений на стороне приложения. Более того, код стал понятнее и лаконичнее.
*заодно исправили ещё момент с некорректной выборкой элементов, из-за которых каждый раз получали "лишние" данные для загрузки.

Итого: сортировка и выборка данных на стороне БД работает в большинстве случаев быстрее и занимает меньше ресурсов + не отражается на производительности самого приложения. Точнее, не ухудшает его.
🔥2
Проблемы с миграциями или как все вернуть, когда казалось бы ничего не получается.

Как сказал один умный человек:
опыт — сын ошибок трудных.


Бывает такое, что в процессе работы приходится по несколько раз менять одну и ту же модель, выполнять миграции, потом снова менять и так далее по кругу. А потом, в какой-то момент, EF уже не справляется и создать миграцию не может, не видит изменений в моделях, а в БД этих изменений и нет. Начинаются проблемы, ошибки и поиск решения.
Но на самом деле всё не так уж страшно и сложно.
Первое, что нужно понимать - если в БД нет изменений (таблицы, колонки) и нет миграции в _EFMigrationsHistory тогда нужно выполнить ряд манипуляций руками и все будет работать как должно!
Смоделируем ситуацию, когда в таблице Users (EF если не указать имя таблицы руками называет таблицы в PascalCase) не появился столбец, например Courses а таблица UserCourses была переименована из StudentCourses, но в бд этих изменений так же нет.

Итак, по порядку:
1️⃣. Все наши проблемы скрываются в файле DbContextModelSnapshot.cs (имя контекста может отличаться, но ModelSnaphot будет все равно)
2️⃣. Или ищем руками названия столбца и таблицы и удаляем те изменения, которые должны были попасть в миграцию, но не попали,
3️⃣. Либо сравниваем текущую ветку с той, в которой точно не было этих потерянных изменений и там, в сравнении, находим нужные строки и удаляем.
4️⃣. Рекомендуем начинать удалять снизу вверх - тогда номера строк в сравнении и в файле будет совпадать и искать будет проще.
5️⃣. После того, как зачистили файл, убедились, что в истории миграций в БД нет лишних миграций и у себя в проекте нет лишних миграций, создаем новую.
6️⃣. Вот в общем-то и всё. Миграция создается успешно со всеми нужными изменениями. ( если нет — возвращаемся к шагу 1 и повторяем все заново, значит где-то что-то осталось не удаленным)
На всякий случай, хотя нет - всегда (!) проверяйте, что в файле с созданной миграцией написано: все ли изменения там присутствуют. И если чего-то не хватает, какого-то изменения или, например, индекса, всегда можно дописать нужное руками.

Запускаемся => смотрим, что все работает => больше не боимся поломок миграций!

С заботой, команда отдела разработки!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Как не надо делать

В процессе разработки периодически приходится работать с публичными api разных сервисов. И периодически сталкиваемся с плохой, а иногда и откровенно ужасной документации api.
Пока "лидирует" по числу недоуменных взглядов и разочарованных вздохов документация к публичному api Stepik.
В чём вообще суть вопроса? А вот пример:
{
"apiVersion": "",
"swaggerVersion": "1.2",
"basePath": "https://stepik.org",
"resourcePath": "/api/step-snapshots",
"apis": [
{
"denoscription": "StepSnapshot resource",
"path": "/api/step-snapshots",
"operations": [
{
"method": "GET",
"summary": "StepSnapshot resource",
"nickname": "Step_Snapshot_list",
"notes": "StepSnapshot resource.",
"type": "object",
"parameters": [
{
"paramType": "query",
"name": "step",
"denoscription": "id of the step to show snapshots for",
"type": "string",
"format": "string",
"required": false,
"defaultValue": null
}
]
}
]
}
],
"models": {}
}

Что тут можно понять?
1. Нет версии api. Работать — работает, но версия должна быть
2. Только один параметр запроса? Серьёзно? Мы точно знаем, что их как минимум 2 (как минимум page)
3. Модель. Какая модель-то на выходе?

Это прям основное, то, что бросается в глаза. И на самом деле это (плохая документация) может быть проблемой, особенно когда api платное, а для того, чтобы разобраться, приходится экспериментировать за свой счёт.

Поэтому, рекомендация простая: если пишите публичный api, поддерживайте документацию в актуальном состоянии. Задайте себе вопрос: а не пропустил ли я чего-нибудь? Могу ли я понять из документации, что получу и при каком запросе?
👍1
2025 год для отдела разработки

Это был очень интересный и не менее насыщенный год!
Мы начали свой путь в самом конце 2024 года и примерно в эти же, предновогодние дни впервые зарелизили наш внутренний портал.
С тех пор кодовая база росла, штат увеличивался, а количество применяемых технологий и качество кода в целом сделали ощутимый рывок вперед!

Подводя итоги, захотелось как-то формализовать успехи и достижения, чтобы их можно было (внутри команды) посмотреть в читаемом виде. Так появилось небольшое приложение, считающее метрики из репозитория. В предновогодней суете еще не оформили его в полноценное открытое решение, но обязательно сделаем это в самом начале следующего года. Оно поможет собрать статистику с любого репозитория, к которому у пользователя есть доступ и посмотреть, что сделано за нужный период: количество коммитов и строк кода, наиболее активные дни недели и время суток и т.д.

К посту прикрепляем некоторые детали из получившегося отчета, можно оценить нашу продуктивность (и режим дня )))

А еще мы в уходящем году множество раз обновляли библиотеку для работы со Stepik api, написали свое приложение с формами обратной связи (которое как минимум видели все ученики, а многие и воспользовались, заполнив анкету/ты) и готовим еще много всего важного, полезного и интересного как для команды, так и, в большей степени, для наших дорогих учеников!
И, конечно, будем делиться своими наработками и наблюдениями здесь в канале. Обмен опытом всегда интересен, и прикладную информацию по изучаемому языку читать, на наш взгляд, вдвойне интересней.

С наступающим Новым годом!
❤‍🔥3