Предисловие
Скажу сразу. Всех авторов Инфостарта я люблю и уважаю. Написать статью - это большой труд. И я обычно "плюсую" прочитанные мною статьи, даже если не согласен с автором. Я решил написать этот материал не для того, чтобы кого-то уязвить. Я забочусь о наших коллегах, только начинающих свою карьеру. Прочитав очередной труд и увидев много звездочек, они могут подумать, что мысли автора это что-то записанное на изумрудных скрижалях, не меньше. Автор, рассказывающий о том, что программы следует писать слева направо и сверху вниз, кажется им настоящим гуру. Они и не догадываются о том, что статьи подобного рода чаше всего написаны почти такими же начинающими, как и они. Потому что человека с опытом вряд ли может удивить и, тем более, возмутить что-либо в чужом коде.
Я как-то не задумывался об этом, пока не попробовал пошутить с одним из авторов, у которого "накипело". Я выразился примерно в следующем смысле:
"Вот вы обличаете нехорошие практики, а какая у вас самого практика в нехороших практиках? Сами-то этим занимались когда-нибудь?"
Вы не представляете - как он обиделся! И я подозреваю, не потому, что у него нет чувства юмора.
Или вот совсем свежий пример от другого автора. Он рассказывает нам, как некие нехорошие люди пишут программы тяп-ляп и абы как. Его благородная ярость все вскипает и вскипает. И наконец достигает чуть ли не высшей своей точки, когда он сообщает нам, что вот, дескать, горе-разработчики знать ничего не знают про ФИФО, просто списывают самую старую партию. И в качестве подтверждения своих слов приводит текст запроса... Вы писали когда-нибудь запрос для ФИФО? Если да, то вы будете смеяться вместе со мной. Это был тот самый запрос. Я этому автору говорю: "вы нам код после запроса покажите, там должно быть самое интересное". Думал, он сам посмотрит и скажет: извините, погорячился... Нет. Взял и поправил свою публикацию. Выложил код. Правда обрезал его посередине. Так и оставил в непонятках. То ли совсем ничего про ФИФО не знает, то ли стесняется.
Не будем, впрочем, затягивать вступление и перейдем к антидоту.
Антидот
Для надежности я решил сделать антидот двухкомпонентным. Я возьму два простых утверждения, которые кажутся очевидными. И продемонстрирую, что получится, если подумать над ними, как следует.
Часть 1. Имена переменных должны быть осмысленными и понятными.
В этом месте я всегда предлагаю подумать на тему, что лучше:
СуммаПервогоИВторого=Первое*Второе;
или
х=Первое*Второе;
Тут обычно начинают задавать вопрос по поводу первого варианта: что это вообще такое и зачем я так написал. Зачем, зачем... Пишу я так. На тысячу строк кода два-три раза нечто подобное и напишу. Но это потому, что я опытный. У других может и все 20 или 30 раз получиться. Мы все делаем ошибки. Нет ничего удивительного в том, чтобы написать "*" вместо "+". Лично я не перестаю удивляться другому. А именно тому, что можно часами сидеть, смотреть на первый вариант и не видеть проблемы. Здесь может спасти только "взгляд со стороны", когда ваш коллега сдвигает вас с мертвой точки. Но вся проблема в том, что вы можете зависнуть не на:
СуммаПервогоИВторого=Первое*Второе;
а на чем-то другом, типа:
Периметр=СуммаПервогоИВторого+СуммаПервогоИВторого;
И тут вам коллега уже ничем не поможет.
Мне как-то привели вот такой пример:
Если ((ПН И ПНП) И (РН И РНП)) ИЛИ
(( ПН = Ложь ) И (РН И РНП)) ИЛИ
((ПН И ПНП) И (РН = Ложь)) Тогда
Док.Обработан = Истина;
Док.ДанныеОбработан = ТекущаяДата();
Док.Записать(РежимЗаписиДокумента.Проведение);
КонецЕсли;
И спросили: "Ну и как? Это хороший код по-твоему?"
Нет. Это - плохой код. Вместо трех строчек условий следует оставить одну с одним "ИЛИ".
(* Внимательные читатели заметили, что здесь я был не прав. Это выражение на самом деле не сокращается)
А насчет имен переменных... Ну, допустим, они будут понятными. Что вам это даст? Если человек путается в условиях, то где гарантия, что он ничего не напутал ранее с установкой значений?
Но самый главный аргумент против моих рассуждений следующий:
"Зачем спецом портить программу? Ведь тогда все станет непонятным."
И вот здесь надо разобраться как следует. Во-первых, непонятным станет не все. У нас с вами есть имена справочников, документов, регистров. Есть имена их реквизитов. А также имена модулей и методов. Переменные - это далеко не все, что есть в нашем коде.
Взгляните на картинку, которую я взял отсюда: https://ru.wikipedia.org/w/index.php?curid=6775969
Представьте себе, что точки - это имена предметных сущностей, а линии - это переменные.
Теперь посмотрите на вторую картинку:
Некоторые полагают, что сам смысл работы программиста заключается в переходе от первого ко второму. Дескать, первое вам кто угодно сделает, а для второго уже нужна квалификация. Для этого есть даже специальный термин: decoupling. Его невозможно хорошо перевести на русский без мата, поэтому я его оставлю, как есть. Если вы присвоили переменной некоторое значение, а потом используете ее через 1000 строк вашего кода - это плохо. Хуже этого могут быть только глобальные переменные. Как говорится: самый лучший префикс для глобальной переменной это //. В идеале, вы объявляете переменную. Используете ее в следующей строке, через строку, через две-три. Но не далее чем вы можете "схватить" за один свой "рабочий такт". После чего, вы можете забыть о ней навсегда. Единственное, о чем вам стоит позаботиться, так это о том, чтобы хорошо различать переменные, если их будет несколько. Идеальный код должен обладать таким свойством, что если взять и заменить в нем все имена переменных на случайно подобранные, то он нисколько не потеряет в читабельности.
Что из всего этого следует. Имена переменных должны быть хорошо различимы в своем локальном контексте. Будут они осмысленными или нет - дело вкуса. Более того, начинающим было бы разумно рекомендовать использовать только абстрактные имена. Это будет естественным образом подталкивать их к созданию более совершенного кода.
Часть 2. Копирование кусков кода - зло.
если зеленый тогда
///////////////////////////////////////////////
новкоординаты=новыекоординаты(текузелрез);
нсрез=текузелрез.строки.добавить();
нсрез.ид=Новый УникальныйИдентификатор;
нсрез.тип="ГРУППА";
нсрез.имя=текузел.имя;
нсрез.РПШирина=19;
нсрез.РПВысота=12;
нсрез.РПВерх=новкоординаты.верх;
нсрез.РПЛево=новкоординаты.лево;
нкорсв=своуз.строки.добавить();
нкорсв.узел=нсрез.ид;
стузелрез=текузелрез;
текузелрез=нсрез;
//////////////////////////////////////////////
связь="";
подключение="";
для каждого св из мсв цикл
если врег(сокрлп(св.свойство))="СКРЫТЬ" тогда
нсрез.тип="СКРЫТАЯГРУППА";
иначеесли врег(сокрлп(св.свойство))="УСЛОВИЕ" тогда
продолжить;
иначеесли врег(сокрлп(св.свойство))="ИСКЛЮЧИТЕЛЬНОЕ_УСЛОВИЕ" тогда
продолжить;
иначеесли врег(сокрлп(св.значение))="ЗАПИСАТЬ" тогда
ВыполнитьКоманду(св.свойство,врег(сокрлп(св.значение)));
иначеесли врег(сокрлп(св.значение))="УДАЛИТЬ" тогда
ВыполнитьКоманду(св.свойство,врег(сокрлп(св.значение)));
иначеесли врег(лев(сокрлп(св.значение),6))="НОВЫЙ " тогда
метаимя=врег(сокрлп(сред(сокрлп(св.значение),6)));
если лев(метаимя,8)="ДОКУМЕНТ" тогда
новзн=документы[сокрлп(сред(метаимя,10))].создатьдокумент();
новзн.дата=текущаядата();
иначеесли лев(метаимя,10)="СПРАВОЧНИК" тогда
новзн=справочники[сокрлп(сред(метаимя,12))].создатьэлемент();
конецесли;
записатьвпамять(св.свойство,новзн);
нсв=нкорсв.строки.добавить();
нсв.свойство=св.свойство;
нсв.значение=новзн.ссылка;
нреф=рефс.добавить();
нреф.имя=св.свойство;
нреф.строкасвойств=нсв;
иначеесли врег(лев(сокрлп(св.значение),5))="НОВАЯ" тогда
стрзнач=сокрлп(стрзаменить(св.значение,"НОВАЯ",""));
стрзнач=сокрлп(стрзаменить(стрзнач,"новая",""));
поз=стрнайти(стрзнач,".");
если поз>0 тогда
имяобъекта=лев(стрзнач,поз-1);
имятаблицы=сред(стрзнач,поз+1);
иначе
имяобъекта=стрзнач;
имятаблицы=неопределено;
конецесли;
нс=память.найти(имяобъекта,"имя");
если нс<>неопределено тогда
новзн=нс.значение[имятаблицы].добавить();
записатьвпамять(св.свойство,новзн);
конецесли;
иначе
новзн=ВычислитьЗначение(св.значение);
записатьвпамять(св.свойство,новзн);
нсв=нкорсв.строки.добавить();
нсв.свойство=св.свойство;
попытка
нсв.значение=новзн.ссылка;
исключение
нсв.значение=новзн;
конецпопытки;
если врег(сокрлп(св.свойство))="СВЯЗЬ" тогда
связь=сокрлп(св.значение);
иначеесли врег(сокрлп(св.свойство))="ПОДКЛЮЧЕНИЕ" тогда
подключение=сокрлп(св.значение);
конецесли;
конецесли;
конеццикла;
если не пустаястрока(связь) тогда
нс=списокбаз.найти(текузел.имя,"имя");
если нс=неопределено тогда
новб=списокбаз.добавить();
новб.имя=текузел.имя;
новб.подключение=подключение;
новб.связь=связь;
новб.база=неопределено;
конецесли;
конецесли;
для каждого уз из текузел.строки цикл
ОбработатьУзел(уз,текузелрез);
конеццикла;
текузелрез=стузелрез;
конецесли;
иначеесли текузел.тип="ЦИКЛ" тогда
//////////////////////////////////////////////////
новкоординаты=новыекоординаты(текузелрез);
нсрез=текузелрез.строки.добавить();
нсрез.ид=Новый УникальныйИдентификатор;
нсрез.тип="ГРУППА";
нсрез.имя=текузел.имя;
нсрез.РПШирина=19;
нсрез.РПВысота=12;
нсрез.РПВерх=новкоординаты.верх;
нсрез.РПЛево=новкоординаты.лево;
нкорсв=своуз.строки.добавить();
нкорсв.узел=нсрез.ид;
стузелрез=текузелрез;
текузелрез=нсрез;
/////////////////////////////////////////////////
Этот типичный пример копирования кода я взял из одной своей работы. Два выделенных мною вверху и внизу фрагмента полностью идентичны. Почему я так поступил? Как говорил Дейкстра, работа программиста сродни искусству жонглера. Только жонглеры крутят в воздухе физические предметы, а мы абстракции в своей голове. Представьте себе артиста в цирке. Вот он крутит три булавы, потом четыре-пять-шесть. Ему подбрасывают еще одну. А ты думаешь: неужели возьмет. Но все равно это когда-нибудь заканчивается. Вот и я не мог в тот раз "взять еще одну булаву". Мне тогда пришлось бы выкинуть что-то другое, более важное. А переделывать после я не стал. С одной стороны - время, а с другой стороны, принцип "работает - не трожь" никто не отменял.
Кто-то скажет, что я тут плохому учу. Ведь есть серьезные проекты. Ты не можешь думать только о себе. Ты должен думать и о тех, кто будет поддерживать твой код.
Хорошо. Давайте подумаем.
Допустим, у нас очень серьезный проект. Сотни тысяч будут пользоваться нашей конфигурацией. Тысячи специалистов будут заниматься ее поддержкой и кастомизацией. Тысячи глаз специалистов будут каждый день смотреть в наш код, и это налагает на нас особую ответственность.
А теперь следите за руками. Предположим, что в нашей конфигурации есть пять-шесть документов, которые формируют расходные записи в каком-нибудь регистре остатков. Соответствующие куски кода в процедурах обработки проведения у всех этих документов полностью идентичны. Я не буду приводить здесь этот фрагмент кода. Вы и сами легко можете представить себе, что там будет что-то типа:
//Получение данных из документа
//Формирование движений по регистру
//Блокировка
//Запись движений по регистру
//Контроль отрицательных остатков
Код, повторяю, полностью идентичен. Но мы не хотим просто копировать его в пять-шесть документов, потому что это "фу!". Мы создаем общий модуль. В общем модуле создаем общую процедуру, какую-нибудь ВыполнитьСписаниеПоРегистру...(). Эта общая процедура вызывает другие общие процедуры: ПолучитьДанные...(), СформироватьДвижения..(), ЗаписатьДвиженияСКонтролем...(). В принципе и эти общие процедуры могли бы вызывать какие-нибудь другие общие процедуры. 1С рекомендует делать не менее 7-8 последовательных вызовов общих процедур, но у меня не хватает на это фантазии.
А теперь на сцене появляется специалист поддержки. Тот самый один из тысячи. Ради которого мы старались сделать свой код лучше. Ему надо внести какое-нибудь плевое изменение в модуль проведения одного из документов. Реквизит регистра заполнить по-своему или контроль подправить. Он знает логику работы платформы. Он знает, что записи регистра формируются в момент проведения. Что в модуле документа есть предопределенная процедура ОбработкаПроведения(). Ему надо, быть может, добавить всего одну строку кода. А может, и того меньше, всего пару символов в определенную строку. И он вправе рассчитывать, что эта работа займет у него если и не одну секунду (1С - это одна секунда, если кто не знает), то буквально считанные минуты. Но не тут-то было. В нашей процедуре обработки проведения некуда вставить эти два символа. Там только одинокий вызов процедуры общего модуля. Специалист теперь должен пройти по нашим общим процедурам, найти нужное место для вставки своих изменений. И двумя символами дело уже никак не обойдется. И, что самое неприятное, при каждом получении от нас обновленной конфигурации, ему придется проделывать эту работу заново.
То есть, мы, выходит, вовсе не подумали об удобстве для тысячи специалистов. А о чем мы тогда думали? Об удобстве для нескольких наших разработчиков в ущерб удобству тысячи специалистов поддержки? Или просто о том, что копировать код это "фу!"?
Делайте со мной что хотите, но будь я руководителем разработки такой ответственной конфигурации, я бы просто заставил... СКОПИРОВАТЬ КОД.
Что из этого следует. Если вы разработчик-одиночка, перечитывайте почаще Маяковского
умри, мой стих,
умри, как рядовой
и старайтесь не преувеличивать значение вашей разработки.
Если же вы отвечаете за разработку массового продукта, то думайте не о каких-то там шаблонах, а о том, что вы хотите получить в результате.