Создание автоматизированой системы на базе X-Control Dispatcher

Задание

Допустим, Вам необходимо разработать систему для мониторинга температуры и управления несколькими электро цепями. Система должна иметь возможность отображать данные в красивом виде, как в самой программе, так и через веб-браузер. Дополнительно необходимо писать температуру в базу данных и ещё много чего.
Устройство, с которого будет считываться температура, у Вас уже есть (разработали/приобрели).

Проблема

Необходимо создать программное обеспечение. Учитывая вышеуказанное техзадание, нужно как минимум:
  1. Сделать опрос устройства.
  2. Сделать отображение данных в программе в красивом виде.
  3. Сделать веб-интерфейс. Задание можно усложнить тем, что уровни доступа для веб-интерфейса должны быть разные для разных пользователей.
  4. Сделать запись в базу данных.
Третий пункт, наверное, самый сложный.

Решение №1. Сложное и затратное

Написать ПО с нуля.
В зависимости от остальных нюансов ТО, времени на разработку понадобится от 1 недели до бесконечности.

Решение №2. Простое и дешевое

Сделать это ПО на базе X-Control Dispatcher.
Единственное, что Вам нужно сделать в данном случае — это написать расширение для взаимодействия Вашего устройства с программой X-Control Dispatcher. Ну и сделать всякие настройки в проге.

Реализация

Нам нужен USB-термометр. Чтобы не изобретать велосипед, выложу уже существующий, который раньше разрабатывал. На термометре дополнительно есть 2 канала для управления нагрузками. Схема:

Печатная плата, исходники и т.д. находятся в прикрепленных файлах, описывать здесь не вижу смысла.
Теперь необходимо разработать расширение для X-Control Dispatcher, которое будет взаимодействовать с термометром. Качаем прогу от сюда (прямую ссылку на скачку не выложил, так как она изменяется при выходе новой версии программы). Расширение будем писать с помощью библиотеки Qt.
В папке с программой есть папка _extansion_. Её содержимое нам и нужно для создания расширения. Внутри есть файл ReadMe.txt, его нужно обязательно почитать, иначе дальше не поймете.
Открываем Qt, создаем новый проект (библиотеку):

В следующем окне выбираем тип «Динамическая библиотека» и назовем «USB_Thermometer_EX». Класс называем как попало, ибо его сразу удаляем. Конкретно, удаляем все *.h и *.cpp файлы. После этого копируем в папку с проектом содержимое папки _extansion_ и добавляем их в проект, должно получиться так:

Все изменения мы будем делать в «extansion.h» и «extansion.cpp». Там находится класс «Extansion» (можете переименовать), который наследуется от класса «XExtansionBaseClass». В последнем есть несколько методов, которые нам нужно переопределить. В файле «xextansionbaseclass.h» много комментариев, прочтите их.
Создаем слот «void poll()», который по таймеру будет опрашивать термодатчики и каналы.
Переопределяем «void x_start();». Он вызывается после того, как модуль подключит слоты и сигналы к этому расширению, расширение должно начать работу именно при вызове этого метода. В нем создаем таймер и подключаем к слоту «void poll()»:
void Extansion::x_start()
{
	QTimer	*t = new QTimer(this);
	connect(t, SIGNAL(timeout()), SLOT(poll()));
	t->start(1000);
}

Создаем метод «void getSensors()» и вызываем его в этом слоте. Данный метод будет получать температуру с датчиков.
В архиве уже есть драйвера и классы для работы с USB, описывать их работу не стану, просто покажу, как с ними работать.
В «extansion.h» подключим «usb_lib/usb_lib.h» и в класс Extansion добавим переменную типа usb_lib, назовем её «usb», через неё мы будет обмениваться данными с устройством. Так же, добавим переменную «QStringList last», в которую будет записан список последних опрошенных датчиков.
Теперь делаем сам опрос. Вот так делается запрос количества датчиков и получение их ID:
void Extansion::getSensors()
{
	if (!usb.openDevice()) return;  // если ошибка соединения
	char	buff[300];

	// количество датчиков
	int	sCount = 0;
	if (!usb.getReport(0, USB_GET_nSensors, buff, 1)) return;
	sCount = buff[0];

	// адреса
	QStringList	addr;
	if (!usb.getReport(0, USB_GET_gSensorIDs, buff, sCount * 8)) return;
	for (int i=0; i<sCount; i++) {
		char	*id = &buff[i*8];
		char	str[100];

		sprintf(str, "%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X", quint8(id[0]), quint8(id[1]), quint8(id[2]), quint8(id[3]), quint8(id[4]), quint8(id[5]), quint8(id[6]), quint8(id[7]));
		addr.append(QString(str));
	}
}

