Конус
"Автоматизация 2000" - небольшая компания, разрабатывающая системы сбора и учета показаний счетчиков энергии (электричество, вода, тепло, газ..).
УСПД - устройство сбора и передачи данных, к которому по шине RS-485 подключаются десятки счетчиков.
ЦОД - центр сбора и обработки данных. "Конус-ЦОД "- программа, которая опрашивает тысячи счетчиков через УСПД или модемы, формирует отчеты для Энергосбыта.
Изначально была одна программа УСПД, больше похожая на прошивку для микроконтроллеров. Использовались только базовые функции языка программирования, низкоуровневые структуры и статические массивы. Функционал был поделен на уровни (1-порты связи, 2-счетчики, 3-точки опроса..) и модули устройств, протоколов, итд.. Для обмена данными между модулями использовались "почтовые ящики", похожие на WinAPI Messages. Стиль исходного кода похож на Сишный, использованы сишные префиксы имен, очень много коротких имен из 2-3 букв, много указателей на указатели.
Из программы УСПД сделали отдельную версию ЦОД рассчитанную на сотни тысяч счетчиков, сгруппированных по домам. Большинство модулей программы осталось прежними, но дорабатывались раздельно.
Моей задачей стала доработка программ УСПД и ЦОД. Исходный код был в ужасном состоянии. При сборке проекта выдавалось около 5000 предупреждений компилятора. Комментариев очень мало, многие имена не соответствовали сути. Очень много повторений одного функционала, новые модули явно создавали путем копирования имеющихся, но забывали поправить. В папке каждого проекта были тысячи файлов и десятки папок с неиспользуемым содержимым (устаревшие компоненты, копии файлов, эксперименты, логи). Сами папки проектов были на разных виртуальных машинах, жестко привязаны к одной и той же папке на почти заполненном диске D:\.
Чтобы не тратить время на двойную работу в каждой виртуальной машине, я настроил проекты на относительные пути, чтобы работали в любой папке и перенес на одну виртуальную машину. Неиспользуемые файлы проектов перенес в отдельный архив. Сделал общую для обеих проектов папку Common, в которую переносил одинаковые для обеих проектов файлы исходников. При помощи утилиты WinMerge сравнивал папки и файлы проектов, и если они не сильно отличаются, то приводил их к "общему знаменателю" чтобы они стали одинаковыми и тоже переносил в общую папку Common. На первый взгляд изменений было очень много, но в дальнейшем оказалось, что почти все отличия были связаны с форматированием отступов и отладкой, добавлены блоки try .. except и отладочные сообщения.
В плане отладки изначально в УСПД было сделано вполне логично - отладочные сообщения содержали номер уровня, модуля и устройства, что позволяло их фильтровать. Но в какой-то момент их стало слишком много, при добавлении функционала методом копирования отладочным сообщениям не уделяли должного внимания и они потеряли информативность. Видны попытки это как-то исправить путем добавления нового функционала вывода отладочных сообщений в отдельные окна, но это только все усложнило и добавило хаоса в исходники. Есть два механизма вывода сообщений в два разных окна. Одно окно рассчитано на пользователя (крупный шрифт, выделение цветом), другое на администратора системы. Получилось, что старые модули пишут в окно администратора, новые в окно пользователя. Есть еще механизм записи логов в базу данных, но он выглядит недоработанным и непонятно где и как используется.
В настройках проектов был отключен контроль некоторых ошибок, а в исходном коде добавлено множество "заглушек", из-за которых сбои в программе игнорировались и дальше программа работала непредсказуемо. Часть ошибок было довольно легко найти и исправить, в основном это выход за пределы массива, неправильное преобразование строки в число или строки в дату-время. Была путаница между классами и структурами. Видимо, изначально были структуры, а их переделали в классы, что потребовало создавать-уничтожать экземпляры классов. Во многих случаях использование класса вместо структуры было ничем не оправданно, только зря все усложняло.
По исходному коду видно, что были проблемы с многопоточностью и невероятно много усилий было приложено для борьбы с этими проблемами. Стандартный TThread был заменен на самописный CThread с функционалом вывода отладочных сообщений. Очень много классов на базе TThread для работы в фоновом потоке, почти все основные модули. Но при этом нет никакого разделения между кодом для основного потока и для фоновых. Из фоновых потоков происходят прямые обращения к визуальным элементам (надписи, ячейки таблиц, полосы прогресса), объектам в других потоков, создаются и удаляются новые потоки. Хаотичным образом и без понимания сути используются механизмы синхронизации между потоками (семафоры, блокируемые списки). Видны попытки это как-то контролировать и отлаживать, но опять же, хаотичные - без понимания, методом тыка. Десяток вариантов кода закомментировано. Например, у потоков обработки событий из "почтовых ящиков" было бесконечное время ожидания семафора появления сообщения в ящике, что не позволяло потоку завершить работу. Поэтому ожидалось два семафора, второй назначался снаружи и открывался при остановке потока. Вместо второго семафора достаточно просто указать таймаут ожидания первого семафора, это намного проще и безопасней. У меня много времени заняла чистка неудачных решений, упрощению и возврату к стандартным решениям.
С базой данных тоже все непросто. Изначально было одно подключение к базе данных и весь функционал обращения к БД был в одном модуле. Но потом сделали "пул" на 500 подключений, причем функционал этих подключений продублировали из основного в отдельный модуль. Судя по исходникам, сделали только добавление новых подключений в "пул", а про закрытие и удаление подключений после использования забыли. Организация схемы БД ужасная - нет пояснений по таблицам и полям, в названиях таблиц и полей префиксы "венгерской" нотации. В модуле БД названия функций и их параметров не всегда осмысленны, перепутаны даты начала и конца периода запроса. В SQL не используются параметры и экранирование строк. Поиском нашлось десятки SQL запросов INSERT/UPDATE/DELETE за пределами модуля БД, где-то в визуальных формах. Причем один из "посторонних" запросов писал в БД данные в неправильном формате, что вызывало ошибку чтения данных при старте программы. Наверняка это не единственное такое место.
Когда у разработчиков возникла необходимость реализовать параллельный Poll (опрос) счетчиков по нескольким каналам (модемам), было реализовано фантастически сложное и непродуманное решение. Видимо, взяли за основу идею создать Connection Pool (пул подключений), который будет делать Task Pull ("вытягивать" задания из очереди заданий). Но по факту везде написано Pull и этот "пулл" означает что угодно - настройки порта, группу портов, набор подключений, набор потоков, список заданий. Для каждой точки опроса (точки подключения к счетчикам) создавался отдельный фоновый поток, в котором создавалось еще несколько потоков (для параллельных подключений?) и внутри каждого запускался поток установки связи и для каждого счетчика запускался поток опроса данных. Немыслимая матрешка из фоновых потоков. В базе данных, видимо, пытались реализовать группы каналов связи (модемов), чтобы ссылаться на них в настройках точек опроса. Но получилось наоборот, группы каналов ссылаются на группы опроса, а группы опроса на точки опроса.
Много проблем в понимании механизма работы доставило отсутствие типизации списков и невнятные имена. Большинство списков имело тип TThreadList (список с потокобезопасным добавлением и удалением элементов) и он весьма неудобен в использовании. Но при этом все такие списки заполнялись при создании и в дальнейшем не менялись, то есть достаточно было обычного массива array, который намного проще и удобнее. Сами элементы списков не были потокобезопасными, но использовались они в пределах одного потока. После замены таких списков на простые массивы стало очевидно, что в ряде случаев они просто не нужны - либо не используются, либо читаются из БД и передаются в другой класс, который и сам может его прочитать из БД. И как выяснилось дальше, в ряде списков мог быть только один элемент, поэтому не было смысла в самом списке.