Это не учения, боец! Добро пожаловать в реальный мир!
Полигон DISc0nNecT'a

Авторизация

Книга про инвестиции

Карта посещений

Другие ссылки

Поиск по сайту

Рефакторинг на примере среды программирования 1С

Рефакторинг на примере среды программирования 1С

Рефакторинг – переработка старого кода или написание нового кода так, чтобы он был более приспособлен к сопровождению и изменениям

Основные причины роста сложности (трудоемкости) модификации программ по мере добавления в них новой функциональности «традиционным» методом:

  • Программы трудно читаемые (логика кода не понятна с первого взгляда).
  • Программы, в которых есть дублирование (copy paste). Как следствие, высока вероятность изменить только одну часть дублирующегося кода, что приводит к рассогласованию логики.
  • Программы, в которых блоки кода не являются изолированными (самодостаточными) – т.е. при добавлении новой функциональности приходится исправлять много старого кода (работающего).
  • Программы, которые реализуют сложную логику условных операторов, которая не читается с первого взгляда.

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

Код должен быть структурирован так, чтобы:

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

Мартин Фаулер: «Написать код, понятный компьютеру может каждый. Хороший программист пишет код понятный людям».

Положения рефакторинга:

1. Тексты функций и процедур не должны быть очень длинными – 10-15 строк максимум. Исключение может составлять текст функции с комплексным длинным запросом. В этом случае рекомендуется выделять отдельную функцию, отвечающую за формирование текста запроса или за формирование результата запроса. Это возможно одно из самых главных правил написания сопровождаемого кода.

2. Полезно выделять большие циклы (когда внутри цикла много команд) в отдельные процедуры, название которых разъясняет принцип того, что делается в цикле (по аналогии с заменой комментария).

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

4. Имена функций и процедур, переменных должны быть написаны языком бизнес-логики, а не техническим языком (звучащие имена методов). Они не должны быть обезличены. Например, вместо функции ПолучитьТекстЗапроса лучше написать ПолучитьТекстЗапросаДляРасчетаУровняЗапасов.

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

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

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

8. Много экспортных методов в одном общем модуле или в одной обработке – это плохо. Очевидно, что общий модуль или обработка пытаются охватить слишком широкую тематику. В этом случае лучше произвести смысловую разбивку и иметь два модуля или две обработки (или даже еще больше).

9. Каждый объект должен работать только со своими значениями (модифицировать) – принцип инкапсуляции. Если в объекте есть функции, которые изменяют данные других объектов, лучше поместить подобные функции в модули изменяемых объектов или выделить их в отдельные обработки или общие модули (в зависимости от сущности указанных изменений). Например, в форме справочника Контрагентов происходит редактирование Контактных данных. При записи справочника Контрагентов они сохраняются. Функция перезаписи контактных данных не должна находиться в модуле справочника Контрагенты (и тем более не в форме справочника). Она должна только оттуда вызываться. Где она будет находиться зависит от логики решения (в отдельной обработке/общем модуле или в модуле регистра сведений контактные данные).

10. Процедуры получения данных и обработки данных должны находиться в модулях объектов. В форме объектов должны быть только интерфейсные процедуры – взаимодействие с пользователем, изменение элементов интерфейса – без получения данных и их обработки.

11. Расширение предыдущего правила – при написании отчета, функция, которая получает табличный документ (выход отчета) должна находиться в модуле отчета – так, чтобы всегда можно было вызвать эту функцию не только из самого отчета.

12. Для косвенной передачи данных из формы в модуль использовать ДополнительныеСвойства формы – не заводить никаких внешних глобальных переменных.

13. В случае если необходимо получить выборку простого списка данных (из одного, в крайнем случае – двух объектов данных) и логика подобной выборки включает более 1-2 строк, необходимо выделять такие выборки в отдельные функции. Указанные функции помещать в модули менеджеров объектов. Пример: Справочники.Контрагенты.ПолучитьПоставщиковМенеджера(Менеджер) – функция выбирает из справочника контрагентов поставщиков, отфильтрованных по менеджеру.

14. Принцип сокрытия полей (инкапсуляция): Не использовать прямое обращение к полям объекта, при вызове его из другого объекта.

Вместо
ОтчетОтгрузки.Контрагент = А;
ОтчетОтгрузки.Склад = Б;
ТД = ОтчетОтгрузки.ПолучитьТабличныйДокумент();

Использовать

ТД = ОтчетОтгрузки.ПолучитьОтгрузкиКонтрагентаСоСклада(А,Б);

15. Необходимо избавляться от временных переменных. Использование временных переменных усложняется тем, что необходимо контролировать весь текст функции, чтобы понять, где происходит их модификация. Вместо использования временных переменных стоит напрямую обращаться к функции, которая возвращает значение, помещаемое во временную переменную, в том месте, где происходит использование временной переменной. Пример ненужных временных переменных:
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать(); // больше результат нигде не используется

Лучше использовать:
Выборка = Запрос.Выполнить().Выбрать();

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

17. Внутри функции не запоминать результат во временной переменной, а сразу же возвращать в точке, где он вычисляется. Если логика более сложна, требует модификации результата в теле функции, стоит подумать над возможностью упрощения текста функции, чтобы не использовать временную переменную. В крайнем случае, имя переменной должно быть Результат. Это имя необходимо использовать ТОЛЬКО для возвращаемого значения функции, для других целей использовать модификации имени (например, РезультатЗапроса).

18. Не использовать в запросах вложенные запросы – с одной стороны это нарушает читабельность запроса и его сопровождаемость, с другой стороны – ухудшает производительность выборки. Вместо вложенных запросов нужно использовать временные таблицы с пакетом запросов.