Теперь нам нужно проверить, изменилось ли количество датчиков или их адреса. Чтобы сильно не заморачиваться, сделаем так:
// Проверяем, изменились ли датчики
	bool	changed = false;
	foreach (QString var, addr)
		if (!last.contains(var)) {
			changed = true;
			break;
		}
	if (!changed)
		foreach (QString var, last)
			if (!addr.contains(var)) {
				changed = true;
				break;
	}

Теперь записываем адреса только что опрошенных датчиков в «last» и в случае, если changed==true, уведомляем программу о том, что список датчиков изменился.
last = addr;
if (changed) emit x_componentsUpdated();

Теперь получаем данные с датчиков, конвертируем в температуру и устанавливаем в качестве значений компонентов
// читаем данные с датчиков
	if (!usb.getReport(0, USB_GET_gSensorData, buff, sCount * 2)) return;
	for (int i=0; i<sCount; i++) {
		double tempRead = ((quint8(buff[(i*2) + 1]) << 8) | quint8(buff[i*2]));
		double t = tempRead / 16;
		// round to 1 digit after ,
		t = (int)(t*10);
		t = t / 10;
		// устанавливаем в качестве значений компонентов
		x_componentSetValue(last.at(i), t);
	}

Почти готово. Вызовом сигнала «x_componentsUpdated()» мы уведомили программу о том, что список компонентов изменился, теперь программа запросит новый список с помощью метода «QMap<QString, widgetComponentItem_t> x_getComponentsList();» и мы должны составить этот список. Переопределяем метод.
QMap<QString, XExtansionBaseClass::widgetComponentItem_t> Extansion::x_getComponentsList()
{
	QMap<QString, widgetComponentItem_t>	m;
	widgetComponentItem_t	i;

	foreach (QString var, last) {
		i.isSetter = true;
		i.label = "Thermosensor";
		i.writable = false;

		m.insert(var, i);
	}

	return m;
}

Здесь возвращаем QMap, где в качестве ключа ID компонента, а в качестве значения — структура с параметрами. isSetter=true указывает, что данный компонент расширения (не путать с компонентом виджета) будет сэттэром для подключенного к нему компонента виджета. writable=false потому, что мы не можем записывать данные в термодатчик (можно только считывать). Подробней читайте в ReadMe.txt.
Каналы для управления внешним миром пока что подключать не будем, попробуем запустить расширение.

Расширение загружается не напрямую с программы, а через модуль xextansion*. Библиотека этого модуля лежит тоже в папке _extansion_\lib. Также, в этой папке есть директория модуля «xextansionDir». Директория модуля имеет такое же название, как и модуль, но в конце ещё «Dir». Копируем этот модуль с папкой в папку «modules_lib» программы. А чтобы было как-то солидней, переименуем модуль и папку, к примеру, в «UsbTerm», тоесть, под винду у нас в папке «modules_lib» будет файл «UsbTerm.dll» и папка «UsbTermDir».
Компилируем наш проект, на выходе мы должны получить библиотеку, в нашем случае, «USB_Thermometer_EX.dll», его и должен загрузить модуль. В папке модуля (UsbTermDir) есть папка «extansion», вот туда и закидываем библиотеку расширения. Так же, в этой папке есть файл «extansion.ini», в нем в группе «General» есть ключ «libFileName», в котором указана библиотека расширения. В нашем случае должно быть так:
[General]
libFileName=USB_Thermometer_EX.dll

Запускаем X-Control Dispatcher. На вкладке «Модули» нажимаем «Добавить модуль», назовем его «Termometer», затем выбираем наш модуль:

Нажимаем OK.

Расширение успешно загрузилось, связалось с термометром, получило данные с термодатчика (подключен один) и уведомило прогу и том, что имеет один компонент, который программа и отобразила в таблице. Теперь мы может подключить к этому компоненту компонент виджета. Сначала советую посмотреть этот видос, в котором рассказано о виджетах и как получить доступ к программе через веб-браузер.
Итак, допустим, Вы создали виджет для термометра, у меня получился такой:

