Проект
Комму – учись на ошибках
Портал для общения внутри компании. Написано в конце второго курса и доработано в начале третьего.

Идея и цель проекта
Система коммуникации сотрудников «КОММУ» по своей сути была именно учебным проектом во всех возможных проявлениях этого понятия.
Началось всё в конце второго курса. Тогда в планах система ещё пафосно называлась СУБ, она же «Система управления бизнесом», но, не выдержав конкуренции с великим и ужасным «Битрикс24», решила слегка умерить амбиции и переквалифицироваться в систему коммуникации сотрудников.
Примерно тогда же был выбран первоначальный стек разработки, представленный в таблице 1.
Таблица 1. Первоначальный стек разработки «КОММУ»
| Компонент | Назначение |
|---|---|
| MariaDB | Реляционная СУБД для хранения данных системы |
| PyMySQL | Драйвер для прямой работы Python с MariaDB/MySQL |
| Flask | Веб-фреймворк, на котором построена серверная часть приложения |
| Pillow (PIL) | Работа с изображениями: создание, обработка и преобразование графики |
| qrcode | Генерация QR-кодов |
| secrets | Генерация криптографически стойких случайных значений, токенов и прочих вещей, которые лучше не получать через random() |
| Bleach | Очистка пользовательского HTML от потенциально опасного содержимого |
| Markdown | Преобразование Markdown-разметки в HTML |
Только недавно сдавшему экзамен по «Базам данных» мне казалось, что библиотеки объектно-реляционного отображения данных (ORM) являются «излишней тратой времени».
Да. Это была ужасная ошибка новичка.
Именно благодаря этому гениальному архитектурному решению мои проекты до сих пор содержат SQL-запросы, записанные под именованными ключами™. Фактически это самодельный промежуточный слой между приложением и базой данных, который появился исключительно потому, что когда-то мне показалось хорошей идеей не использовать уже существующий промежуточный слой между приложением и базой данных.
Но об этом преступлении против здравого смысла поговорим позже.
Дизайн
С чего вообще может начать веб-разработчик?
Вопрос неоднозначный, ведь если идти по этапам, то МЫ начинаем с предпроектной стадии, затем переходим к проектированию, затем к реализации…
Ах! Восхитительная вещь этот жизненный цикл программного продукта! Сколько схем, сколько мнений, и все почему-то уверены, что именно ИХ каскадная / спиральная / капиталистическая модель есть истина в последней инстанции…
Вы так не думаете?
А я думаю, что это стоит разобрать в отдельной статье, пока очередная методология не успела обзавестись манифестом, сертификацией и консультантами по её правильному внедрению.
Сегодня же мы сразу перейдём к дизайну.
Точнее, Я буду разбирать дизайн, а Вы постараетесь не потерять нить рефлексии ученика, который спустя время вернулся к собственному проекту и теперь вынужден выяснять, почему он вообще сделал именно так.
Архаичный разработчик
Будучи ещё не до конца сформировавшимся специалистом в области дизайна, я совершил очередную непростительную для учебного проекта ошибку: не стал использовать Bootstrap.
Непростительный шаг! — скажете вы.
Ужасная трата времени для учебного проекта! — добьёте вы.
Юношеский максимализм и боязнь неизведанного, — отвечу я.
Чем именно закончилась эта замечательная попытка изобрести фронтенд самостоятельно, можно увидеть на изображениях 1–3.

Рисунок 1. Комму.Регламенты написанные на чистом CSS

Рисунок 2. Очередное творение CSS безумца. Страница авторизации Комму

Рисунок 3. Страница новостей в ужасном дизайне! Почему у новости есть «владелец файла» неизвестно. Логика была слишком сложной в то время…
К счастью, в тот момент я ещё не успел окончательно загрязнить репозиторий подобными творениями, поэтому ранний дизайн системы сохранился исключительно на скриншотах внутри проклятых файлов с названиями в духе «Курсова Работа Проектирование.финал.финал.docx».
Возможно, это и к лучшему. Некоторые вещи должны оставаться погребёнными в документах времён колледжа, чтобы случайный git blame однажды не уничтожил остатки самоуважения автора.
Впрочем, долго этот кошмар не продлился. Ошибка была достаточно быстро осознана, после чего система полностью переехала на готовые CSS-фреймворки.
Как выяснилось, разработчики всё-таки не зря потратили десятилетия на создание готовых компонентов интерфейса.
Конвейерный девелопер

Рисунок 4. Наглядная демонстрация различий между древностью и конвейером
Пройдя все стадии принятия, было решено, что интерфейс Windows 98 (иначе я не могу назвать то стилистическое решение, которое когда-то было выбрано при написании нативного CSS) всё-таки устарел примерно в 1999 году. Ну, на крайняк, в 2000-м.
Следовательно, великую «КОММУ» необходимо было переписать с использованием современных фреймворков, коих запертые в подвалах программисты за прошедшие годы наплодили вагон и ещё примерно две сотни тележек.
В качестве оного был выбран Bootstrap, на который как раз была ориентирована учебная программа. Решение не самое оригинальное, зато теперь не требовалось собственноручно объяснять каждому <button>, как именно ему следует выглядеть. С примером Bootstrap интерфейса можно на рисунке 5 и на официальном сайте в разделе Examples→Snippets.
Рисунок 5. Наглядный пример того как выглядит типичный Bootstrap интерфейс
Отходя от избитых шуток про жалкое конвейерное производство (или избитого меня Вами за столь рваное повествование), мне бы хотелось напомнить об одной важнейшей детали, которую нельзя забывать при создании практически любого интерфейса: психологии.
Люди (не смотря на то, что это слово не является сказуемым я бы подчеркнул его двумя палочками, но великий WordPress не позволяет ;-;) обладают удивительно типичными паттернами поведения. Благодаря им пользователь зачастую понимает назначение элемента ещё до того, как прочитает написанный на нём текст.
Мы ориентируемся на цвет, форму, иконку, расположение и уже знакомые визуальные образы. Интерфейс при этом как бы заключает с пользователем негласный договор: если элемент выглядит знакомо, то и вести себя он должен знакомым образом.
Например, кнопки:
Даже если убрать половину подписей, общий смысл остаётся довольно предсказуемым. Яркая основная кнопка привлекает внимание и обозначает главное действие. Вторичная старается не мешаться под ногами. Красная недвусмысленно намекает, что после нажатия может произойти нечто, о чём пользователь через три секунды напишет в техническую поддержку.
Цвет, разумеется, не должен быть единственным способом передачи смысла: существуют нарушения цветового восприятия, особенности зрения и банально разные экраны. Поэтому хороший интерфейс подкрепляет цвет текстом, формой, иконкой или контекстом.
Булочка, сосиска, булочка
Другой великолепный результат многолетней дрессировки пользователей — бургер-меню.
Булочка. Сосиска. Булочка.
Три горизонтальные полоски сами по себе не имеют никакого врождённого значения. Однако за годы существования мобильных интерфейсов пользователи привыкли: увидел три полоски где-нибудь в верхнем углу — скорее всего, там меню.
Причём важна не только иконка, но и ожидаемое расположение. Логотип обычно ищут сверху, навигацию рядом с ним или за бургером, поиск — в шапке, настройки — за шестерёнкой, закрытие окна — за крестиком. Пользователь не должен каждый раз исследовать интерфейс как археолог, пытающийся восстановить назначение загадочной пиктограммы исчезнувшей цивилизации.
И здесь появляется важный принцип: предсказуемость зачастую полезнее оригинальности.
Конечно, существуют региональные и культурные различия. В некоторых регионах планеты Земля, особенно в азиатских странах вроде Японии и Китая, исторически сформировались заметно отличающиеся подходы к плотности интерфейсов, навигации и количеству одновременно отображаемой информации. Поэтому универсального «психологически правильного интерфейса для всего человечества» не существует.
Но внутри конкретной аудитории знакомые паттерны всё равно работают. Если пользователь тысячу раз видел крестик как «закрыть», корзину как «удалить», шестерёнку как «настройки», а бургер как «меню», заставлять его на тысяча первый раз разгадывать авторскую метафору обычно бессмысленно.
Ух! Это было восхитительное мини-эссе, не правда ли?
А я, кажется, знаю, что вы теперь хотите сделать.
НЕТ! НЕ ЗАКРЫВАЙТЕ САЙТ!
Я не об этом.
Я просто хотел пошутить, что на этом сайте (великий и прекрасный r.zezh.ru) меню сделано не в виде бургера, а в виде плюсика.
Да.
Действительно.
Зачем тогда я только что описывал всю эту психологию? Потому что фреймворки убирают с разработчика обязанность тратить сотни тысяч рублей на исследования — они, зачастую, уже оптимизированы под эти паттерны.
От теории к практике
Итак, вооружившись Bootstrap, знаниями о психологии интерфейсов и внезапным осознанием того, что не каждый элемент обязан иметь серую рамку с закруглением, можно было приступать к переделке.
Для начала предлагаю ещё раз взглянуть на то, с чем мы имели дело изначально. Можете снова посмотреть на Рисунок 3.
При всех издевательствах над старым дизайном стоит признать одну вещь: его основная компоновка была вполне удачной.
Слева располагалось основное меню системы, сверху — логотип, строка поиска и элементы управления учётной записью, а всё оставшееся пространство отдавалось непосредственно содержимому страницы. Поэтому при переходе на Bootstrap я не стал сносить всё до фундамента исключительно ради возможности торжественно назвать произошедшее редизайном.
Эта раскладка сохранилась.
Более того, впоследствии она станет практически стандартной для моих личных проектов. Очень похожую организацию интерфейса можно будет встретить в PLACS и WP, статьи о которых появятся позже. Видимо, в какой-то момент я случайно изобрёл собственное представление об удобстве и с тех пор отказываюсь от него избавляться.
Причина довольно прозаична: для настольного интерфейса такая схема чертовски удобна. Основная навигация всегда находится слева и остаётся перед глазами, глобальные элементы вынесены наверх, а центр экрана полностью принадлежит текущей задаче. Пользователю не приходится гадать, где он находится и куда исчезло меню после очередного перехода.
Изменять требовалось не столько расположение, сколько визуальную иерархию элементов внутри него.

Рисунок 6. Обновлённый интерфейс просмотра новости
На рисунке 6 это особенно хорошо заметно. Старый интерфейс старался показать всё одновременно и примерно с одинаковой настойчивостью. Рамка здесь, рамка там, ещё одна рамка внутри предыдущей рамки. Поля ввода, карточки, кнопки и панели отличались друг от друга настолько незначительно, что пользователю приходилось сначала читать интерфейс, а уже потом им пользоваться.
В обновлённой версии появляется нормальная визуальная иерархия.
Заголовок новости крупнее обычного текста. Сама новость находится в отдельном визуальном блоке. Информация об авторе вынесена в собственную карточку. Основное действие «Вернуться на главную» выделено синим цветом, тогда как второстепенное «Вернуться назад» остаётся серым.
То есть интерфейс начал сообщать пользователю не только что здесь находится, но и насколько это важно.
Это небольшая разница на уровне CSS и огромная на уровне восприятия.
Вместо десятка одинаково выглядящих элементов взгляд получает несколько точек, за которые можно зацепиться. Пользователь сначала видит заголовок и содержимое, затем информацию об авторе и уже после этого элементы навигации. Именно так и должна работать визуальная иерархия: не заставлять человека сознательно разбирать страницу на составляющие, а делать это за него.

Рисунок 7. Интерфейс просмотра содержимого файла
Та же логика используется и на странице файла. Имя файла, отправитель и дата собраны в одном информационном блоке, после которого сразу расположены доступные действия.
Причём действия снова различаются визуально: «Скачать файл» является основным и поэтому выделено синим, а переход в мессенджер и возврат на главную являются вспомогательными и не пытаются перетянуть внимание на себя.
Отдельно помогают иконки. Стрелка загрузки, сообщение и домик позволяют распознать назначение кнопок ещё до чтения текста. Разумеется, подписи всё равно оставлены, потому что превращать корпоративную систему в экзамен «угадайте значение пиктограммы» никто не собирался.
Но лучше всего преимущества новой системы компонентов видны там, где интерфейс становится действительно сложным.

Рисунок 8. Интерфейс мессенджера «КОММУ»
Мессенджер одновременно содержит список чатов, текущую переписку, участников, сообщения, вложения и поле ввода. В старой стилистике всё это довольно быстро превратилось бы в братскую могилу серых прямоугольников.
Здесь же каждая область имеет понятную функцию и своё место.
Список диалогов закономерно находится слева, текущая переписка занимает большую центральную область, информация о выбранном чате и его участниках вынесена вправо и может быть скрыта нажатием на информационную кнопку i. Собственные сообщения выделены синим и прижаты к правой стороне, благодаря чему переписку можно визуально разделить на «я» и «остальные», даже практически не читая её.
Поле ввода при этом находится внизу, то есть ровно там, где пользователь ожидает найти его после примерно любого мессенджера, созданного человечеством за последние двадцать лет.
И вот здесь проявляется главное преимущество перехода на готовый фреймворк.
Bootstrap не сделал интерфейс хорошим автоматически. Если достаточно постараться, отвратительный интерфейс можно написать на чём угодно, и люди ежедневно героически это доказывают. Я не шучу! Есть целая ветка Reddit, посвящённая этому, и мини-игра под названием User Inyerface за авторством бельгийской студии Bagaar. Кстати, советую поиграть, если не хотите заставлять пользователей страдать. А если не поиграете, есть второй путь: можете, как я, самостоятельно разработать говно из Windows 98, посмотреть на него спустя год и уже на собственном опыте понять, зачем специалисты последние лет тридцать изучали UX и его психологию. Тоже работает, но первый вариант несколько дешевле.
И так, вернёмся к фреймворку. Он дал готовый и, что гораздо важнее, последовательный визуальный язык: одинаковые отступы, состояния кнопок, карточки, формы, цвета действий и понятную иерархию компонентов. Благодаря этому страницы, выполняющие совершенно разные задачи, всё равно воспринимаются частями одной системы.
Старый интерфейс требовал от пользователя изучить, как устроена «КОММУ». Новый интерфейс в гораздо большей степени позволял использовать уже имеющиеся знания о том, как вообще принято пользоваться современными приложениями.
не автор старого интерфейса (автор старого интерфейса)
И именно это, пожалуй, было главным результатом редизайна. Не красивые кнопочки. Не модные скругления. И даже не избавление от эстетики Windows 98.
Пользователю наконец перестали мешать пользоваться системой.
Помимо кнопок?
Интересное название главы, не правда ли? Интрига, поиск скрытого смысла автора, попытка понять, что же он хотел этим сказать…
Нет. Этого не будет.
Увы и ах, но мы переходим к той части проекта, которую мне так и не удалось отрефлексировать в полной мере. Почему? Потому что принцип «не мешай пользователю пользоваться» в моей системе координат почему-то оказался значительно выше принципа «не мешай системе быстродействовать».
Чем это закончилось?
Как минимум периодическими зависаниями, неоптимальными запросами и несколько неправильно настроенным кэшированием, которое уже никогда не будет исправлено. Не потому, что исправить невозможно. И даже не потому, что я не знаю, как это исправить сейчас. Просто учебные проекты учебными проектами, а двигаться дальше всё-таки нужно.
Запомни, читатель! В какой-то момент рефакторинг старого кода перестаёт быть обучением и превращается в археологическую реставрацию собственного говна. Пользы всё меньше, кисточка всё мельче, а впереди уже лежат проекты, в которых эти ошибки можно просто не совершать заново.
База данных
Как уже стало известно из главы «Идея и цель проекта», к тому моменту мною лишь недавно был закрыт экзамен по предмету «Проектирование и разработка баз данных». Ещё не успевшие окончательно уложиться в голове нормальные формы, различия между реляционной и иерархической моделями и прочие прекрасные плоды многолетнего развития теории баз данных породили дьявольскую структуру, чья основная задача формулировалась примерно как «хранить все данные пользователей в одном месте».
Это была ошибка. Ужасная ошибка.
Думаю, стоит начать с малого. Юные разработчики! Прильните к экрану и проанализируйте следующий фрагмент:
CREATE TABLE `employees` (
`EmployeeID` int(11) NOT NULL,
`UserLogin` varchar(100) NOT NULL COMMENT 'Логин ЛК сотрудника',
`UserPassword` varchar(100) NOT NULL COMMENT 'Пароль ЛК сотрудника',
`FirstName` varchar(100) NOT NULL COMMENT 'Имя сотрудника',
`LastName` varchar(100) NOT NULL COMMENT 'Фамилия сотрудника',
`PositionID` int(11) NOT NULL COMMENT 'Идентификатор роли, к которой относится сотрудник',
`ManagerID` int(11) DEFAULT NULL COMMENT 'За каким менаджером закреплен сотрудник?',
`MobileNumber` varchar(15) NOT NULL COMMENT 'Мобильный телефон сотрудника',
`CorporateNumber` varchar(15) NOT NULL COMMENT 'Корпоративный номер сотрудника',
`BirthDate` date NOT NULL COMMENT 'День рождения сотрудника',
`PhotoLastUpdated` timestamp NULL DEFAULT current_timestamp(),
`AboutMe` text DEFAULT NULL COMMENT 'Сотрудник пишет что-то о себе',
`StartDate` date NOT NULL COMMENT 'Дата создания личного кабинета',
`Photo` longblob DEFAULT NULL COMMENT 'Содержимое изображения',
`IsDismissed` tinyint(1) NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
Уже здесь прекрасно видно стремление запихнуть в одну сущность всё, что хотя бы отдалённо относится к сотруднику: данные учётной записи, персональную информацию, организационные связи и даже фотографию. Само по себе большое количество полей ещё не означает нарушение нормальных форм, однако подобная структура быстро превращается в неудобный монолит и связывает данные, жизненные циклы которых совершенно не обязаны совпадать.
Но особенно прекрасна строка Photo с типом данных LONGBLOB. Да, изображение пользователя хранится прямо в основной таблице сотрудников. Решение не является запрещённым древними законами SQL и в отдельных системах вполне оправданно, но в данном проекте оно лишь раздувало таблицу и усложняло работу с данными там, где можно было хранить файл отдельно, оставив базе его идентификатор или путь.
И это, к сожалению, не единичный приступ архитектурного вдохновения. Рассмотрим, например, устройство мессенджера:
CREATE TABLE `chatmember` (
`MemberID` int(11) NOT NULL COMMENT 'Уникальный код участника',
`EmployeesID` int(11) NOT NULL COMMENT 'Идентификатор пользователя',
`ChatID` int(11) NOT NULL COMMENT 'Идентификатор чата'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
CREATE TABLE `chats` (
`ChatID` int(11) NOT NULL COMMENT 'Уникальный идентификатор чата',
`ChatPreview` mediumblob DEFAULT NULL COMMENT 'Иконка чата',
`ChatName` tinytext NOT NULL COMMENT 'Имя чата'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
CREATE TABLE `message` (
`MessageID` int(11) NOT NULL COMMENT 'Уникалльный код сообщения',
`ChatID` int(11) NOT NULL COMMENT 'Идентификатор чата',
`SenderID` int(11) NOT NULL COMMENT 'Идентификатор пользователя-отправителя',
`SendDate` timestamp NULL DEFAULT NULL COMMENT 'Дата отправки',
`ContentType` int(11) NOT NULL COMMENT 'Тип отправляемого контента',
`IsHidden` tinyint(1) NOT NULL DEFAULT 0,
`ReplyToMessageID` int(11) DEFAULT NULL COMMENT 'В ответ на определённое сообщение',
`ProcessingCompleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Хранит состояние обработки сообщения на сервере',
`ProcessingHEXCode` varchar(16) NOT NULL COMMENT 'Уникальный код обработки, существует только во время этапа обработки сообщения'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
CREATE TABLE `messagecontenttype` (
`TypeID` int(11) NOT NULL COMMENT 'Уникальный код типа сообщений',
`Name` tinytext NOT NULL COMMENT 'Кодовое название типа сообщения. Нужно для правильной генерации внешнего вида'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
CREATE TABLE `messagewithfile` (
`FileID` int(11) NOT NULL COMMENT 'Идентификатор файла',
`File` longblob NOT NULL COMMENT 'Содержимое файла',
`FileName` tinytext NOT NULL COMMENT 'Имя файла',
`MessageID` int(11) NOT NULL COMMENT 'Идентификатор сообщения, к которому прикреплён файл'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
Помимо гениальных комментариев вроде «Нужно для правильной генерации внешнего вида», здесь можно заметить ещё одну характерную особенность архитектуры: это BLOB на BLOB’е и BLOB’ом погоняет. Иконка чата? MEDIUMBLOB. Прикреплённый файл? LONGBLOB. Фотография сотрудника из предыдущего примера? Разумеется, тоже LONGBLOB. Если данные существуют в виде последовательности байтов, значит, по мнению тогдашнего меня, им самое место непосредственно в MariaDB.
Впрочем, гораздо интереснее таблица message. Поскольку логика мессенджера и без того казалась нам недостаточно сложной (см. рисунок 3 на нём отчётливо видны проблемы с разумностью), в ней существуют замечательные поля ProcessingCompleted и ProcessingHEXCode. Они обслуживали отдельный механизм отслеживания создаваемого сообщения и были нужны исключительно потому, что я не знал о существовании lastrowid и вместо получения идентификатора только что вставленной записи построил вокруг проблемы собственную систему.
Да, проблема уже была решена драйвером базы данных. Мне оставалось воспользоваться готовым решением. Вместо этого я добавил два поля, дополнительное состояние обработки и шестнадцатеричный код.
Слава богу, мне уже не придётся переписывать этот проект.
Если после увиденного у Вас всё ещё осталось нездоровое желание подробнее ознакомиться с базой данных «КОММУ», ниже я прикреплю PDF-документ со словарём данных и диаграммой «сущность–связь». Там подобных решений значительно больше.
И да, мне не стыдно, поверьте. Стыдно должно быть не за старый плохой код, а за неспособность понять, почему он был плохим. В конце концов, смысл учебного проекта именно в том, чтобы спустя время открыть его и с некоторым ужасом обнаружить, что обучение действительно сработало.
Что это было?
Если подводить итог, то великая «КОММУ» была совсем не плохим учебным проектом. Именно с неё начался тяжкий и долгий путь к тому, что видите Вы сейчас.
Именно после неё я начал понимать, что такое айдентика, как работает психология пользователя и почему бизнес-система почти всегда синяя…
Что? Вам стало интересно, почему синяя?
Нет, не стало?
Так или иначе, позже в блоге появится отдельная статья, целиком и полностью посвящённая реалиям современной айдентики. Там и разберёмся, почему половина корпоративного мира однажды посмотрела на синий цвет и коллективно решила: «Да. Вот так выглядит доверие».
«КОММУ» действительно умела многое, и я горжусь этим проектом. Со всеми её LONGBLOB, странными архитектурными решениями, самодельными механизмами обработки сообщений и интерфейсом родом из прошлого тысячелетия.
Для парня, который только начинал по-настоящему программировать, она стала идеальным полигоном для проверки собственных сил. Здесь можно было ошибаться, переделывать, изобретать уже изобретённое, не знать о существовании lastrowid, а через несколько лет открыть исходный код и наконец понять, почему некоторые вещи лучше было не изобретать вовсе.
И всё же Ваш покорный автор справился.
Если Вам по какой-то причине оказалось мало этих слов, ниже можно ознакомиться со страницей презентации системы, скриншот которой также украшает эту статью. Там «КОММУ» сохранилась именно такой, какой я видел её тогда, ещё до того, как научился смотреть на собственные проекты глазами человека, которому потом придётся ими пользоваться.
Спасибо за внимание. На странице блога Вы можете почитать и другие размышления на свободные темы.
