Мастер-класс от Poppy (практикум по рефакторингу)




Принцип обмена данными из 1С с сайтом (на MySQL) и выдачи (публикации) этих данных по запросу.
PHP-Скрипт автоматической загрузки данных из файла данных в формате CSV в базу данных сайта работающего на WordPress.

В продолжение моей темы: 1С:Альфа-Авто Автосалон Автосервис: обмен с сайтом.
С помощью данного скрипта можно загружать в автоматическом режиме, по расписанию, данные сервисных книжек (ремонтов авто) из 1С:Альфа-Авто Автосалон Автосервис.
Также можно загружать данные в ручном режиме: для этого делается скрытая страница, где размещается специальная кнопка.
Комментарии размещенные внутри скрипта разъяснят логику и порядок действия.
Комментарии с "/////    echo" использовались для отладки.
Дополнительно создана таблица для журналирования результатов загрузки данных.
Скрипт включает в себя защиту от SQL инъекций (думаю безопасность соблюдена в полной мере).
В кратце:
1. Пишется скрипт, который запускает этот.
2. Создается регламентное задание в WordPress, по которому запускается скрипт из п.1. 
3. Этот скрипт осуществляет проверку на существование файла обмена в папке.
4. Если данные не новые, загрузка не производится.
5. Если данные новые, очищается таблица сервисных книжек.
6. Загружаются новые данные.

Собственно сам скрипт:

<?php // Полная загрузка сервисных книжек, создан 2024-01-05 12:44:55

global $wpdb2;
global $failure;
global $file_hist;

/////  echo '<H2><b>Старт загрузки</b></H2><br>';

$failure=FALSE;
//подключаемся к базе
$wpdb2 = include_once 'connection.php'; ; // подключаемся к MySQL
// если не удалось подключиться, и нужно оборвать PHP с сообщением об этой ошибке
if (!empty($wpdb2->error))
{
/////   echo '<H2><b>Ошибка подключения к БД, завершение.</b></H2><br>';
$failure=TRUE;
wp_die( $wpdb2->error );
}

$m_size_file=0;
$m_mtime_file=0;
$m_comment='';
/////проверка существования файлов выгрузки из 1С
////файл выгрузки сервисных книжек
$file_hist = ABSPATH.'/_1c_alfa_exchange/AA_hist.csv';
if (!file_exists($file_hist))
{
/////   echo '<H2><b>Файл обмена с сервисными книжками не существует.</b></H2><br>';
$m_comment='Файл обмена с сервисными книжками не существует';
$failure=TRUE;
}

/////инициируем таблицу лога
/////если не существует файла то возврат и ничего не делаем
if ($failure){
///включает защиту от SQL инъекций и данные можно передавать как есть, например: $_GET['foo']
/////   echo '<H2><b>Попытка вставить запись в лог таблицу</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>$m_mtime_file,'last_size_upload'=>$m_size_file,'comment'=>$m_comment));
wp_die();
/////    echo '<H2><b>Возврат в начало.</b></H2><br>';
return $failure;
}
/////проверка лога загрузки, что бы не загружать тоже самое
$masiv_data_file=stat($file_hist);   ////передаем в массив свойство файла
$m_size_file=$masiv_data_file[7];    ////получаем размер файла
$m_mtime_file=$masiv_data_file[9];   ////получаем дату модификации файла
////создаем запрос на получение последней удачной загрузки
////выбираем по штампу времени создания (редактирования) файла загрузки AA_hist.csv, $m_mtime_file

/////   echo '<H2><b>Размер файла: '.$m_size_file.'</b></H2><br>';
/////   echo '<H2><b>Штамп времени файла: '.$m_mtime_file.'</b></H2><br>';
/////   echo '<H2><b>Формирование запроса на выборку из лога</b></H2><br>';
////препарируем запрос
$text_zaprosa=$wpdb2->prepare("SELECT * FROM `vin_logs` WHERE `last_mtime_upload` = %s", $m_mtime_file);
$results=$wpdb2->get_results($text_zaprosa);