Стрелка должна отклоняться в зависимости от температуры. Подключим эту стрелку к нашему термодатчику. Копируем ID этого виджета (Правая кнопка -> скопировать ID), переходим на вкладку модуля термометра и вставляем этот ID в колонке «Виджет», а в «Компонент виджета» выбираем стрелку, должно получиться примерно так:

Теперь значение термодатчика передается на стрелку термометра.

Отображение температуры готово!

Как писалось выше, у термометра есть 2 канала, с помощью которых можно управлять внешним миром. При выключенном канале на нем 0В, при включенном около 5В, ток до 10мА. Сделаем возможность управления этими каналами через компоненты виджета.
Нам нужно добавить 2 компонента расширения для этих двух каналов. Они, в отличии от термодатчиков, будут постоянными, тоесть, всегда должны отображаться в списке компонентов. Дополним метод «x_getComponentsList»:
QMap<QString, XExtansionBaseClass::widgetComponentItem_t> Extansion::x_getComponentsList()
{
	QMap<QString, widgetComponentItem_t>	m;
	widgetComponentItem_t	i;

	foreach (QString var, last) {
		i.isSetter = true;
		i.label = "Thermosensor";
		i.writable = false;

		m.insert(var, i);
	}

	i.isSetter = true;
	i.writable = true;
	i.label = "Channel 1";
	m.insert("ch1", i);
	i.label = "Channel 2";
	m.insert("ch2", i);

	return m;
}

Здесь мы добавили ещё 2 компонента. writable=true потому что компонент виджета должен иметь возможность устанавливать сюда значение (иначе, мы не смогли бы управлять каналами).
Добавляем метод «void getChannels();», который будет получать значения каналов и устанавливать их в компоненты подобно методу «getSensors()»:
void Extansion::getChannels()
{
	if (!usb.openDevice()) return;  // если ошибка соединения
	char	buff[10];

	if (!usb.getReport(0, USB_GET_Chanels, buff, 1)) return;
	quint8	s = buff[0];
	x_componentSetValue("ch1", bool(s & (1<<2)));
	x_componentSetValue("ch2", bool(s & (1<<1)));
}

Метод вызываем в слоте «poll()», после «getSensors()».
Теперь в списке компонентов отобразились эти 2 канала:

Мы можем подключить их к компонентам виджета и видеть их состояние, но нам же нужно ими ещё и управлять. Компонент виджета может устанавливать какое-либо значение, уведомив об этом своего сэттэра (в нашем случае, это расширение), а сэттэр может принять это значение и выполнить необходимые действия.
Конкретно, нам нужно принимать возвратное значение, тоесть, то, которое передал компонент виджета, для этого переопределяем метод «void x_componentReturnValueUpdated(QString id, QVariant value);».
id — это тот id виджета. который мы указали в списке (он так же отображается в таблице). value — это значение, полученное с компонента.
void Extansion::x_componentReturnValueUpdated(QString id, QVariant value)
{
	// устанавливаем значение каналов
	if (id == "ch1") usb.setReport(value.toBool(), 2, NULL, 0);	// установить пин 2 (канал 1)
	else if (id == "ch2") usb.setReport(value.toBool(), 1, NULL, 0);	// установить пин 1 (канал 2)
}

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

Сначала разберемся с галочкой, так как это самый простой вариант. Подключаем её к компоненту расширения точно так же, как подключали кнопку:

И всё! Теперь с помощью этой галочки мы можем управлять первым каналом.
Приступим к реализации второго канала, сделаем управление через кнопку. Когда канал выключен, на кнопке должно быть написано «Включить» и при клике должна отправляться комманда на включение. В противном случае наоборот. Заходим в настроки кнопки (Правый клик -> настройки компонентов) и делаем вот такие установки:

