Рефакторинг плохого кода 1С
Берём типичный «плохой» код новичка и пошагово превращаем его в аккуратный — то, что просят показать на практической части собеседования.
Рефакторинг — переработка структуры кода без изменения его результата: тот же итог, но быстрее, безопаснее и понятнее. На собеседовании это проверка, умеете ли вы улучшать код осознанно, а не переписывать наугад.
Найти дефекты (прошлый урок) — половина дела. Дальше интервьюер часто говорит: «Хорошо, а теперь перепишите». И смотрит, как вы двигаетесь: по шагам, объясняя каждый, или сносите всё и лепите заново. Хороший рефакторинг — это серия маленьких понятных шагов, где после каждого код всё ещё работает. Именно так его и стоит показывать.
Вопрос-крючок
«Вот процедура заполнения цен в документе. Перепишите её так, как сделали бы в проде, и по ходу объясняйте, что и зачем меняете».
Было: код новичка
&НаКлиенте
Процедура ЗаполнитьЦены(Команда)
Для Каждой Стр Из Объект.Товары Цикл
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ Цена ИЗ РегистрСведений.Цены.СрезПоследних(,Номенклатура = &Н) КАК Ц";
Запрос.УстановитьПараметр("Н", Стр.Номенклатура);
Выборка = Запрос.Выполнить().Выбрать();
Выборка.Следующий();
Стр.Цена = Выборка.Цена;
КонецЦикла;
КонецПроцедурыЗдесь всё сразу: запрос в цикле, работа с базой под &НаКлиенте, и вдобавок строка Стр.Цена = Выборка.Цена упадёт, если цены на номенклатуру нет, — Следующий() вернёт Ложь, а мы всё равно читаем поле. Идём по шагам.
Шаг 1. Выносим запрос из цикла
Главная беда — обращение к базе на каждой строке. Соберём номенклатуру в массив и получим все цены одним запросом. Условие В (&Массив) позволяет отфильтровать срез последних сразу по списку:
// Собираем номенклатуру из табличной части в массив
МассивНоменклатуры = Объект.Товары.ВыгрузитьКолонку("Номенклатура");
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Цены.Номенклатура КАК Номенклатура,
| Цены.Цена КАК Цена
|ИЗ
| РегистрСведений.Цены.СрезПоследних(&Дата, Номенклатура В (&Массив)) КАК Цены";
Запрос.УстановитьПараметр("Дата", Объект.Дата);
Запрос.УстановитьПараметр("Массив", МассивНоменклатуры);Метод ВыгрузитьКолонку() одним вызовом достаёт все значения колонки в массив. Заодно мы заменили ТекущаяДата() на Объект.Дата — цены теперь берутся на дату документа. Один запрос вместо двухсот — это главный прирост.
Шаг 2. Раскладываем результат в соответствие
Чтобы в цикле по строкам быстро находить цену по номенклатуре, сложим результат запроса в Соответствие — структуру «ключ → значение» с мгновенным доступом:
ЦеныПоНоменклатуре = Новый Соответствие;
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
ЦеныПоНоменклатуре.Вставить(Выборка.Номенклатура, Выборка.Цена);
КонецЦикла;
Для Каждой Стр Из Объект.Товары Цикл
Цена = ЦеныПоНоменклатуре.Получить(Стр.Номенклатура);
Стр.Цена = ?(Цена = Неопределено, 0, Цена);
КонецЦикла;Метод Получить() у соответствия вернёт Неопределено, если ключа нет, — и тернарный оператор ?(условие, тогда, иначе) аккуратно подставит ноль. Так мы заодно закрыли ошибку из исходника: строки без цены больше не роняют код, а получают ноль.
Шаг 3. Разделяем клиент и сервер
Вся работа с базой должна уехать на сервер. Обернём её в функцию &НаСервереБезКонтекста — она не тащит форму целиком, принимает массив и возвращает соответствие. Клиентская процедура только собирает данные и раскладывает результат:
&НаСервереБезКонтекста
Функция ПолучитьЦены(МассивНоменклатуры, ДатаДокумента)
ЦеныПоНоменклатуре = Новый Соответствие;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ Цены.Номенклатура КАК Номенклатура, Цены.Цена КАК Цена
|ИЗ РегистрСведений.Цены.СрезПоследних(&Дата, Номенклатура В (&Массив)) КАК Цены";
Запрос.УстановитьПараметр("Дата", ДатаДокумента);
Запрос.УстановитьПараметр("Массив", МассивНоменклатуры);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
ЦеныПоНоменклатуре.Вставить(Выборка.Номенклатура, Выборка.Цена);
КонецЦикла;
Возврат ЦеныПоНоменклатуре;
КонецФункции
&НаКлиенте
Процедура ЗаполнитьЦены(Команда)
Массив = Объект.Товары.ВыгрузитьКолонку("Номенклатура");
Цены = ПолучитьЦены(Массив, Объект.Дата);
Для Каждой Стр Из Объект.Товары Цикл
Цена = Цены.Получить(Стр.Номенклатура);
Стр.Цена = ?(Цена = Неопределено, 0, Цена);
КонецЦикла;
КонецПроцедурыТеперь на сервер уходит один вызов вместо двухсот, и он несёт только массив ссылок, а не всю форму.
Шаг 4. Добавляем обработку ошибок
Запрос к базе может упасть (нет прав, блокировка). Оборачивать серверную работу в Попытка ... Исключение ... КонецПопытки стоит там, где сбой ожидаем, а его нужно показать пользователю понятно, а не «страшным» стеком. Важно: не «глотать» исключение молча — если ловим, то логируем и сообщаем:
&НаСервереБезКонтекста
Функция ПолучитьЦены(МассивНоменклатуры, ДатаДокумента)
ЦеныПоНоменклатуре = Новый Соответствие;
Попытка
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ Цены.Номенклатура КАК Номенклатура, Цены.Цена КАК Цена
|ИЗ РегистрСведений.Цены.СрезПоследних(&Дата, Номенклатура В (&Массив)) КАК Цены";
Запрос.УстановитьПараметр("Дата", ДатаДокумента);
Запрос.УстановитьПараметр("Массив", МассивНоменклатуры);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
ЦеныПоНоменклатуре.Вставить(Выборка.Номенклатура, Выборка.Цена);
КонецЦикла;
Исключение
ЗаписьЖурналаРегистрации("Заполнение цен", УровеньЖурналаРегистрации.Ошибка,
, , ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение "Не удалось получить цены. Обратитесь к администратору.";
КонецПопытки;
Возврат ЦеныПоНоменклатуре;
КонецФункцииЗдесь ИнформацияОбОшибке() отдаёт объект с деталями сбоя, а ПодробноеПредставлениеОшибки() разворачивает его в текст для журнала. Пользователь видит понятное сообщение, а разработчик — полный стек в журнале регистрации.
Стало: итоговый результат
Мы прошли путь из четырёх шагов, и после каждого код оставался рабочим. Итог: один запрос вместо цикла, чистое разделение клиент/сервер, безопасное чтение цены и внятная обработка ошибок. Тот же результат — но такой код не стыдно отдать в прод.
Как отвечать
Проговаривайте рефакторинг вслух по шагам: «Сначала выношу запрос из цикла — это даёт главный прирост. Потом раскладываю в соответствие для быстрого доступа. Дальше разделяю клиент и сервер. И в конце добавляю обработку ошибок». Двигаться маленькими шагами, каждый из которых улучшает код и сохраняет работоспособность, — ровно то, что отличает инженера от переписывателя.
Как это работает: почему шаги, а не «всё сразу»
Большой рефакторинг «одним куском» опасен: легко сломать поведение и не заметить. Маленькие шаги дают контрольные точки — после каждого можно перепроверить результат. На собеседовании это ещё и способ показать мышление: интервьюер видит не только финал, но и как вы к нему пришли. Порядок шагов тоже не случаен — сначала то, что даёт наибольший эффект (запрос из цикла), потом архитектура, и лишь затем страховка от ошибок.
Частые ошибки на собеседовании
- Сносят всё и пишут заново. Теряется связь с исходником, легко потерять важную деталь. Рефакторьте шагами.
- «Глотают» исключение. Пишут
Попытка ... Исключение КонецПопыткис пустым блоком — ошибка исчезает молча, и данные тихо портятся. Если ловите — логируйте и сообщайте. - Оборачивают в Попытку вообще всё. Обработка ошибок не бесплатна и маскирует баги, если натыкана везде. Ставьте её там, где сбой ожидаем и его надо показать пользователю.
- Забывают про дату документа. Выносят запрос из цикла, но оставляют
ТекущаяДата()— производительность починили, корректность нет. - Перекладывают весь цикл на сервер вместе с формой. Используют
&НаСерверевместо&НаСервереБезКонтекстаи тащат на сервер всю форму без нужды.
Итоги-шпаргалка
- Рефакторинг — улучшение кода без изменения результата; показывайте его маленькими шагами.
- Порядок: 1) запрос из цикла → 2) результат в
Соответствие→ 3) клиент/сервер через&НаСервереБезКонтекста→ 4) обработка ошибок. ВыгрузитьКолонку()собирает ключи в массив;Соответствиедаёт быстрый доступ «ключ → значение».Получить()возвращаетНеопределенодля отсутствующего ключа — обрабатывайте это, чтобы код не падал.- Не «глотайте» исключения: логируйте через
ЗаписьЖурналаРегистрациии показывайте пользователю понятный текст.