if ($results)
{   foreach ( $results as $r)
{
////если штамп времени и размер файла совпадают, возврат
if (($r->last_mtime_upload==$m_mtime_file) && ($r->last_size_upload==$m_size_file))
{////echo '<H2><b>Возврат в начало, т.к. найдена запись в логе.</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>$m_mtime_file,'last_size_upload'=>$m_size_file,'comment'=>'Загрузка отменена, новых данных нет, т.к. найдена запись в логе.'));
wp_die();
return $failure;
}
}
}
////если данные новые, пишем в лог запись о начале загрузки
/////echo '<H2><b>Попытка вставить запись о начале загрузки в лог таблицу</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>0, 'last_size_upload'=>$m_size_file, 'comment'=>'Начало загрузки'));

////очищаем таблицу
$clear_tbl_zap=$wpdb2->prepare("TRUNCATE TABLE %s", 'vin_history');
$clear_tbl_zap_repl=str_replace("'","`",$clear_tbl_zap);
$results=$wpdb2->query($clear_tbl_zap_repl);
/////   echo '<H2><b>Очистка таблицы сервисных книжек</b></H2><br>';
if (empty($results))
{
/////   echo '<H2><b>Ошибка очистки таблицы книжек, завершение.</b></H2><br>';
//// если очистка не удалась, возврат
$failure=TRUE;
wp_die();
return $failure;
}

////загружаем данные
$table='vin_history';         // Имя таблицы для импорта
//$file_hist Имя CSV файла, откуда берется информация     // (путь от корня web-сервера)
$delim=';';          // Разделитель полей в CSV файле
$enclosed='"';      // Кавычки для содержимого полей
$escaped='\