При нажатии на кнопку надпись будет изменяться с «Выключен» на «Включен» и наоборот, изменяться будет моментально. Так как для компонента не установлен сэттэр, он будет при клике отправлять значение сам себе. Подключим этот компонет к расширению точно так же, как подключали галочку.
Готово! Теперь по нажатию кнопки можно изменять состояние выхода. Как заметили, изменение надписи на кнопке теперь происходит с небольшой задержкой. Это потому, что у компонента теперь есть сэттэр и он при клике отправляет значение не сам себе, а расширению, оно отправляет комманду термометру, а считывает значение уже по таймеру, после чего устанавливает новое значение на кнопку и она изменяет надпись. Таким образом обеспечивается обратная связь и если при нажатии кнопки не будет связи с термометром, то надпись на кнопке не изменится и пользователь сможет на это отреагировать.

Дополнительные возможности

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

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

Заметка

Здесь я не писал подробности об термометре, так как целью данной статьи было объяснить Вам, как создавать расширения для X-Control Dispatcher.

Зачем это нужно?

Раньше я фрилансил и в основном приходилось делать программно-аппаратные комплексы средней и малой сложности. Аппаратная часть в большинстве случаев была простой — датчики и исполнительные устройства, а вот программная часть была довольно сложная и на неё уходило 70-80% времени разработки всей системы. Собственно, эта проблема и дала начальный толчок для создания подобной программы.
Программа полностью бесплатная и Вы можете её свободно использовать в коммерческих и не коммерческих целях. Теперь, когда Вам нужно будет разработать подобную систему, Вам уже не нужно будет с нуля писать всю программу, нужно будет просто написать небольшое расширение, а в некоторых случаях, можно использовать и готовые. Таким образом, сильно уменьшается время и затраты на разработку, а вместе с ними и конечная цена, что не может не порадовать заказчика, особенно в столь сложной экономической обстановке)))
  • +6
  • 16 апреля 2015, 18:53
  • MrMisha
  • 1
Файлы в топике: easyelectronics_.zip

Комментарии (32)

RSS свернуть / развернуть
До чего же оно уродливое… Был бы сделан интерфейс по хорошим промышленным стандартам, можно было бы посмотреть. А блядства гламурного и так повсюду навалом.
-1
В чем заключается уродство и гламур?
0
Банально цвета вырвиглаз зачастую (я первую часть описания продукта тоже смотрел) + нет какого либо единого стиля между элементами, все действительно взято как в первой части писали — откуда попало из инета.
Сама система может быть очень хороша (чесно признаюсь — не смотрел), но внешне она выглядит мягко говоря отталкивающей и сделанной первокурсником-студентом, что и не дает относится к ней серьезно.
+1
Того, что выглядит ужасно — я не отрицаю, ибо даже самому не нравится, но дизайном я не занимался.
Это во первых. Во вторых, прогу гораздо удобней использовать через веб-браузер, там выглядит гораздо лучше, но тоже особо не заморачивался.
А система действительно класная, просто нужно с ней разобраться)))
0
Заметил, что отрисовка виджетов отличается на десктопе и в WEB. А как у вас реализованы сами виджеты: каждый виждет имеет 2 захардкордженные реализации (для Qt и для WEB), или какое-то универсальное решение (типа QML+QmlWeb)?
0
Отдельное отображение в программе и на Web. В папке с компонентами виджетов (widgets_components_lib) xwc*.dll для проги, xwc*_web.js для web.
0
Дык получается, что для создания виджетов (имеется ввиду написание своих виждетов) нужно делать двойную работу. А почему не хотите полностью перейти на WEB? Ну или можно с QML попробовать поиграться…
0
Хотя бы потому, что нужно переделывать прогу. Может когда-то дойдет до этого дело, но пока что так оставлю. Ну и плюс те компоненты, которые уже есть, перекрывают практически все задачи. Учтите, что программа не позиционируется как очередная SCADA система (которых и так навалом), это простая, понятная и бесплатная программа для построения систем автоматизации малой и средней сложности, которых я не находил. Не хватает только поля ввода текста и ползунка, но скоро добавлю.
И вы ещё кое-что спутали. То, что вы подразумеваете под виджетами, на самом деле — компоненты виджетов. К примеру, выше на изображении термометра есть один виджет, а на нем 2 компонента — изображение (шкала) и стрелка. Ещё ниже тоже один виджет с 4 компонентами — изображение, стрелка, чекбокс и кнопка.
0
И вы ещё кое-что спутали.
Да, я не до конца разобрался в терминологии, но мы друг друга поняли правильно, я имел ввиду именно «компоненты».
Ну и плюс те компоненты, которые уже есть, перекрывают практически все задачи.
Компонент, как по мне, очень мало. Что первым бросилось в глаза — нет графиков величины от времени (аналогов самописца). Я просто сам (по работе) писал аналогичную систему (там все было замешано на визуальном редакторе от Borland C++ и javascript) и построитель графиков был самым востребованным компонентом (ибо оператор не будет 24/7 смотреть на стрелку виртуального прибора, но важно отслеживать динамику и график величины по времени подходит как нельзя лучше).
0
А, да, графиков нет. Пока что я делаю так: пишу в бд, а от туда excel затягивает данные и отображает в виде графика. Но, естественно, на веб-морже это не отображается.
0
А почему не хотите полностью перейти на WEB?
а я ему это еще в прошлых сериях говорил, там архитектура изначально неверная, если он нацелился на кроссплотформенность + веб
надо было писать на питоне + есть готовые виджеты на яваскрипте, включая шкалы и графики
0
API расширений напоминает плагины к 3ds max — которые нужно собирать в конкретном компиляторе и под конкретную версию программы. Плохая идея. Вполне можно было сделать чисто процедурный API, позволяющий писать расширения на люом языке, который нравится юзеру.
Алсо, ЕМНИП Qt5 не поддерживает WinXP, что несколько сокращает круг потенциальных клиентов.
0
  • avatar
  • Vga
  • 16 апреля 2015, 22:47
C чего бы не поддерживает? Я, конечно, давно не пользовался XP, но никто ещё мне не жаловался, что мои проги, написанные на Qt5 не работают под XP. Да и в инете об таком косяке даже упоминания нет.
0
API расширений напоминает плагины к 3ds max — которые нужно собирать в конкретном компиляторе и под конкретную версию программы. Плохая идея.

Это, увы, известная проблема для С++ (плохая бинарная совместимости интерфейсов). Вроде в одной С++ среде есть std::string и в другой есть аналогичный тип. Но, хотя внешне они аналогичны, внутренняя реализация и структура хранения данных может отличатся. Все прекрасно работает пока вы собираете проект одним компилятором в одной среде. Когда одна библиотека собрана в одной среде, а другая в другой – начинаются большие проблемы.

Я когда-то подключал библиотеки OCCI (это нативный интерфейс С++ к Ораклу) к проекту на VS2005. Потратив несколько дней на шамансто с отключением одних дефолтных системных библиотек и подсовыванию нужных, наступав параллельно на кучу граблей плюнул на это дело, и перешел на OCI (интерфейс на С)…

Поэтому, как по мне, «внешний» API интерфейс для плагинов лучше делать на С (ну или хотя бы без торчащих наружу STL и прочих шаблонных типов), а уже внутри можно использовать все что душе угодно.
0
Я о том же. Хотя Audiere с С++-интерфейсом к дельфи подключали вполне успешно.
Еще один метод сделать ООП-подобный API для плагинов — COM-интерфейсы.
0
COM-интерфейсы это удобно, независимо от ЯП и т. д. Но их реализация (по крайней мере на С++) не так тривиальна как хотелось бы.
0
|Улыбнуло слово "_extansion_" :) Ни тебе «extension» ни «expansion»… Это наверное что-то между :)
+2
  • avatar
  • wowa
  • 16 апреля 2015, 23:37
да, очень смешно
0
А LabVIEW для этих целей не подходит?
+1
Только стоимость LabVIEW заставляет волосы двигаться на голове.
0
Подходит, если бюджет позволяет. Вы можете его свободно распространить, допустим, на 10 компов предприятия?
0
По поводу дизайна. Так лучше?

Это конкретно оптимизировано под умный дом (ну, частично). Лампочка и вентилятор кликабельны, вентилятор крутится когда включен.
0

