software-development
January 11, 2023

За черную консоль замолвите слово..

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

ч0рная консоль, с циферками. Та самая.

А будущее выглядит как-то так:

Воображаемый но желаемый нейронный интерфейс, те самые провода из башки.

Загадывать на будущее — дело неблагодарное, поэтому предсказывать развитие событий не берусь.

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

Поехали.

Даже вам это не сломать.

Круглое как шар, надежное как лом

Шанс сломать консольную программу, а тем более саму консоль действиями пользователя примерно равен шансу сломать металлический шар.

То есть это конечно возможно технически, но: 

очень непросто.

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

Вот так выглядит код простейшей консольной программы на Си:

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
    char str[20];
    printf("Строку введи да: ");
    fgets(str, 20, stdin);
    printf("%s\n", str);
    return 0;
}

Все что она делает это ожидает ввода и затем отображает введенную строку:

Вся логика работы строго последовательная, как и у большинства консольных программ.

Вывод текста:

printf("Строку введи да: ");

Ввод:

fgets(str, 20, stdin);

Вывод:

printf("%s\n", str);

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

Поэтому большую часть исходного кода консольной программы всегда будет составлять прикладная логика:

алгоритмы, расчеты, обработка данных и так далее.

Та самая бизнес-логика, за реализацию которой платят деньги.

Теперь тоже самое, но с графическим интерфейсом (С++ и Qt):

#include <QApplication>
#include <QLabel>
#include <QWidget>
#include <QLineEdit>
#include <QVBoxLayout>
class МойВиджет : public QWidget{
    private:
        QLabel* вывод;
        QLineEdit* вводТекст;
    public:
    МойВиджет(QWidget *parent = NULL) : QWidget(parent) {
        this->resize(320, 40);
        this->setWindowTitle("Страх и ужос");
        QVBoxLayout *layout = new QVBoxLayout( this );
        layout->setMargin( 0 );
        QLabel* введиМеня = new QLabel("Введи текст да:", this);
        layout->addWidget( введиМеня );
        вводТекст = new QLineEdit( this );
        вводТекст->setPlaceholderText("вот сюда");
        вводТекст->setFocus();
        layout->addWidget( вводТекст );
        вывод = new QLabel("", this);
        layout->addWidget( вывод );
        connect(вводТекст, &QLineEdit::textChanged,
                this, &МойВиджет::customSlot);
       }
       void customSlot() {
                вывод->setText(вводТекст->text());
       }
};
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    МойВиджет widget;  
    widget.show();
    return app.exec();
}

Размер кода как видите сразу утроился.

Вот как это выглядит:

Не надо фокусироваться на QT или разнице между Си и C++, неважен язык или графический фреймворк:

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

Теперь давайте более детально разберем второй пример, я постарался сделать его максимально наглядным.

Посмотрим, что там внутри есть.

Простейшие. Холст, масло, микроскоп.

Графические примитивы

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

Вот эти ребята:

#include <QLabel>
#include <QWidget>
#include <QLineEdit>
#include <QVBoxLayout>

Да, как вы догадались, все это — объекты, поэтому графический интерфейс это всегда ООП, пусть хоть в извращенном виде (привет Cocoa), но все-таки ООП.

Работая с интерфейсом — вы всегда работаете с объектами.

Таков путь.

Хоть в функциональном стиле, с лямбдами:

#! /usr/local/bin/wish8.5
button .b -text 0 -command {.b config -text [expr {[.b cget -text]+1}]}
pack   .b 

Хоть через struct в кристально чистом Си:

  window = gtk_application_window_new (app);
  gtk_window_set_title (GTK_WINDOW (window), "Window");
  gtk_window_set_default_size (GTK_WINDOW (window), 200, 200);

  button = gtk_button_new_with_label ("Hello World");
  g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);
  gtk_window_set_child (GTK_WINDOW (window), button);

Общий подход к работе будет все равно из ООП.

Связь между графическими элементами, вид изнутри.

Сложная логика настройки и связывания

Имея дело с графическим интерфейсом, в проекте всегда будет огромная жопа гора кода, отвечающая за его настройку:

связывание и расположение элементов, размеры, отступы, раскраска и так далее.

В примере выше это выглядит вот так:

 this->resize(320, 40);
 ..
 layout->setMargin( 0 );
 ..
 layout->addWidget( вводТекст );
 ..
 вводТекст->setFocus();
 ..

Вот тут более объемный пример из официальной документации.

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

А вам нужно работать и решать прикладные задачи вашим софтом.

Поэтому далеко не всегда такие затраты на создание интерфейса осмысленны и разумны в экономическом плане.

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

События

Ну куда же без них родимых:

  connect(вводТекст, &QLineEdit::textChanged,
                this, &МойВиджет::customSlot);
       }
       void customSlot() {
                вывод->setText(вводТекст->text());
       }

Графический интерфейс подразумевает интерактивное взаимодействие с пользователем:

движение мышкой, клики, перетаскивания блоков, скрытие-раскрытие окон и так далее.

Именно для этого его придумали и развивали.

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

Вот так это начиналось:

Самый первый графический интерфейс

Хороший пример это перетаскивание мышкой окна:

навели курсор на шапку окна, зажали мышью и потащили. Пока тащите - окно визуально перемещается по экрану, т.е. работает и ввод и вывод.

Выглядит как-то так:

Знали бы вы как это все работает.

Теперь представьте сложность происходящего:

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

Не обосраться при разработки софта с графическим интерфейсом очень и очень сложно, даже если вы большая компания с бесконечными ресурсами.

Поэтому заканчивается временами вот так:

Знаменитая заливка, попавшая на заставку сериала IT Crowd

Проблемы

Так что мы получим в обязательном порядке, связавшись с графическим интерфейсом?

Проблемы конечно, кучу проблем.

Первая картинка по слову leak

Память, которая обязательно будет течь.

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

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

Смиритесь и примите как факт:

Все графическое и интерактивное - течет.

Такова цена.

Риск ошибки

Сама концепция графического интерактивного интерфейса подразумевает несколько разных вариантов ввода:

клавиатура и мышь или разнообразные движения пальцем в случае планшета

и вывода:

текст, графика, анимация, звуки

И все это работает одновременно. Естественно это порождает ошибки:

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

Думаю и так очевидно:

чем больше кода в вашем проекте тем больше шанс на ошибки в нем.

Существуют даже исследования, изучающие соотношение между объемом кода, уровнем программиста и количеством ошибок, которые он делает в день:

https://en.wikipedia.org/wiki/Software_metric

https://en.wikipedia.org/wiki/Software_bug#Benchmark_of_bugs

И надо сказать что соотношение там точно не в пользу ПО с графическим интерфейсом.

Хотя это еще далеко не все, на одном обьеме ваши проблемы на пути в графический рай не заканчиваются.

Потому что на свете есть:

Знаменитый UAC из Windows 7

Контроль ввода

Дело в том что окружение программы с графическим интерфейсом — тоже графическое (сюрприз). А значит содержит все те же сложности и ошибки в реализации, тестировании и тд, только умноженные на сто: окружению еще нужно уметь контролировать ваши программы.

Вот что бывает когда у окружения это не получается:

Windows 98, эпичная эскалация привилегий.

Не грози консольному централу, возюкая мышкой в своем интерфейсе

За 40 лет развития графических интерфейсов, с появления Apple Lisa и до нынешних 3D-шлемов с виртуальной реальностью, фундаментальные проблемы графического интерфейса не то что не были решены — они умножились.

И то что подавалось и продавалось нам как однозначный прогресс и хайтек, одновременно создало вокруг индустрии ПО ареол ненадежности.

Слышим «программный» подразумеваем «ненадежный».

Глюк, баг, зависнуть — это теперь часть лексикона даже очень далеких от компьютеров людей.

Можете проверить сами, попросив ваших пожилых родителей описать смысл фразы «компьютер завис» — результат удивит.

Вообщем единственным реальным вариантом создания чего-то по-настоящему надежного является разработка консольного ПО с текстовым интерфейсом.

Как это было и 40 и 60 лет назад и будет следущие 100.

Вот вам и «дедовское легаси».