Однажды много лет назад я решил почитать "Искусство войны" Сунь Цзы. Ведь это произведение до сих пор является самой влиятельной книгой по стратегии в мире. Его принципы применимы не только в военной сфере, но и в бизнесе, юриспруденции, спорте и даже программном обеспечении. Но когда я открыл его и начал читать, то обнаружил, что многие его утверждения настолько очевидны и банальны, что о них не имеет смысла даже говорить: Кто – еще до сражения – побеждает предварительным расчетом, у того шансов много; кто – еще до сражения – не побеждает расчетом, у того шансов мало. У кого шансов много – побеждает; у кого шансов мало – не побеждает; тем более же тот, у кого шансов нет вовсе. Я был разочарован. И только гораздо позже смог осознать, что в этих банальных, простых и очевидных утверждениях кроется вся суть. И самое сложное в том, чтобы полностью осознать их и только после этого применить на практике. Данная статья ни в коем случае не претендует на уровень "Искусства войны", но содержит много банальных и очевидных советов. Так сказать, сборник граблей правил, облегчающий разработку корпоративного программного обеспечения, которым я и хочу поделиться. Я не претендую на истину в последней инстанции. Если вы не согласны с чем-либо - это ваше личное мнение, и я его уважаю. Надеюсь, вы будете уважать мое. Все советы и указания в статье опциональны и носят рекомендательный характер. Некоторые советы могут противоречить нюансам корпоративной этики или политикам, принятым в вашей компании. Будьте внимательны и помните, что правилам, установленным в компании, лучше следовать (все иное - на ваш страх и риск). UPD (спасибо @Sergey-Titkov): Воин (в контексте статьи) - отличный специалист с точки зрения его технических и прочих навыков. Он отлично знает свой стек технологий и предметную область, в совершенстве владеет софт-скиллами. Его единственный минус: воин действует как изолированная единица, не учитывая нюансы работы в команде. У воина есть Честь и Кодекс. Следуя им и не принимая никаких компромиссов, воин огнем и мечом искореняет любую несправедливость, встреченную на его пути. Воин не пытается навредить, он стремится сделать проект идеальным. Но, к сожалению, такой "Путь Воина" может привести к проблемам. Выделим четыре категории участников разработки: Разработчик QA-инженер DevOps-инженер МенеджерПричем, под менеджером будем понимать не только непосредственное руководство, но и product-, project-менеджеров, а также все сопутствующие обязанности по руководству и организации бизнес-процессов: team- и tech-lead, scrum-мастер и различные менторские активности. Разработчик-воин является адептом совершенного кода, не приемлет legacy и временные решения и не готов идти на компромисс с собственным чувством прекрасного. Именно поэтому из лучших побуждений он переписывает критичный участок кода, полностью и безоговорочно "нанося непоправимое добро" (от автора: большое спасибо коллеге за весьма точное выражение). У вас есть система аренды транспортных средств, в которой есть описание транспорта: Представьте, что код был написан в те далекие времена, когда электромобили и прочий современный транспорт не были популярны или вообще отсутствовали на рынке. Поэтому в описании транспорта есть понятие объема двигателя и объема бензобака. Относительно электромобилей мы можем договориться, что объем бака = емкости аккумулятора, а объем двигателя = номинальной максимальной мощности в режиме длительной нагрузки. Однако, это вызывает множество вопросов: как разница в единицах измерения повлияет на валидацию, расчеты стоимости, рисков и прочее? как избежать пересечения логик? Например, как при наличии скидок на заправках не позволить арендовавшему электромобиль купить бензин по скидке? Или, например, не распространится ли скидка на электротранспорт на остальные виды? при разнице в аренде (тарифных планах), как понять, какой тариф применим к арендованному транспорту? как вообще отличить электромобиль от автомобиля на бензиновом двигателе? что делать с гибридами? и т.д. А теперь допустим, что компания решает помимо электромобилей предоставлять в аренду прочий электрический транспорт: электросамокаты, гироскутеры, моноколеса, велосипеды с электроколесом и т.д. В данном случае есть несколько компромиссных вариантов: аккуратно расширить старые сущности новыми полями и расширить механизм добавлением обработки новых полей таким образом, чтобы старая логика не была затронута и не ломалась при получении расширенного объекта, а новая логика не пыталась обработать старый объект добавить новый более универсальный механизм обработки параллельно старому, чтобы со временем плавно "пересадить" на него обработку всех сущностей, а старый вывести из использования Однако, это все компромиссы. разработчик-воин не готов мириться и ждать, поэтому переписывает все и сразу. Код стал более гибким, более качественным. Задача выполнена "наилучшим образом". Почему же никто не рад? QA-инженеры вместо проверки конкретных сценариев конкретной части бизнес-логики вынуждены перепроверить множество пользовательских кейсов (а возможно, даже провести полный регресс-тест всей системы)UPD (спасибо @sshmakov): Регулярный полный регресс системы - хорошая практика, когда он запланирован и выполняется как отдельная задача. Плохо, когда команда вынуждена проводить его незапланированно и после каждой задачи. DevOps вынуждены изменить CI/CD проекты для доставки вашего кода на production Менеджеры, которые ожидали небольшое изменение, которое придет на production в течение короткого периода времени, вынуждены ждать его несколько месяцев и извиняться перед клиентами и соседними командами разработки, которым оно было заранее обещано. QA-воин точно так же, как и разработчик, в стремлении к прекрасному способен упустить реальную потребность компании в максимально быстрой доставке изменений конечному пользователю. покрыть автоматизированными тестами все возможные и невозможные кейсы (особенно это касается ситуаций, которые потенциально возможны на dev- и qa-окружении, но из-за определенной специфики никогда не возникнут на production) переписать базовые механизмы работы с тестами в рамках тестирования определенной задачи (что тоже потребует отдельного review и тестирования) посчитать все найденные баги сверхкритичными ошибками, блокирующими продвижение изменений дальше UPD (спасибо @Mike-M): Для потребителя качество важнее скорости доставки изменений. Быстрая доставка изменений пользователю - требование не пользователя, а бизнеса. Здесь лишь описана ситуация, когда применима поговорка "лучшее - враг хорошего" и QA-воин теряет слишком много времени в стремлении изменить проект от "хорошего" к "лучшему". DevOps-воин действует аналогично QA, задерживая доставку изменений, занимаясь следующими задачами: полная переделка существующей инфраструктуры под новые реалии перенос логики развертывания на последние технологии и паттерны, без учета того, что текущий бизнес-процесс не рассчитан на них и тоже потребует изменения слишком рано обновляет версии используемого ПО, получая ошибки сборки, несоответствие версиям кода, а также малый объем документации и решений встреченных на последних версиях проблем (впрочем, это замечание относится ко всем "видам" воинов) В отличие от предыдущих участников, менеджер сопровождает проект на всех этапах разработки, следит за общей картиной, координирует взаимодействие внутри команды и между командами, старается соблюсти баланс ресурсов. Менеджер-воин в стремлении все контролировать может броситься в крайности, что приведет к следующим результатам: излишнее погружение в разработку проекта (вплоть до контроля кода на уровне каждого разработчика), в том числе самостоятельное участие в активностях, которые можно делегировать другим (например, самостоятельно участвовать во всех процессах вместо создания набора понятных правил, позволяющих организовать самоконтроль или взаимный контроль задач коллегами) излишнее количество организаторских активностей (митинги и совещания, обсуждение проблем с участием людей, не вовлеченных в проект на данном этапе, излишне частая синхронизация знаний между командами и внутри нее) обсуждение специфичных для определенного этапа вопросов на активностях, не предназначенных для этого (например, мозговой штурм проблем, возникших у разработчиков внутри команды, на демонстрационном митинге между командами) излишнее повышение приоритета возникших проблем (например, вместо краткой переписки в формате "вопрос-ответ" в общем чате, назначить совещание и выдернуть на него всех членов команды) Вы работаете в команде, где кроме вас есть другие специалисты, с которыми вы работаете над решением общей задачи вместе. И время выполнения задачи складывается из времени, затраченного всей командой, а не только вами. Поэтому перед тем, как внедрять ваше безусловно гениальное решение, задайте себе следующие вопросы: Какую проблему я решаю? Как я могу решить задачу, оказав минимум влияния на систему? Какую логику я затрону своими изменениями? Какие изменения затрагивают общие механизмы? Могу ли я доставить их отдельно и заранее? Что делать с прочими проблемами, обнаруженными в ходе решения текущей проблемы? Что я могу изменить, не выходя за рамки затронутой бизнес-логики? На первый взгляд все эти вопросы заданы с точки зрения разработчика, но на самом деле подойдут любой из вышеуказанных категорий. Разработчик планирует и вносит изменения непосредственно в код, QA-инженер - в автоматизированные тесты и процесс тестирования, DevOps - аналогично, в развертывание, а менеджер ставит задачи перед командой и организует взаимодействие (т.е. с точки зрения менеджера под "системой" понимается не только проект, но и сама команда). Воин всегда хочет как можно меньше времени потратить на понимание задачи и как можно быстрее броситься в бой с шашкой наголо приступить к ее выполнению. В простой задаче он не ищет подводных камней ("да здесь все понятно, все очевидно"), а в сложной не хочет разбираться ("ничего не понятно, сделаю по примеру, так быстрее и эффективнее"). Решение в том виде, в котором мы понимаем задачу без глубокого погружения, чаще всего неэффективно, а иногда даже неправильно. Чаще всего хороший разработчик остановится на третьем или даже четвертом шаге, т.е. откатит изменения кода или накатит изменения для БД, если это возможно. Но вот до последнего шага без ретроспективы или вопросов от менеджера скорее всего не дойдет.Мы привыкли писать код, а не налаживать процесс разработки. Зачем, если и так работает? В следующий раз будем внимательнее.Вот только, если есть правила или, еще лучше, какой-то механизм синхронизации, то не понадобится быть внимательнее, все будет работать в любом случае. Задачи от пользователя идут к разработчику через менеджеров и могут быть искажены на любом этапе (как пользователем, так и менеджером или самим разработчиком).Пользователь и менеджер могут не знать возможностей и нюансов реализации систем, поэтому могут попросить то, что сделать невозможно, или выбрать не самый оптимальный путь.Задача разработчика не в том, чтобы взять и сделать, что сказано, а в том, чтобы понять, что требуется, и предложить наиболее оптимальный вариант решения. Когда вы работаете над крупным проектом, который уже есть и работает на production, любое внесение изменений приводит к затратам ресурсов всей команды: тестирование, изменения со стороны DevOps, продвижение изменений через окружения до production с тестированием на каждом. Чем больше изменений, тем больше ресурсов команды будет потрачено. Чаще всего мы вынуждены выбирать между более точечным исправление и более общим. Мы находим корень проблемы и правим именно его. полноценное исправление: исправляет большинство сценариев, даже необнаруженные ранее, поэтому не приводит к затратам ресурсов команды в будущем; не приводит к росту технического долга (при полноценном исправлении без заглушек и "оставим на потом"), а в редких случаях может уменьшить его; гарантированное исправление: исправляется корень проблемы, а не "симптомы" занимает больше времени: внесение изменения в механизм может привести к необходимости изменения в местах его использования; затрагивает много бизнес-логики: приводит к большим затратам ресурсов команды в ближайшей перспективе; Проблема правится только в месте возникновения и(или) только для конкретного случая. выполняется быстро, может быть использован в качестве hotfix; затрагивает минимум бизнес-логики, что приводит к минимальным затратам ресурсов команды "здесь и сейчас" неполноценное исправление: прочие аналогичные ошибочные сценарии остаются неисправленными или даже необнаруженными, в будущем потребуется снова тратить ресурсы команды на их исправление; негарантированное исправление: конкретный случай может оказаться лишь проявлением более крупной проблемы, требующей правки общего механизма, которая приводит к значительным затратам ресурсов команды; рост технического долга: "пока что исправим так, а потом сделаем, как надо". Также стоит помнить, что технический долг увеличивает сам себя (накручивание костылей вокруг костылей), а также растет со временем. Из всех вариантов решения нужно выбирать тот, который приводит к наименьшим суммарным затратам ресурсов команды как в текущее время, так и в перспективе.Именно такой компромисс позволит вам максимально быстро доставлять код до production и занимать для этого минимум ресурсов команды. Каждая роль в команде предполагает определенный уровень знания о системе (проекте), но не больше. Менеджеры должны знать систему с точки зрения бизнес логики DevOps-инженеры должны знать систему с точки зрения ее развертывания плюс бизнес логику QA-инженеры должны знать систему с точки зрения бизнес-логики плюс базовые нюансы реализации (некоторые таблицы в БД и их связи, Private и Public API, разметка UI для написания UI-тестов и так далее). Помимо этого QA-инженеры - эксперты в области тестирования системы Разработчики должны знать систему практически полностью от бизнес-логики до архитектуры и реализации Воин - самостоятельный боец. Он не задумывается о взаимодействии внутри команды и о том, чтобы облегчить выполнение задач остальным. Он считает, что на других ролях тоже должны быть воины, которые со всем справятся самостоятельно. Каждый член команды может, но не обязан знать больше. Отсюда следующие выводы: Только вы знаете, какая бизнес-логика затронута вашими изменениями. Разработчик-воин выполнит задачу и отдаст ее на тестирование без пояснений. В итоге QA-инженер протестирует только те кейсы, которые описаны в задаче, хотя изменениями могли задеть и другие части системы. После внесения изменения, разработчик должен выполнить следующие действия: Сообщить менеджерам об изменении в бизнес логике или появлении определенных нюансов; Сообщить QA-инженерам, какая логика задета, в идеале, уточнить кейсы, которые более критичны или появились в процессе изменения; Сообщить DevOps-инженерам, какие изменения нужно внести в процесс развертывания (переменные, конфиги, настройки доступов); Передать знания о внесенных изменениях коллегам-разработчикам (code review + документация) Также хочется заметить, что существует особая каста разработчиков-воинов - святое воинство. Их Кодекс гласит, что тестированием должны заниматься только QA-инженеры, а для истинного dev-воина это тяжкий грех. Они выполняют задачу и передают ее дальше, ни разу не проверив свои изменения. И только  "святостью" можно оправдать непоколебимую веру в их безошибочную непогрешимость. Только вы владеете все информацией по процессу тестирования и можете сказать, какие изменения нужно внести в тесты для поддержки изменений от разработчиков. После получения информации от разработчиков, QA-инженеры должны выполнить следующие действия: Сообщить менеджерам об объеме изменений в тестировании.Помните, что технический долг для QA более критичен, чем технический долг для DEV; Запросить у разработчиков недостающую информацию.Нужно заниматься не механическим тестированием задачи, а разобраться в ней, понять все бизнес- и тест-кейсы. Если не хватает каких-либо знаний о системе или не понимаете задачу (или ее решение) до конца, нужно обратиться к разработчикам. Передать полученные от разработчиков знания и внесенные в тестирование изменения своим коллегам QA-инженерам (code review + документация) Среди QA-воинов тоже есть приверженцы своего "святого воинства" - экстрасенсы. Это воины, которые найдя проблему, не дают никаких описаний и пояснений, видимо, транслируя эту информацию напрямую в мозг разработчика. Однако, простые разработчики не способны воспринимать информацию таким образом и поэтому вынуждены гадать на кофейной гуще пытаться спрогнозировать условия возникновения ошибки и проводить собственное независимое расследование. Только вы владеете все информацией по процессу развертывания системы. Однако, все нюансы и проблемы с развертыванием должны быть запрошены разработчиком до начала внесения изменений, т.к. влияют на решение задачи. Поэтому из обязательных действий после внесения изменений разработчиком DevOps-инженер должен выполнить следующие: Внести изменение в развертывание системы в соответствие с предоставленными разработчиком данными Передать полученные от разработчиков знания и внесенные в тестирование изменения своим коллегам DevOps-инженерам (review + документация) Только вы владеете информацией о сопряженных задачах в других командах, приоритетах и организации проекта на высоком уровне. Поэтому после внесения изменений от менеджера требуются следующие действия: Запустить оповещение всех заинтересованных лиц об изменениях (менеджеров других команд, конечных пользователей и так далее) Скоординировать процесс и запустить связанные с выполненной задачи Простимулировать обмен знаниями (обновления, изменения, дополнения) внутри команды и между командами Данный вопрос рассмотрим для всех категорий вместе, но под общим механизмом для них будем понимать разное: для разработчиков это будет некая общая часть кода/системы; для QA - общая часть системы тестирования; DevOps - развертывания, а для менеджера - это сами задачи уровня бизнес-логики и сами бизнес-процессы. Под основным продуктом можно понимать как саму систему в целом, так и, например, общую ветку в системе контроля версий. Допустим, есть две задачи, которые необходимо выполнить. Также понятно, что необходимо внести изменения (или создать) в некую общую часть, которая понадобится в обоих случаях. Есть несколько путей, которым можно следовать: Выполнить задачи последовательноЭто хороший вариант, который не требует никаких дополнительных действий. Но он не всегда возможен. Приоритетные задачи в большинстве своем могут выполняться параллельно за исключением синхронизации общих участков. Выполнить задачи параллельно и синхронизировать их итоги при добавлении в основной продуктЭто самый неприятный, но часто выполняемый вариант. Он приводит к множеству проблем: Конфликт измененийЧем дольше вы удерживаете критичные изменения, тем больше шанс возникновения конфликтов с изменениями ваших коллег как в коде, так и в самой бизнес-логике. Невозможность совместного использованияЕсли вы вносите какое-то исправление в общий механизм, оно может быть полезным для ваших коллег в рамках решения других задач. Но пока вы его не добавите в основной продукт, оно будет недоступно для них. Объединение независимых изменений (увеличение объема изменений)Чем меньше объем единовременных изменений, тем проще их протестировать, проще с ними ознакомиться и начать их использовать. Внесение общих изменений в рамках частного решения увеличивает объем изменений, а объем покрываемых кейсов выходит за рамки задачи. Ассоциация общего с частнымВнедряя общее изменение в рамках определенной крупной задачи, вы не только заставляете коллег дольше его ждать, но и вносите определенное когнитивное искажение, приводящее к ассоциации общего механизм с частной задачей, хотя область его использования гораздо шире. Вынести общую часть как отдельное изменение, а затем продолжить выполнение задачЭто наиболее оптимальный вариант в случае, когда последовательное выполнение невозможно. Конфликт изменений становится менее вероятным, общий кусок можно использовать совместно, причем он будет обработан, доставлен и воспринят отдельно, что уменьшит объем изменений по задаче и избавит от когнитивного искажения. Иногда в ходе решения проблемы можно обнаружить другую, не связанную с ней, проблему. Например, разработчик, изучая участок кода, может подметить какую-то ошибку или опечатку; QA-инженер может придумать какой-то нетривиальный кейс, который не был рассмотрен ранее и так далее. Воин стремится решить все наши проблемы, поэтому он готов сразу же броситься грудью на амбразуру начать решать новую проблему в рамках старой. Однако, такой подход чреват следующими проблемами: Неотделимость измененийКак только вы внесли изменения в рамках задачи, эти изменения становятся неотделимыми от нее. Вы не сможете доставить эти изменения на production раньше, чем доставите изменения по вашей задаче. Параллельное решениеВаши коллеги также могут заметить эту проблему. Тогда вы рискуете не только получить дублирующее исправление, которое приведет к конфликтам изменений, но и потратите время нескольких человек на параллельное решение одной и той же задачи. Дороговизна исправленияВы сами не всегда можете определить критичность проблемы и все условия ее возникновения, а также реальность совпадения всех этих условий. Поэтому вы можете потратить много времени, исправляя несущественную ошибку (например, ошибочный кейс, который никогда не возникнет на production) и, одновременно, отложите решение поставленной задачи. Также, так как на исследование побочной проблемы не выделено время, вы можете поправить только частичный случай, а проблема может оказаться глобальнее. Точечное исправлениеТак как вы решаете побочную проблему в рамках текущей, вряд ли вы захотите тратить много времени на изучение ошибочного кейса. Поэтому вы вполне можете поправить частный случай или симптом вместо поиска и исправления глобальной проблемы. Например, вы исправите ошибку, приводящую к некорректной записи в базу данных, но не станете выявлять записи, уже сохраненные с ошибкой. Что же делать? Просто заведите задачу (баг), а потом сообщите о задаче менеджерам. Укажите максимум доступной для вас информации: примерные шаги воспроизведения, кейсы, значения, конфигурацию, ветки, где это скорее всего воспроизводится и так далее. В рамках заведенной задачи QA-инженеры попытаются воспроизвести проблему и дополнят задачу (шаги воспроизведения, stacktrace, настройки и так далее). Менеджеры по описанию проблемы, вероятности и частоте ее появления на production определят приоритет. Если проблема критичная, то она сразу же поступит разработчикам на исправление и будет устранена независимо от вашей задачи. Данный вопрос может противоречить всему вышесказанному, но иногда необходимо пойти на компромисс, чтобы поддержать или улучшить качество системы Итак, вы выполнили задачу и уже затронули определенную логику в вашей системе. Это означает, что часть системы уже требует тестирования. Именно эту часть вы можете изменить дополнительно: провести рефакторинг; внести изменения, чтобы уменьшить существующий технический долг; добавить комментарии к коду, с пониманием которого возникли вопросы; Главное - всегда оценивайте, сколько ресурсов команды потребует ваше изменение. Благими намерениями...Не всегда хорошие поступки и стремления приводят к хорошему результату. Правила разработки и взаимодействия в команде написаны кровью из глаз на основе реального опыта множества людей. Да, обязательность некоторых правил очевидна не сразу. Но чаще всего соблюдать эти правила не сложно, а положительный эффект от них значителен, особенно в далекой перспективе. Группа воинов - не командаКаждый воин - отдельная самостоятельная единица. Он действует независимо, сводя взаимодействие внутри группы до минимума. Группа воинов - сила, но не команда. Группа солдат, работающих в команде способна работать гораздо эффективнее группы воинов, даже если их индивидуальные навыки ниже. Работа за других понижает эффективность команды в целомПомощь другим - правильный и полезный поступок. Но ровно до тех пор, пока оказывается не в ущерб своей деятельности. Если помощь другим затягивает решение текущей задачи, это повлияет на время выполнения задачи все командой. Попытка охватить все аспекты разработки с полным погружением в каждый из них уничтожит эффективность команды полностьюОдин человек будет работать за всех. Речь уже не будет идти о команде. Каждый будет являться командой-в-себе и играть все роли. Подобная команда из семи человек на самом деле будет являться семью командами из одного человека каждая. У каждого своя рольУ каждой категории свой набор возможностей и ответственностей, своя экспертиза в определенной части системы. Понимать чуть больше, чем положено, полезно для команды. Но не нужно пытаться отвечать за все и сразу. Попытка сыграть чужую роль должна приводить к ответственности за любое действие в этой роли. У каждой роли должен быть регламентКаждый должен понимать, что входит в его роль и что он должен делать в том или ином случае. Тогда разработчику будет точно понятно, как внести изменения, QA-инженеру понятно, как тестировать и покрывать тестами, а менеджеру удобно управлять командой, руководствуясь регламентами каждой роли. В команде должно быть взаимодействиеПоведение воина в стиле "каждый сам за себя" ведет к имитации "итальянской забастовки" и понижению эффективности команды. Каждый член команды должен делиться знаниями самым эффективным путем с фиксацией всей информации с возможностью ее воспроизведения. Т.е. заведенная задача и(или) документация с возможностью поиска будет гораздо лучше видеозаписи митинга без тайм-кодов, зато с помехами (отступления от темы, неточность формулировок из-за объяснения "на ходу", прерывания связи). Данная статья не призывает вас писать плохой код, лепить костыли и изобретать велосипеды. Я лишь хочу напомнить, что мы живем не в вакууме и разработка - это не только написание кода. Особенно в корпоративном секторе. Корпоративный работник действует в команде, где каждый выполняет свою роль, и его действия влияют на на остальных. Если есть возможность написать хорошо и не задержать выход новой версии на месяц - отлично. Если нет, возможно, стоит написать компромиссное решение, но запланировать  и согласовать его улучшение на будущее. Ну, и документация - наше все. Если на все ваши действия есть задачи, а на все части системы - документация, то вы всегда сможете восстановить ход событий и пояснить, что, зачем и когда было сделано. Искусство войны в программировании Отличная статья со списком советов по разработке от @AloneCoder Инженер-разработчик C#