Посмотри лучше на щит управления Р-36М.
0
Не, эт не красиво)))
0
Я солидарен с Lifelover, что картинка не очень удачная, но его пример еще хуже ;), поставлю индустриальный компакт диск
+1
Тоже уродливая скада. Что за active/inactive? Нужно по человечески написать — «включено», «выключено», «открыто», «закрыто», «авария».
Почему не сделать в качестве индикатора аварии простой прямоугольник с текстом типа «авария вентилятор приток», если есть авария — красный мигающий, нет — серый. Логично, наглядно.
Зачем эти фотографии моторов? Существуют стандартные графические обозначения. Состояние узла должно быть обозначено цветом. И не каким попало — на этот счёт даже у буржуев были рекомендации, довольно логичные притом. Саму мненмосхему лучше рисовть без пестроты, чтобы сигналы было видно чётко.
Ну и т.д. Зато градиентные заливки, закруглённые уголки, глянец и блеск — это мы можем.
0
Но по крайней мере, это куда лучше сабжа топика :)
0
язык английский поэтому и активе/инактив с русификаторм всо ок ;)- мне он тока мешает поэтому не ставлю. По поводу фотореалистичности тут мнения и стандарты разделяются — у нас и америки нет жестких требований здесь, у немцев был жесткий батхерт но я не видел немецкой библиотеки значит и им приятно. Идем далее вот был телефон с телефонисткой, дисковый, кнопочный, щас смартфон с цветным экраном ну вы меня понели ;). И я бы не сталвсе списывать на блеск и глянец — есть начто смотреть приятно есть на что неприятно.
0
Чтобы получить одобрение Lifelover'а, следует оформить в стиле Windows 2000.
Я, впрочем, тоже не одобрю, хотя против современного дизайна ПО особо ничего не имею. Дизайн — это, все же, не просто натыкать пяток не сочетающихся картинок.
+1
Коллега, мой вам совет: найдите дизайнера (а лучше дизайнершу, угостите ее хорошим вином :) и попросите объяснить базовые принципы. Это не сделает Вас дизайнером за пару часов, но появиться общее представление о дизайне.

У вас:
— жуткая цветовая гамма (очень яркие и несочетающиеся цвета).
— нет общей стилистики (лампочка, кнопки плеера, часы – они плоские с эффектом объема за счет бликов, не этом фоне очень вырвиглазно смотрится псевдореалистичный 3Д вентилятор)
— у вас на форме всего 4 надписи, но для их отображения вы использовали 3 разных шрифта (что-то типа Times New Roman, Arial, Comic).

Идея в том, что если Вы не уверенны в себе как дизайнер – старайтесь использовать одну (и довольно блеклую) цветовую гамму, однотипные изображения (например иконки из одного набора), и одинаковый нейтральный шрифт (допустим Arial). Возможно, при этом, результат будет «скучным», но это лучше, чем «вырвиглазный»
+1
Я же писал, что сейчас дизайном не занимаюсь. Когда проект будет приносить прибыль, я обязательно найду дизайнера, который украсит программу, но в ближайшее время монетизировать проект не планирую, так как хочу сначала его полностью доделать и протестировать. Те, кто сейчас использует мою прогу, могут сами настроить вид по вкусу, как настроить виджеты, рассказано в видосе, а стиль самой проги (цветовая гамма, шрифт) можно настроить в файле style.css.
Над проектом я работаю больше 100 часов в неделю, где найти время для работы над дизайном проги, я даже не представляю.
На работе (с которой год назад уволился) шеф тоже делал большой акцент на дизайне проги, я бы даже сказал, основной. Прога получилась красивой, но со скрытыми глюками, ибо время, которое правильней было бы использовать улучшение работоспособности программы, ушло на проработку дизайна. Мне тогда это надоело и через три месяца уволился нах.
Текущий дизайн программы позволяет её нормально протестировать, поэтому лично для себя его оставлю.
0
Над проектом я работаю больше 100 часов в неделю, где найти время для работы над дизайном проги, я даже не представляю.

Коллега, а Вас понимаю. Но мой совет остается в силе. Как разработчик GUI приложений, вы ведь все равно тратите какое-то время на дизайн формы. Пара простых правил дизайна UI – и созданные формы будут смотреться лучше и удобнее не требуя от вас дополнительных затрат времени.
Текущий дизайн программы позволяет её нормально протестировать, поэтому лично для себя его оставлю.
Хотите тестирования – не жалейте время на написание автоматизированных юнит/UI тестов. Ручное тестирование программы через интерфейс (да еще и в одиночку, самим разработчиком) – штука не благодарная, время затраченное на написание автоматизированных тестов (в сложной программе), поверьте, окупает себя.
+2
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.