19. Дублирование кода – это плохо. Следует избегать дублирования кода даже в рамках одной функции (например, 2 одинаковых цикла подряд с небольшими отличиями друг от друга). Выделять повторяющийся код в отдельную небольшую функцию.

20. Длинные списки параметров, передаваемых процедуре или функции (от 5-6 и более) заменять на Структуру значений. Но Аккуратно! Баланс нужно чувствовать. Можно сказать только одно – подобное чувство приходит с опытом.

21. Вместо наборов строк, как идентификаторов выбора, необходимо использовать перечисления, добавляемые в структуру метаданных.

Т.е. вместо
Если ВариантВызова = «КонтрольЗаказов» Тогда
...
ИначеЕсли ВариантВызова = «КонтрольОтгрузок» Тогда
...
КонецЕсли;

Нужно завести перечисление Перечисление.ВариантыКонтроляЗадолженности с вышеуказанными значениями.

Подобный подход позволит еще на этапе программирования (а не на этапе работы пользователя) выявлять ошибки, связанные с возможными опечатками во время программирования. Дополнительно у программиста появляется четкое представление о конечности возможных вариантов.

22. Необходимо избегать цепочек вызова типа А.Получить().Получить().Выполнить().Разобрать(). Если объект А – не типовой объект, а объект, который может быть модифицирован (документ, обработка, общий модуль), то необходимо в модуль объекта А добавить новую функцию «ВыполнитьРазбор()», в которой прописать требуемую логику, а вызывать уже А.ВыполнитьРазбор(). Исключением из этого правила являются те случаи, когда мы не можем модифицировать объект. Например: Запрос.Выполнить().Выбрать(). Такая запись в этом случае предпочтительнее, чем использование временной переменной

23. Не использовать многовариантных условных операторов – несколько выражений Иначе в поле выбора. Для того чтобы избавиться от нескольких условий, можно использовать следующие подходы:

  • Если внутри условного блока выполняется несколько операторов, выделить их в отдельную функцию.
  • Заменить один условный блок на несколько проверок с возвращаемым результатом. Например:

Если ... Тогда

Иначе ...

Иначе ...

КонецЕсли;

Заменить на:
Если ... Тогда ... Возврат ... КонецЕсли;

Если ... Тогда ... Возврат ... КонецЕсли;

Возврат ...

Последний возврат должен содержать результат основной логики. Остальные – условную логику функции.

24. Вложенность условий необходимо по возможности заменять на одно условие (И/ИЛИ/НЕ). Зачастую в подобной замене помогает инверсия условных выражений (иногда потребуется выделение функций).

Например в схеме:
Если Условие1 Тогда
Оператор1;
Если Условие2 Тогда
Оператор2;
КонецЕсли;
Иначе
Оператор3;
КонецЕсли;

Обработку можно заменить следующим образом:
Если Не Условие1 Тогда
Оператор 3;
Возврат
КонецЕсли;

Оператор1;

Если Условие2 Тогда
Оператор 2
КонецЕсли;

25. Сложные условия, в составе которых идет несколько проверок для повышения читаемости следует выделять в отдельные функции, возвращающие булево значение. Название функции в этом случае позволит более правильно определить суть проверяемого условия и повысить читабельность. Как правило дополнительный плюс этого подхода – то, что подобную функцию получается возможно использовать и в других процедурах.

26. Для простых условий следует более часто использовать оператор выбора ?(,,).

27. Вместо блока Если Тогда в результате функции, возвращающей булево значение, использовать условное выражение. Например: Возврат (А=5);

28. Не использовать «магические числа» – цифровые значения в конкретных проверках. Для замены магических чисел следует использовать функции, возвращающие число (предпочтительно общих модулей) или, в крайнем случае, – глобальные переменные.

Например, если в рамках задачи идет проверка уровня задолженности 50%, то не стоит использовать прямое указание цифры:
Если ПроцентЗадолженности = 50 Тогда...

Заменить на:
Если ПроцентЗадолженности = РазрешенныйУровеньОплатыПоДоговоруСДистрибьютором() Тогда

А саму функцию описать как
Функция РазрешенныйУровеньОплатыПоДоговоруСДистрибьютором() Экспорт

Возврат 50

КонецФункции

В будущем это значение всегда можно сделать параметризируемым и изменений вносить придется значительно меньше.

Если «магическое число» используется в запросе, то лучше передавать его как параметр запроса, а в момент передачи параметра вызывать соответствующую функцию.

29. Неявные моменты, необрабатываемые функцией, не заменять на возврат неопределенного значения, возврат текстового описания ошибки, или возврат кода ошибки. Необходимо использовать вызов исключения, с текстом, описывающим суть проблемы, со ссылкой на место проблемы. Такой подход позволит на этапе тестирования лучше выявлять проблемы, а на этапе сопровождения ошибка будет возникать именно в том месте, где есть проблема, а не в вышестоящих операторах, пытающихся обработать неопределенное значение. Дополнительно текст исключения позволит лучше понять, что происходит. Например, в функции ОбработатьЗаполнение, если в качестве основания заполнения передан неверный тип документа, можно вызывать исключение, сообщая что данный тип не поддерживается при вводе на основании.

30. Постараться не использовать функции, которые одновременно возвращают значение и в теле функции изменяют что-то. Для изменения данных лучше использовать процедуры. Функции должны либо проверять, либо возвращать рассчитанные значения. Если нужно в теле функции вернуть несколько значений, стоит использовать СтруктуруЗначений для возврата.


© Василий Коновалов

Яндекс.Метрика