Правила разработки сложных систем. История одного проекта

в 11:15, , рубрики: c#.net, архитектура системы, Программирование, проектирование, Проектирование и рефакторинг, проекты, метки: , , ,

Привет. Меня зовут Александр. И я хочу поделится своей историей работы над одним крупным и сложным проектом.

В этой статье не будет кода и схем, в ней будет только история создания «от и до» самого проекта. Думаю, многим будет интересна данная статья. Итак, поехали!

Начало

Все началось летом 2011. На тот момент я был 3 года чистокровным фрилансером. То есть моя работа — это фриланс. Работал и работаю до сих пор только с западными заказчиками. Основная специализация — разработка проектов связанных с распознаванием образов, текста и т.д.

Все началось с того, что я, как всегда, с утра проверял почту, чистил спам, занимался рутинной работой. Обычно я не смотрю, что у меня в спаме, но тут я увидел письмо, с вполне реального адреса. Я открыл письмо, в котором одна компания искала программиста для допиливания крупного западного проекта. Причем эта компания требовала программиста именно из моего города и обязательно с опытом работы в области распознавания. Я ради любопытства ответил на это письмо. Буквально через час мне пришел ответ. А через два мы уже созвонились с менеджером проекта. Поначалу мне показалось, что ничего сложного в доработке нет, обычный набор функционала. После непродолжительного разговора с менеджером я огласил свой прайс, то есть ставку в час. И на этом мы попрощались. На следующий день мне сказали, что согласны на мой ценник и дали тестовое задание. Я его успешно выполнил в течении часа, и мы двинулись дальше. А здесь начинается самое интересное. Во-первых, меня пригласили в офис для того, чтобы подписать договор о неразглашении (Non-Disclosure Agreement). Во-вторых, и это логично, исходники проекта мне обещали отдать только после подписания договора. Если честно, меня это смутило, не знаю даже почему. И интуиция меня не подвела. Я потребовал хотя бы часть исходного кода, чтобы оценить сложность работы и попросил рассказать подробнее о проекте. Как оказалось проект на тот момент велся уже три года и я был 4 (!) исполнителем. До меня работала американская компания, потом индусы, потом компания, которая наняла меня, пыталась реализовать проект силами одной девочки-программиста, а потом это все чудо предложили разгребать мне. Меня это не просто удивило, а очень насторожило. Потом я узнал множество удивительных вещей, например о том, что заказчик 2 года не видел программу, а видел только скриншоты, а индусы кормили обещаниями этого заказчика. У меня не укладывалось в голове, как такое можно реализовать. Менеджеру индусов надо дать медаль «За находчивость».

После того как я выслушал удивительную историю, мы договорились с менеджером о том, что он мне отдаст исходный код и я оценю масштаб трагедии. Чтобы было более понятно, я расскажу более подробно о проекте. Этот проект — это инструмент для инженеров, архитекторов, электриков и других людей, которые занимаются строительством домов, небоскребов, одним словом зданий. Он служит для подсчета различных элементов на строительных планах, расчета площадей, измерения длин и составления смет. Грубо говоря есть строительный план и на нем есть розетки. Нам надо распознать и посчитать сколько этих розеток. Для распознавания использовалась библиотека написанная другим программистом. Сам проект написан на C#. Моя задача была собрать все воедино и доработать дополнительный функционал, а также привести программу к более менее стабильному состоянию. Кажется все просто и элементарно. Я тоже так подумал. Но не тут-то было.

После того как я получил исходники, я попытался скомпилировать проект. Это мне не удалось. После краткого анализа, я исправил ошибки и все же запустил проект. Но, к сожалению, он не заработал так как нужно. После нескольких часов анализа кода я пришел к выводу, что вся проблема в библиотеке распознавания. На тот момент у меня стояла 64-битная «семерка», а у менеджера 32-битная. У него все работало, у меня нет. Я попросил, что бы мне скомпилировали библиотеку под 64-битную платформу. Но разработчик библиотеки с пеной у рта доказывал, что не в разрядности дело. Я не мог ему ничего доказать, так как он дал очень немного информации о своей библиотеке и вообще берег ее как зеницу ока. Время шло и мне надо было хотя бы полностью провести процесс поиска. Я плюнул на все и поставил себе 32-х битную версию ОС. И о чудо! Все заработало. Отвлекаясь, хочу сказать о библиотеке, в будущем дело все же оказалось в ее разрядности.

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

1. Компоненты и контролы.

Проект очень сильно связан с графикой, но для ее вывода и обработки использовался обычный PictureBox. Самый маленький размер плана — 5400x3600 пикселей. Знающие люди поймут, что для PictureBox -это достаточно проблематичная тема с выводом больших картинок и их обработкой. Не стоит забывать, что помимо самих планов выводится еще много информации (площади, текст, найденные символы и т.д.). При запуске проекта с 5 маленькими планами, программа непременно падала с ошибкой «Out of memory». Что было очень большой проблемой, так как основной задумкой было то, что инструмент должен был работать по принципу «запустил и забыл».

2. Логика архитектуры исходного кода программы.


Вернее ее отсутствие. Я 6 месяцев потратил на то, что бы понять эту архитектуру, но мне так и не удалось это. Классы связаны не то что непонятно, а просто хаотично. В одном классе могут быть написаны методы, которые отвечают за разные логически не связанные операции. Особенно «порадовал» класс под названием «RubberBand», если я правильно понял, то с английского это переводится как «резинка», хотя может быть это какой-то термин. Я не знаю. Так вот, этот класс представлял собой нечто такое всеобъемлющее. В нем можно было найти методы по работе с БД, графикой, интерфейсом, а также с расчетной частью. С самого начала я подумал, что это некий класс, который используется как черновик. То есть он не используется, а в нем хранят только какие-то наработки. Потому что, на первый взгляд, в нем нет логики, а есть только отдельные куски кода. Но я ошибался — это самый главный управляющий класс.

3. Переменные, методы и комментарии.

Тут все также неоднозначно. Из-за того, что проектом занималось несколько компаний, код был разной стилистики, написания. У меня сложилось впечатление, что именование переменных происходило «от балды». Зачастую, переменные были названы, например так: int rjik, double opgdd. Но это не самое основное. После нескольких дней рефакторинга проблема с переменными была решена.

Далее следуют методы. Тут немного попроще. Хотя нет — не проще. Сложность в том, что многие методы, также названы либо бессмысленно, либо абсолютно не неся никакой смысловой нагрузки. Отвлекаясь, хочу заметить, что когда я разбирал код, у меня сложилось впечатление, что именование происходит по принципу, что вижу, то пишу. Ну, например, вижу стол — именую переменную на индийском «стол».

Особенно запомнился один метод, который именовался «bindingDB(bool Draw)». Он содержал в себе более 800(!) строк кода. Если вы подумали, что он каким-то образом связан с базой данных, то вы, как впрочем и я, ошибаетесь. Я потратил несколько дней, чтобы понять зачем он и разбить его. Этот метод был уникальным. Он вызывался практически из всех других классов. Он отвечал за расчет статистики, вывод графики, взаимодействие с GUI и многое другое. Причем многие строки кода были скопированы с ранее упомянутого класса RubberBand.

Перейдем к комментариям. Комментариев было немного. Большинство из них было несущественными. Предыдущие разработчики оставляли комментарии такого типа: «Тут присваиваем переменной i 0». А в каких-то запутанных местах комментариев попросту не было.

Еще хочу заметить, что было несколько кусков кода, которые в наглую были сворованы с codeproject'a.

4. База данных

Тут плохого ничего не могу сказать про архитектуру. Так как ее проектировали американцы. Но про stored procedures можно написать целую книгу, о том как НЕ надо писать запросы.

5. Клиент-сервер

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

В реальности все выглядело иначе. После формирования проекта на клиенте, он отсылался в очередь на сервере. После попадания в очередь клиент блокировался, до того момента пока не пройдет анализ на сервере. Представьте, есть организация с 5 клиентскими машинами. На каждой из машин сформируется по одному проекту (средний проект — это 10 страниц и 10 искомых картинок). Такой средний проект просчитывается в районе 1 часа. То есть если будет сформировано 5 проектов, то последний проект начнется только через 4 часа. А все эти 4 часа клиент будет заморожен. Индусы по-моему не знают что такое многопоточность.

Решение проблем

На решение всех проблем мне было дано 2 месяца. В действительности это вылилось в 7 месяцев не только переделки, но и доработки нового функционала. В итоге, даже после моего колдовства, мы получили непригодный к работе продукт. Нет, тут дело не в том, что я не смог его залатать, а дело в том, что проект изначально был криво спроектирован, что впоследствии привело к очень плачевному результату. Очень много моментов таких как: мерцание, переполнение памяти, медленная работа и прочее. Попросту невозможно было убрать. И мы с заказчиком решили, что надо переписать проект с нуля.

Новый проект

За месяц я должен был предоставить демо-версию. Которая будет покрывать 30% всего функционала. В феврале я приступил к проектированию и разработке проекта.
Далее я приведу список того, что я отметил для себя при проектировании.

1. Масштабируемость и гибкость

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

2. Технологии

Важный момент любого проектировщика, так как от используемых технологий, зачастую зависит успех и качество разработки. В качестве языка программирования я выбрал C# и фреймворк .NET 4.0. Это было изначально заложено в самом проекте, так что я не стал что-либо менять.

СУБД я выбрал MSSQL 2008 r2, я больше привык работать с МС, поэтому им я доверяю.

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

3. Архитектура БД и классов

Здесь я был предельно внимателен и осторожен при проектировании. Так как я хотел сохранить чистоту кода, легкость понимания и избежать «индийского кода», сначала на листах А4 описал все классы, каждый лист был именован, например «ImageProcessing»,«Rendering», «Reporting» и т.д. Далее эти листы соединялись по логике, к примеру листы с графическими классами соединялись отдельно, от классов для работы с БД. После всех этих манипуляций у меня была получена архитектура классов. Также я поступил с архитектурой БД. Многие могут сказать, что это неправильно, но мне так удобно и комфортно проектировать.

4. Переменные, методы и комментарии

При именовании методов и переменных я старался называть их так, чтобы в дальнейшем не было путаницы и траты времени на понимание значения метода или переменной. У меня в коде нет таких переменных как «a»,«b», «c», так как такое обозначение не несет смысловой нагрузки. Исключением являются переменные i,j — я привык использовать их в циклах. А также переменные x,y,z — это координаты.

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

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

5. Память

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

6. Копипаст

Раньше я этим активно пользовался. Я писал метод или класс. Потом при необходимости я брал и копировал код. Но с данного проекта я принципиально отказался от такого метода. Так как я лучше потрачу 15 минут на написание кода, чем я потрачу час на отладку и баги. Просто зачастую когда мы копируем код, мы подсознательно можем не поменять какие-то вещи в нем, из-за которых могут появляться ошибки.

7. От большего к меньшему

Изначально старайтесь писать какие-то основные вещи, а только потом прикручивайте к ним фишки. Так как если вы начнете писать все разом, то получится «распыление». То есть вы распылите свое внимание и время. И зачастую получается так, что и в основных моментах проблемы и в дополнительных тоже проблемы. Получится «Ни себе, ни людям».

Результат

Результатом моей работы стал продукт, который уже анонсируют на территории США. И через пару дней начнутся его продажи. За четыре месяца я смог разработать продукт, который не смогли за 3 года разработать три предыдущих компании. Я не говорю, что я мегакрутой программист. Я говорю о том, что качество продукта, скорость разработки, исправление ошибок, зависит от тех пунктов, которые я описал выше. И сейчас если сравнить два продукта — новый и старый, то это небо и земля. И в будущих проектах я буду придерживаться одного простого правила «Лучше потратить больше времени на планирование и проектирование, чтобы потом не тратить в разы больше времени на отладку и исправление», чего и вам желаю.

Автор: vyadzmak

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js