30 Comments

  1. можно вкратце объяснить суть рефакторинга?

    Reply
  2. Вадимко

    Оба кода являются наикрасивейшими образчиками мЫшления!

    Reply
  3. CheBurator

    У Поппы — понятнее.. вот и весь рефакторинг (упрощенно)

    Reply
  4. BabySG

    Совершенно обоснованное мнение по «неопытному кодеру» — повторять н-раз один и тот же код — признак неопытности.

    Так что Poppy совершенно права и её код читается намного легче.

    А отмазки, типа: «Я не хотел дробить эту функцию на несколько, т.к. постоянно приходится ее тянуть в разные обработки.» выглядят вообще смешно.

    Зато прикольно будет, наверное, когда прицип обода поменяется и нужно будет изменять код в Н-местах вместо одного.

    Reply
  5. alexk-is

    Рефакторинг — это не только перестановка строк местами и вынос повторов в процедуры и функции…

    Стало "понятнее" — ну, не намного. Код по-прежнему не читается с листа. Переменные, процедуры и функции не говорят сами за себя. "СоздатьМДКласс" — на самом деле создает массив с обрабатываемыми видами объектов метаданных. И так почти по каждой строке… (без бутылки не разберешься 🙂 ).

    А вот что медленнее работать будет это точно. 🙁

    И не всегда минимум кода это хорошо.

    Например:

    Вариант 1

    Код
    Массив = Новый Массив(7);
    Массив[0] = 1;
    Массив[1] = 2;
    Массив[2] = 3;
    Массив[3] = 4;
    Массив[4] = 5;
    Массив[5] = 6;
    Массив[6] = 7;
    

    Показать полностью

    Вариант 2

    Код
    Массив = Новый Массив(7);
    Для Индекс = 0 По 6 Цикл
        Массив[Индекс] = Индекс + 1;
    КонецЦикла;
    

    Показать полностью

    Сравним:

    Вариант 1 — 8 строк кода

    Вариант 2 — 4 строки кода

    Вариант 1 — 8 команд при выполнении

    Вариант 2 — 26 команд при выполнении

    Кроме этого вариант 2 работает в 6-10 раз медленее…

    Обратите внимание на 175 пост http://infostart.ru/blogs/652/ кратко и по существу…

    Reply
  6. Vitek

    2 alexk-is

    Вариант 1 — 8 команд при выполнении

    Вариант 2 — 26 команд при выполнении

    Кроме этого вариант 2 работает в 6-10 раз медленее…



    Я все понимаю, но это ж не ассемблер 🙂

    «Медленнее» в данном случае весьма условно.

    К тому же, а если бы массив содержал 100 элементов?

    Reply
  7. quest

    ИМХО, рефакторинг требует юниттестирования. что бы быть уверенным что отрефакторил правильно. А здесь оного не увидел.

    Reply
  8. Altair777

    (7) > К тому же, а если бы массив содержал 100 элементов?

    Мне кажется, что на практике очень редко массивы заполняются значениями по простым формулам, зависящим от индекса элемента

    И, опять же — рефакторинг — это не догма, а средство )

    Reply
  9. Strange Device

    Считаю, что вынос каждого определения таблицы значений или массива в отдельную функцию или подпрограмму — не улучшает читаемость кода, а только затрудняет его чтение…

    Reply
  10. Vitek

    (10)А вот и нет.

    Reply
  11. Altair777

    (10-11) у меня сейчас дежа вю будет 🙂

    http://infostart.ru/blogs/652/

    Reply
  12. alexk-is

    (10) (11) В данном случае не улучшает т.к. из названия функции совершенно не понятно, что она делает. Но могло бы улучшить, если функция вызывается несколько раз, если название отражает содержание, если добавить комментарии…

    Reply
  13. fez

    Присоединюсь к (8)

    Атцы рефакторинга (Кент Бек, Мартин Фаулер) настаивают на том, чтобы рефакторинг НАЧИНАЛСЯ с написания комплекта тестов. Для того, чтобы оный рефакторинг был БЕЗОПАСНЫМ.

    Reply
  14. artbear

    1. Считаю, что работа по рефакторингу выполнена не полностью.

    Например,

    — что такое МДКласс и СоздатьМДКласс, совершенно непонятно 🙁

    — ОбойтиХХХ также не очень раскрывает смысл кода 🙁

    — переменная «Описание» также выполняет несколько функций и ее название не очень удачно.

    .

    2. По тестированию также не очень понятно, было ли оно выполнено или нет — а ведь структура кода довольно сильно изменилась.

    .

    3. Правильность работы кода, как исходного, так и полученного, не проверял, но терзают смутные сомнения — правильно ли использовать одну и ту же переменную «Описание» на разных уровнях кода? Ведь все время работаем с одним объектом 🙁

    Reply
  15. artbear

    Также хорошо бы спец.блок

    Код
    Р = ложь;
    Выполнить(Код);
    Если Р = истина Тогда

    Показать полностью

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

    .

    Например, ВыполнитьКод_ВозвращающийРезультатОтбораВСпецПеременную_P(Код)

    .

    А вообще подобное использование такой переменной со спец.названием — это неявная передача данных, что может быть чревато.

    Вполне может быть, что проще изменить алгоритм — например, Код это просто выражение на языке 1С, которое должно вернуть Истину или Ложь.

    А уж внутренний алгоритм выполнит

    ИтоговыйКод = " P = " +Код;

    и не будет никаких неявностей.

    .

    Тут не настаиваю, могут быть выбраны разные пути, в т.ч. и уже существующий 🙂 — дело вкуса.

    Reply
  16. artbear

    А.

    Также не очень красиво

    Функция ОбойтиМетаданные(П, Код)

    функция и получает параметр П и возвращает ту же переменную П 🙁

    А в описании задачи сказано, что П — изначально это пустая структура.

    .

    Фактически можно либо

    1) Создать П внутри ОбойтиМетаданные и возвращать П как результат работы данной функции, а от параметра П избавиться.

    2) или избавиться от функции, преобразовав ее в процедуру.

    Для данной задачи ИМХО п.1 предпочтительнее.

    .

    Да и наименование параметра П также совсем не звучит 🙁

    .

    Б.

    Функция ОбойтиАтрибуты(Атрибуты, ВидАтрибута, П, Описание, Код); — точка с запятой лишняя 🙁 Данная опечатка, похоже, показывает, что код не тестировался 🙁

    .

    Данный блок определен как функция, а на самом деле ничего не возвращает — маленький косяк 🙂

    Reply
  17. ValeriVP

    минус

    как минимум по тому, что считаю использовании попыток без обработки исключительных ситуаций грубейшей ошибкой

    Reply
  18. artbear

    Также код внутри блока

    Код
    Для Каждого Класс ИЗ МДКласc Цикл
    КонецЦикла;

    Показать полностью

    лучше выделить в отдельный метод типа СобратьДанныеПоМетаданному().

    Код становится еще проще читать и исправлять.

    Reply
  19. ValeriVP

    также я не могу даже предположить, для каких задач могут пригодиться данные в таком виде

    Reply
  20. Altair777

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

    прочитай внимательно шапку

    В качестве подопытного кода была выбрана статья http://infostart.ru/blogs/688/. Не буду обсуждать полезность и нужность рассматриваемого кода, просто переставлю строчки кода так, что бы с ними можно было работать.

    Reply
  21. O-Planet

    Вообще имеет смысл что-то оптимизировать, не число буковок сокращая, а меняя саму суть. А придраться можно к чему угодно. Опять же, какова направленность оптимизации? Вариантов 4, как минимум: читабельность, функциональность, скорость, универсальность. Зачем в данном случае была предпринята оптимизация, и чем предложенный вариант лучше оригинала — не раскрыто. Лично мне, начинавшем программировать на С, в предложенном варианте сразу бросаются в глаза ляпы, которые никакой уважающий себя сишник не позволил бы себе. Например: зачем Описание=Новый Структура(); делать в цикле? Это ведь память. СоздатьМДКласс и СоздатьТЗ я бы сделал одним методом, суть у них одинакова, и сделал бы универсально:

    Код
    // Поля - перечисленные через запятую поля таблицы значений
    Функция СоздатьТЗ(Знач Поля)
      ТЗ=Новый ТаблицаЗначений;
      Пока Поля<>"" Цикл
        П=Найти(Поля,",");
        Если П=0 Тогда
          Прервать;
        КонецЕсли;
        Поле=СокрЛП(Лев(Поля,П-1));    
        Поля=Сред(Поля,П+1);
        ТЗ.Добавить(Поле);
      КонецЦикла;
      ТЗ.Добавить(Поля);
      Возврат ТЗ;
    КонецФункции
    

    Показать полностью

    ПС Poppy осознает свою гениальность… Она тоже стареет?

    Reply
  22. artbear

    (22)

    0. Смысл рефакторинга — в первую очередь, это повышение читаемости кода для возможности дальнейшего его исправления, сопровождения/изменения, повышения функциональности и т.д. и т.п.

    .

    1. Цитата:

    >>СоздатьМДКласс и СоздатьТЗ я бы сделал одним методом,

    >>суть у них одинакова, и сделал бы универсально:

    Не согласен, как раз суть/смысл у них разные 🙂 и нужны отдельные методы для точного понимания, что же делается этим кодом. А вот внутри этих спец.методов уже можно юзать как твое СоздатьТЗ, так и ручное заполнение.

    .

    2. По Описание = Новый Структура() — главное правило «Не нужно делать преждевременную оптимизацию» ! Нужно смотреть в профайлере уже после написания работоспособного кода и выполнения рефакторинга.

    Reply
  23. O-Planet

    >> Не согласен, как раз суть/смысл у них разные 🙂 и нужны отдельные методы для точного понимания

    Угу. Припоминаю эксамплы из Borland C 3.1 и BP 7 Там этот подход яростно практиковался, когда каждую мыслю в отдельный метод оформляли. Иногда достаточно обойтись комментарием, имхо, имея универсальный метод. Одно дело — десяток индивидуальных процедур, пусть даже с понятными названиями, другое — одна универсальная с комментариями в каждом конкретном месте ее вызова. Второе все-таки воспринимается проще. Этот подход практикуется, как понимаю, и у разрабов типовых конфигураций.

    Reply
  24. artbear

    (24) Коммент для показа назначения кода очень часто не есть гуд 🙁 при чтении вызовов подобных методов, как правило, неудобно изучать подобный код.

    .

    Гораздо проще и надежнее писать код так / называть методы так, что они не нуждаются в комментах, а их назначение понятно из их наименования.

    .

    Разработчики типовых — отдельная песня 🙂

    В основном, конечно, очень грамотные решения, но и частенько совершенно отвратительный код 🙁

    А уж любовь 1С к длиннющим блокам кода — это просто ужас 🙁

    Reply
  25. O-Planet

    (25) Думаю, лучше сочетать все-таки. 100 поименованных методов — тоже не есть гут. Когда метод унифицирован, то он лучше описывает алгоритм уже на уровне реализации. Даже тут: одно дело — две процедуры «СоздатьМДКласс()» и «СоздатьТЗ()»… Не посмотрев, я даже представить не могу, что может находиться внутри их, особенно в первой. Не вижу из контекста, что такое МДКласс… Если же использовать один метод и там и там то все становится ясно в контексте, плюс — я сразу понимаю, что есть на выходе «СоздатьМДКласс()». Т.е. если сделать

    МДКласс= СоздатьТЗ(«РегистрыСведений,Справочники,Документы, … «);

    ТЗ=СоздатьТЗ(«Класс,Вид,ТЧ,Атрибут, …»);

    — то все понятно и по структуре данных, и по их типу.

    Reply
  26. artbear

    (26) 1. Я уже выше писал, что как раз название МДКласс и СоздатьМДКласс совсем неудачны и непонятны 🙂

    2. В данном случае можно ипользовать и этот подход — все понятно и без комментариев.

    Но с более сложным кодом такая фишка уже не пройдет 🙂

    И самое главное — у правильного метода не нужно смотреть реализацию, все понятно из его наименования 🙂 Например, твой же СоздатьТЗ не требует доп.комментов 🙂

    Reply
  27. MSensey

    Минус

    1. где описание проблем, которые были в исходном коде?

    2. что должен был понять читатель этого блога?

    Reply
  28. Makdir

    «1. где описание проблем, которые были в исходном коде?

    2. что должен был понять читатель этого блога?»

    Присоединяюсь. Важен не сам код решения, а содержание ошибок кода и методология. Лучше было дать сравнительную таблицу типа «верно», «неверно» и «почему именно так», а также рекомендации к составлению оптимального кода, при использовании которых начинающий программист сумел бы обойти «острые углы». Да и, в принципе, рефакторинг в моем понимании предполагает более глубокий анализ кода и структуры данных, а не просто исправление ошибок в исходном коде. Думаю многие со мной согласятся, что это слабое место 1С — инструментарий рефакторинга в ней оставляет желать лучшего.

    Reply
  29. n949eo

    Плюс)

    Reply
  30. AlexO

    (5) BabySG,

    Совершенно обоснованное мнение по «неопытному кодеру» — повторять н-раз один и тот же код — признак неопытности.

    да неужели? вот откуда во всех типовых — десятки функций, делающих одно и тоже, но реализованные аюсолютно по-разному. Ну, и ошибки в них — разные…

    Код идентичный повторять не нужно — делаешь вызов одной и той же фукнции.

    А отмазки, типа: «Я не хотел дробить эту функцию на несколько, т.к. постоянно приходится ее тянуть в разные обработки.» выглядят вообще смешно.

    это почему еще? очень удобно не изобретать велосипед каждый раз (за что тут же, на инфострате, ратуют наверняка те же самые, кто пишет «смешно тянуть код в обработки» 🙂 )

    Это минус 1с-у — при такой дезорганизаванности и бессистемности операторов до сих пор нет официального репозитария стандартного кода и функций.

    Зато прикольно будет, наверное, когда прицип обода поменяется и нужно будет изменять код в Н-местах вместо одного.

    «прикольно» — это когда обработка номера документа в разных документах вызывает разные функции обработки.

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *