Давно хотел написать этот пост, но почему-то мне казалось, что смысла особого нет, потому что меня никто не поймёт. Те, кто не программист, не поймут, потому что не знают деталей нашей работы. А программисты - потому что сейчас другое время, другие ценности, работать принято по-другому, и многие вещи уже считаются сами собой разумеющимися, хотя в годы моей юности это было совсем не так. Но вот сегодня на ретроспективе нашего спринта подняли вопрос из заголовка поста, и мне захотелось всё-таки его написать. Надеюсь, что меня не разнесут за кощунственное надругательство над святынями современной разработки программных продуктов :)
Итак, в чём я вижу проблему? Сразу говорю, я не очень знаю, как её решать, но надеюсь, что всё-таки пост не окажется похожим на неконструктивное нытьё. Проблема в том, что я работаю уже давно, с 1994 года. До 2000 года, когда я устроился в Транзас, я работал в маленькой фирме, которая занималась метеообеспечением авиации. Всё было просто: нас было двое - менеджер, который отвечал за взаимодействие с заказчиками, их поиск и прочую организационную фигню, и я - программист. За эти годы я написал очень много кода. И могу похвастаться: это были вполне законченные программные продукты, с документацией, вполне сопровождаемые и достаточно сложные. Например, одно из приложений было графическим редактором, который позволял показывать и редактировать карты погоды, проводить на них фронты и т.п. Другое принимало карты по линии связи, третье - отправляло. В общем, очень много чего там было, и всё это написал я один, от начала и до конца, почти не пользуясь никакими библиотеками, кроме API Windows, которое тогда ещё было 3.1. Даже MFC ещё тогда не было. И, в общем, всё работало, баги иногда были, но не сказал бы, что их было очень много. И, вполне возможно, что работало бы и до сих пор, если бы Windows поддерживала 16-битные приложения. А может, где-то и работает. Учитывая, насколько устаревшее оборудование иногда можно встретить на метео у нас в глубинке (да и не только в глубинке), я этому не удивлюсь.
И вот, с тех пор прошло больше 20 лет. Огромный срок и для программиста, и вообще для технологии разработки ПО. Казалось, бы всё должно было стать лучше и совершеннее, и производительность труда должна была только вырасти. Ведь компьютеры стали в сотни раз быстрее, а возможности программ, в том числе и инструментов программиста, стали намного больше. Но, увы. Возможно, конечно, я стал уже слишком стар, и просто брюзжу, что раньше деревья были выше, а дожди мокрее, но мне так совсем не кажется.
Потому что раньше за пару недель я мог написать в одиночку довольно солидную программу, которая была бы уже вполне закончена и готова к тому, чтобы поставить её заказчику. Или исправить огромную кучу замечаний по результатам командировки, выслать заказчику исправленную версию, с которой он бы уже работал и остался доволен.
А что может сделать один человек за один двухнедельный спринт сейчас? Да почти ничего. Мне кажется, целая наша команда не может сделать законченный программный продукт за 2 недели. Я помню, как мы разрабатывали один довольно простенький графический редактор. Это длилось много спринтов, и в разработке участвовала вся команда. Причём, это был совершенно отдельно стоящий редактор, никакая не часть Uninav, на который можно было бы свалить медленность разработки...
В общем, на мой взгляд, скорость разработки с тех пор упала в разы, а то и в десятки раз. Это чудовищно! Почему же так происходит? Что мне сейчас мешает работать с прежней скоростью?
Мне кажется, хотя с точки зрения современной разработки я сейчас скажу ересь, почти всё, что было придумано за последние 20 лет для улучшения качества программ, на самом деле замедляет разработку. Почему?
Вот раньше, если мне надо было, допустим, исправить багу в программе, что я делал? Во-первых, код был на 100% написан мной, так что я в нём отлично ориентировался и сразу понимал примерно, в чём может быть дело. Сложных ошибок, связанных с многопоточностью, тогда ещё не было, потому что многопоточность встречалась редко. Самые сложные ошибки были связаны с порчей памяти. Даже в самых сложных случаях я мог наставить отладочных печатей, мгновенно пересобрать программу и хотя бы методом двоичного поиска найти проблемное место. В общем, я не помню, чтобы какие-то баги искал много дней. Максимум, через несколько часов я уже мог отправить заказчику исправленную версию. Часто так и происходило: мой напарник был в командировке, звонил мне, просил исправить, и я всё исправлял очень быстро и оперативно.
Что происходит теперь? Возьмём самую простую багу, какая только может быть: где-нибудь вместо символа "А" выводится "Б". Сначала вся команда это обсуждает. Брать в спринт или нет, сколько будет стоить исправление. Вся команда тратит на это время, а не только тот, кто будет исправлять. Потом надо завести для неё отдельную ветку, ответвиться от мастера. Потом надо у себя собрать эту ветку. Поскольку в мастер постоянно попадает куча изменений, как правило, пересборка длится несколько часов. И хорошо, если сборка закончится успешно. А в половине случаев оказывается, что кто-то где-то решил перейти на новый компонент, новый компилятор, ещё что-нибудь поменял, и именно у меня - не срослось... Причём компилятор или сборщик обычно ругаются так, что вообще невозможно понять, в чём на самом деле проблема. Приходится писать в разные чаты с просьбой помощи, там пройдёт ещё пара часов пока кто-нибудь ответит... Потом оказывается, что проблема известная, но надо завести пункт, или пул-реквест уже висит, и уже завтра будет вмержен, а пока можно взять эту ветку... Ветку-то взять можно, но её надо снова собирать, а главное - потом будут проблемы с ребейзом моей ветки... И вот, получается что первый день уже прошёл, а пока ничего не исправлено.
Ну ок, наконец-то всё собралось и, дай Бог, запустилось. Кстати, чтобы запустить продукт тоже надо ждать минут 5-10... Он подгружает какие-то модули, почему так долго - непонятно. После этого надо понять, где именно находится этот текст, который надо исправить. В случае текста можно воспользоваться поиском, но в более сложных случаях всё не так очевидно, ведь этот код с вероятностью 99% писал не я. Ведь у нас командная разработка, все типа взаимозаменяемы, все всё должны знать и уметь взять любую задачу... А в итоге никто не знает ничего, потому что знать хорошо такой огромный продукт просто невозможно... Да и достался он нам от другой команды. Но, так или иначе, проблема обычно решается, и это и есть самая интересная и самая продуктивная часть работы. Собственно говоря, только здесь полезная работа и делается - исправляется бага или пишется новый код. Правда, в случае, когда правится код, написанный кем-то другим, высока вероятность сделать новые баги, потому что до конца понять чужой код удаётся, увы, не всегда.
Кстати, не далее как сегодня мне нужно было немного подправить свою программку, написанную в 2009 году. Я был приятно удивлён, что в коде есть комментарии, хотя писал я её сам для себя! Как же это упростило мою задачу - к каждой функции было написано, что она делает, да ещё и на русском языке! Почему-то сейчас комментарии в коде я не вижу практически никогда. Это считается дурным тоном. Просто человек, который пишет код, думает, что он пишет понятно. Ему-то понятно. Но даже ему лет через 10 будет непонятно... А у нас даже Uninav, который ещё не имеет коммерческого релиза, пишется 10 лет...
И раз уж я заговорил о коде... Отсутствие комментариев можно было бы пережить, если бы код был простым и понятным. Увы, нет. Иногда мне кажется, что другие программисты намеренно стараются написать код как можно сложнее, применив как можно больше сущностей. Или, наоборот. Вот лямбда-выражение всегда можно заменить функцией с понятным названием. Но нет, городят этот огород с нагромождением квадратных, фигурных и круглых скобок. А шаблоны? Они вполне способны породить ошибки компилятора с текстом длиннее, чем этот пост. Конечно, когда-то это всё оправдано, я сам иногда пишу и лямбды, и шаблоны. Но я это делаю только тогда, когда они явно упрощают жизнь и понимание кода. Я всегда стремлюсь решить задачу самым простым способом. Спросите у тестеров, много ли багов я при этом делаю?
А после того как ошибка исправлена и проверена локально, начинается самое интересное. Надо включить этот код в продукт. Сначала надо повесить пул-реквест в мастер. При этом сразу же оказывается, что мастер уехал далеко вперед, и надо делать ребейз. Ребейз - это значит, что если понадобится снова собрать продукт у себя, то собираться он будет опять несколько часов. А, скорее всего, придётся. Потому что сначала сборка зафейлится из-за того, что в конце строки у меня случайно оказался лишний пробел, и проверка стиля кодирования не прошла. Ну а потом будет код ревью, и кто-то обязательно напишет замечание.
В последнее время я сам очень мало занимаюсь код ревью, потому что мне кажется, что польза от моего просмотра чужого кода минимальна. Чтобы врубиться в то, что он делает, мне надо потратить почти столько же времени, как если бы этот код писал я сам. Но когда я пишу код сам я могу его собрать, проверить, поотлаживаться. Тут же можно только смотреть. А я человек не очень внимательный. В общем, мне кажется, за всё время всех моих код ревью я нашёл у других буквально пару каких-то мелких ошибок. Хотя, я не спорю, есть люди, которые делают его довольно качественно. Но у меня не получается.
И вот, во время код ревью окажется, что для текста нужно пользоваться каким-нибудь макросом tr(), о котором я знать не знал. А, главное, до меня его в этом коде не было. Ни в коем случае нельзя пользоваться старыми добрыми new/delete, не дай Бог каким-нибудь sprintf и прочими "сишными" функциями, которые верой и правдой служили мне много лет. Нельзя просто привести тип, обязательно нужно писать static_cast<>, а вот dynamic_cast<> почему-то попал в кабалу теперь. В общем, не напишешь код по-старому. Хотя я как-то писал, и не припомню, чтобы мои программы падали, висли и глючили больше, чем нынешние...
После код ревью приходится всё начинать сначала. Опять ребейз, потому что мастер успел опять убежать. Опять ошибки в код стайле. Потом сборка не соберётся из-за того, что компилятор GCC под Линукс не понимает какое-то выражение (и выяснится это только через несколько часов сборки).
Ну а если ошибок компилятора не будет, то сборка зафейлится из-за автотестов. У нас считается, что весь код должен быть покрыт автотестами, и мы прилагаем много усилий к их написанию. Кстати, это уже само по себе увеличивает время разработки очень здорово, потому что хорошие тесты могут быть не проще, чем тестируемая программа. Но если бы эти тесты валились тогда, когда они должны валиться! Нет, они валятся потому что виртуалка слишком медленная, и вылетают по таймауту! Тестов много, и каждый раз найдётся хоть один, который не пройдёт, хотя мой код совсем, ну никак его не затрагивает. Есть тесты, которые валятся периодически, раз в несколько сборок. И после этого опять надо начинать сборку сначала и ждать несколько часов. Иногда бывает, что не удаётся вмержиться в мастер несколько дней, а то и неделю!
А если то, что нужно исправить, находится в rsdk, то сначала надо пройти все похожие круги ада с пул-реквестом в rsdk, а потом повесить PR с обновлённым rsdk в uninav...
И вот, спринт подходит к концу, и я, наконец, исправил эту несчастную багу. А кто-то начал исправлять не в начале спринта, и не успел. И потом на ретро мы будем долго выяснять, почему же мы опять не успели всё доделать к концу спринта, снова составим какой-то план, чтобы улучшить ситуацию, и снова всё будет по-старому... Потому что былые времена, когда всё было просто и понятно, не вернутся уже никогда.
Современные продукты стали слишком сложными. Причём они сложные не по объёму функциональности, которые они выполняют. В них сложный код, и этот код очень сложно в них встраивать. Их пишет слишком много народу. Из-за этого всё время кто-то один недопонимает идеи, которые заложил в коде кто-то другой. Со временем появляется слишком много лишних зависимостей, которые и приводят к тому, что на любое ничтожное изменение код пересобирается много часов. Чтобы бороться с ошибками, которые возникают из-за сложности кода, инфраструктуры и, скажем прямо, плохого понимания кода разработчиками, придумывают юнит-тесты, автотесты, код ревью, сборки пул реквестов и т.д. Но все эти инструменты, кроме того что требуют много времени на разработку себя, ещё и очень удлиняют процесс попадания кода в конечный продукт.
Ну и чтобы текст не казался сплошным нытьём, которым он всё равно является, попробую описать своё видение того, как эти проблемы решать. Понятно, что такую махину, как наш uninav, изменить быстро не получится. Но если идти в правильную сторону, то когда-нибудь станет лучше.
Во-первых, нужно уделить большое внимание надёжности инфраструктуры. Не должны валиться автотесты просто так. Вообще каждый случай, когда сборка свалилась просто так, должен рассматриваться как криминал, потому что он стоит много дорогого времени разработчиков. Дополнительные проверки типа cppcheck, clang-format я бы вообще убрал. По-моему, пользы от них никакой. В крайнем случае, можно время от времени запускать и исправлять найденные ошибки, но не каждую сборку.
Нужно стремиться к тому, чтобы разные части продукта как можно меньше зависели друг от друга. В частности, вот есть продукт Radar, и вся радарная функциональность должна быть отдельно. В идеале, я бы переехал в отдельный репозиторий. Чтобы никто, кроме нашей команды, в него не коммитил. Код ревью должны делать только мы сами, причём я бы уменьшил количество необходимых апрувов до одного. Обязательно убрать дурацкое правило с rebase.
Ну а в идеале, как мне кажется, многие проблемы решит (хотя, некоторые и породит), если уже в рамках нашего продукта и репозитория у каждого разработчика будет какая-то своя зона ответственности. В рамках этой зоны данный человек написал сам, или, по крайней мере, хорошо понимает большую часть кода. И если возникает задача, в рамках которой нужно модифицировать этот код, то именно этот человек, в первую очередь, берётся за эту задачу. Совсем утопичный вариант - разбить код на модули, каждый из которых написал один человек. И связь с внешним миром каждый модуль держит только через внешние интерфейсы, и вот их уже можно обсуждать, а требования к модулю - просто чтоб он выполнял нужные функции. И даже не важно, как он внутри написан.
Просто вот не нравится мне коллективная разработка. Недаром же ещё иногда у файлов встречаются шапки, где указан автор кода, и иногда даже зачем-то e-mail. Должен быть у кода хозяин. Иначе получается как будто книга, у которой огромный коллектив авторов. Одну строчку написал один, следующую - другой... Будет ли такая книга написана красиво, будет ли она литературным шедевром? Вряд ли. А вот если хотя бы всю главу целиком напишет один человек - будет уже намного лучше.
Да, и главное, если бы все писали код исходя из принципа "как можно проще и понятнее", очень многих проблем удалось бы избежать. Но это уже точно совсем утопия.
Вот примерно и всё, что я хотел сказать. Надо же, написал это всего за один вечер!