Люди — наше всё!

Во все времена наиболее важным и в то же время самым непредсказуемым элементом любого производства остаются люди. Высококвалифицированный программист способен в одиночку разработать и внедрить продукт без выстроенных производственных процессов, однако создать более одного программного продукта и обеспечить параллельную промышленную эксплуатацию он уже физически не сможет.
В современных реалиях необходимы сотни, а порой и тысячи программных продуктов. Их разработка, внедрение и сопровождение требуют усилий тысяч различных специалистов.
Давайте снова представим себе задачу реализации прибыльного бизнеса как массовый забег на марафонскую дистанцию. Как вы помните, мастер спорта пробегает её за два часа двадцать минут. Но это не значит, будто мы можем нанять тысячу человек, которые гарантированно пробегут марафон за это время. Мало кто уложится в три часа, другим понадобится четыре, но большинство не добежит до финиша. То же и с разработкой. Производительность программистов может различаться в двадцать раз. Бесспорно, можно найти несколько действительно талантливых сотрудников, однако наивно мечтать о том, чтобы нанять таких тысячу.
Хорошее, эффективное производство отличается от плохого тем, что оно может обеспечить гарантированный, повторяемый и одинаковый результат в условиях высокой разницы в квалификации и производительности сотрудников.
Если продолжить пример марафона, то эффективным будет считаться такой спортивный коллектив, в котором все участники, независимо от начальных данных, смогут пробежать дистанцию за три с половиной часа. И речь идёт не о разовом варианте — они должны делать это ежедневно в течение многих лет, причём и состав участников, и условия местности будут меняться. Иными словами, эффективная команда станет постоянно и управляемо улучшать свои показатели — так, что через определённый срок все участники марафона смогут ежедневно пробегать дистанцию за три часа, а ещё через какое-то время приблизятся к показателям мастеров спорта, то есть двум часам двадцати минутам. При этом они будут постоянно адаптироваться к изменениям внешней среды, будь то асфальтированные дороги или проселочные, бег в поле, лесу, по горам, заплыв по воде, в солнечную или дождливую погоду, в снег или при сильном ветре.
Немаловажную роль играет и мотивация: зачем люди принимают участие в марафоне? Если вы спросите их, для чего они бегают, получите много разных, иногда прямо противоположных ответов. Кто-то хочет сбросить вес, другой — повысить выносливость. Или, скажем, хочет доказать себе, что может. Или потому что «все бегают», и это модно. Либо престижно. Кто-то просто любит бегать, а кого-то привлекают хорошие деньги, которые платят за победу. И даже потому, что больше нечем себя занять или «ничего другого не умею делать».
Примерно то же самое и в разработке программных продуктов. Мотивация сотрудников работать именно в вашей компании может сильно отличаться. Вряд ли кто-то трудится только для того, чтобы она получала прибыль. Ежедневное достижение хороших результатов тоже является своеобразным вызовом, поскольку человек — не робот и не может постоянно работать на пределе своих возможностей. Слишком много факторов влияет на производительность: к примеру, выспался ли сотрудник сегодня, поругался с супругом или пребывает в предвкушении встречи с друзьями вечером.
Таким образом, именно человек — одновременно и самый важный, и наиболее уязвимый элемент производства.
Эффективное производство незаметно помогает сотруднику выполнять его обязанности и в то же время снижает влияние негативных внешних факторов, которые ежедневно обрушиваются на человека, например, путём автоматизации рутинных операций. Необходимо взращивать культуру производства, направленную на непрерывное повышение профессионализма сотрудников.
Одним из важных факторов успеха в марафоне является поддержание постоянной скорости бега, для чего контролируется время, за которое спортсмен пробегает стандартный отрезок. Аналогично и в производстве ПО — необходимо научиться успешно выполнять задачи за стандартный отрезок времени, или спринт — в терминологии Scrum.
Люди, объединённые вокруг программного продукта в автономные осознанные команды — ключ к организации эффективного производства программных продуктов.

Фокус на команду

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

Команда – наше всё!

Прежде чем строить «команду мечты», нужно разобраться, чем она отличается от группы людей. Очевидно, что просто собрав несколько человек в одной комнате, мы не получим команду. Мы постоянно объединяемся в группы. Встретил знакомых на улице — вот уже и группа. Но ведь ни у кого язык не повернётся назвать её командой. С другой стороны, даже географически распределённые, разделённые расстоянием сотрудники могут составлять эффективную команду. На её сплочение влияет нечто большее, нежели просто физическое сближение людей.
Первое, что объединяет людей в команду — общая цель и одинаковое понимание способа её достижения. Пока у группы людей есть общая цель, они вместе к ней приходят. Если цели людей различаются или у них нет единого способа её достижения, а объединены они только силой обстоятельств (например, приказом по компании), команды из них не сложится. Показательным примером группы с разными целями может служить басня И. А. Крылова «Лебедь, рак и щука».

мы — команды

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

Как создавать команду?

Как быстро создать продуктивною команду? Мало просто собрать людей вместе и дать им цель. Нужно научить их работать вместе и достигать ожидаемого результата. Наиболее эффективный способ формирования продуктивной команды — использование в качестве донора существующей команды. Мы называем этот метод «почкованием». Вновь нанятые сотрудники включаются в существующую команду, пока все роли не будут задублированы. То есть фактически её состав удваивается. В таком увеличенном составе команда выполняет два спринта, в течение которых новички приучаются к принятым в компании процессам и практикам, получая реальный опыт правильного и эффективного их применения. Затем команда делится на две. Все вчерашние новички фактически составляют новую команду, выполняющую спринты уже самостоятельно, но всё-таки под надзором команды-донора. В итоге за один-два квартала количество эффективных команд удваивается.
Такой принцип формирования команды очень похож на приготовление хлеба с использованием закваски. Хлеб можно приготовить с использованием обычных хлебопекарных дрожжей, но они не способны придать достаточно пористую структуру тесту. Так и новичков нужно проводить через работу в уже существующей команде, чтобы они на своём опыте убедились, сколь эффективно действуют принятые в компании процессы и практики. Это снимает ненужное сопротивление — ведь часто новые сотрудники уже имеют опыт работы в других компаниях и убеждены, будто «знают, как лучше». А это, как правило, становится существенным тормозом в использовании и понимании других подходов.

Устойчивая команда

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

мы — устойчивые команды

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

Кросс-функциональная команда

Кросс-функциональная команда (англ. Cross-Functional Team) — это команда, имеющая все необходимые навыки, чтобы выполнять работу и не зависеть от тех, кто не является её частью.
Кросс-функциональная команда выполняет все производственные процессы:
  • реализация требований;
  • исправление ошибок;
  • выпуск релизов.
Ей также доступны все проектные практики:
  • обследование;
  • реализация;
  • внедрение;
  • сопровождение.
Для неё естественно освоение всех Scrum-практик:
  • планирование спринта;
  • ежедневные встречи;
  • демонстрация результата;
  • ретроспектива.
Члены такой команды выполняют обязанности всех необходимых производственных ролей:
  • лидер команды;
  • бизнес-аналитик;
  • архитектор;
  • аналитик;
  • системный аналитик;
  • системный архитектор;
  • прикладной программист;
  • системный программист;
  • тестировщик;
  • технический писатель;
  • ответственный за выпуск;
  • DevOps-инженер;
  • Scrum-мастер;
  • специалист по внедрению;
  • специалист технической поддержки.
Это становится возможным потому, что каждый участник способен выполнить сразу несколько производственных ролей. Роли обычно группируются следующим образом:
  • бизнес-аналитик/аналитик/системный аналитик/технический писатель;
  • архитектор/прикладной программист;
  • системный архитектор/системный программист;
  • тестировщик/ответственный за выпуск/DevOps-инженер.
Эффективная команда должна давать максимальный прирост к продукту. Это достигается эмпирически полученным соотношением производственных ролей — другими словами, их балансировкой. Максимальный прирост при соблюдении всех стандартов качества команда даёт при соотношении 1−4−2:
  • 1 аналитик;
  • 4 программиста;
  • 2 тестировщика.
Подробнее о производственных ролях речь пойдёт в следующей части книги.

Командная ответственность за результат

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

Мы – кросс-функциональные команды

Кросс-функциональная команда — это команда, имеющая все необходимые навыки, чтобы выполнять работу и не зависеть от тех, кто не является её частью.
Командная ответственность за результат развивает в команде взаимопомощь и уважение к чужому труду.

Культурный код команды

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

Культурный код команд

Культурный код команды — это наработанные командой навыки, позволяющие ей гарантированно прийти к успеху. Именно поэтому метод «почкования» наиболее эффективен, поскольку команда-донор передаёт новой команде свой культурный код.

Командные практики

Таковыми считаются производственные практики, фокусирующие внимание на командном достижении результата. Люди и их взаимодействие важнее процессов и инструментов. Идеально, если все внедряемые командой практики будут командными. Осваивая каждую из них, команда должна задавать и понимать ответы на вопросы:
  • Какие выгоды получит команда?
  • Какие действия команда должна выполнить?
  • За что отвечает команда?
  • Какой результат должна получить наша команда?
То есть команда должна понимать: действия любого её члена влияют на общий результат.
Подробное освещение лучших командных практик достойно отдельной книги. Здесь же я приведу лишь один пример.
Так, одной из практик, направленных на непрерывное развитие, является командная оценка компетенций. Их развитие в команде идёт в двух направлениях:
  • получение новых компетенций;
  • распространение компетенций среди членов команды.
Необходимость получения новых компетенций может быть связана с применением новых технологий, например, для разработки микросервисных приложений. Распространение компетенций среди членов команды прежде всего даёт возможность взаимозаменяемости. Знание функционала несколькими сотрудниками позволяет команде более гибко распределять задачи по его доработке и сопровождению и не прерывать работу над функционалом, даже если кто-то ушёл в отпуск или заболел. При масштабировании нагрузку по наставничеству новых сотрудников можно гибко перераспределять. Для сотрудников это тоже ценно, поскольку это их профессиональный рост.
Основным инструментом для мониторинга развития членов команды и принятия решений, связанных с их профессиональным ростом, является карта компетенций. Она показывает, какими компетенциями обладает команда и насколько уверенно ими пользуется. Кроме того, карта содержит план по росту компетенций.
Есть четыре уровня владения компетенцией.
  • Высокий уровень предполагает, что сотрудник решает задачу в оговорённые сроки и с высоким качеством.
  • Средний уровень — сотрудник решает задачу с превышением нормативных трудозатрат.
  • Низкий уровень — сотрудник имеет слабое представление о компетенции, и результат в этом случае не гарантирован.
  • Нулевой уровень — сотрудник не владеет данной компетенцией, и необходимости в этом нет.
Суть практики командной оценки компетенций состоит в следующем:
  • предоставить сотрудникам обратную связь по результатам работы в спринтах;
  • дать каждому обратную связь по уровню развития его профессиональных компетенций и взаимодействию с командой;
  • согласовать и зафиксировать планы по развитию компетенций на краткосрочный (до даты промежуточной оценки) и долгосрочный период (больше года).
Командная оценка компетенций должна проводиться регулярно.
На периодической основе в спринтах:
  • на ретроспективе сотрудник демонстрирует рост своих компетенций на примере решённых задач;
  • если подтверждён рост компетенции, в карте отмечается новый уровень.
На командной аттестации раз в полгода сотрудник:
  • показывает прирост за полгода по карте компетенций;
  • демонстрирует обратную связь от экспертов и наставников по компетенции.

Командная аттестация

Распространение компетенций среди членов команды прежде всего даёт возможность взаимозаменяемости. Знание функционала несколькими сотрудниками позволяет команде более гибко распределять задачи по его доработке и сопровождению.

Карта компетенций

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

Самостоятельность команды

Характеристика самостоятельности команды говорит, насколько автономно команда может решить любую поставленную перед ней задачу. Рассмотрим типовой производственный случай. Менеджер проекта планирует передать клиенту на приёмо-сдаточные испытания процесс, который объединяет работу функционала нескольких микросервисов, входящих в разные программные продукты. Программные продукты находятся во владении разных команд. Чтобы передать такой процесс, менеджер проекта вынужден идти в каждую команду, чтобы поставить задачу по реализации части процесса. Затем он должен передать эти части процесса клиенту — и верить, что весь процесс заработает. При таком подходе менеджер проекта сталкивается с несколькими проблемами.
  • Необходимо правильно декомпозировать процесс в соответствии с зонами владения команд.
  • Часть шагов может оказаться вне зоны владения какой-либо команды, и придётся решать вопрос принадлежности функционала с архитектором и руководителями продуктов.
  • У команд разная загрузка. Одна может взять задачу в ближайший спринт, а другая — лишь через несколько спринтов. Менеджер проекта вынужден синхронизировать реализацию отдельных частей процесса.
  • В процессе реализации могут появиться вопросы к другим командам, и менеджеру проекта придётся разрешать конфликтные ситуации.
  • Части процесса, разработанные разными командами, могут оказаться несовместимыми между собой и т. д.
Согласитесь, было бы гораздо проще, если бы менеджер проекта пришёл в одну команду и поставил ей комплексную задачу: реализовать процесс целиком, даже если во владении команды — функционал только части процесса. Под комплексной мы понимаем задачу, состоящую из подзадач для разных команд. Исходя из этого, самостоятельной можно считать такую команду, которая готова решать комплексные задачи.
Различаем три уровня самостоятельности команды.
  • Низкий уровень самостоятельности предполагает, что команда не берёт комплексные задачи. Команда решает только задачи в своей зоне ответственности.
  • Средний уровень самостоятельности предполагает, что команда берёт в работу комплексную задачу. Другими словами, становится генеральным подрядчиком. Команда выступает заказчиком смежных частей процесса для других команд. Принимает от них промежуточный результат, проверяет работу всего процесса и сдаёт его менеджеру проекта на обзоре результатов спринта целиком. Очевидным недостатком этого подхода является срок выполнения задачи, так как он продолжает зависеть от сроков решения частей процесса смежными командами.
  • Высокий уровень самостоятельности предполагает, что команда берёт в работу комплексную задачу и решает её в рамках своего спринта. Команда самостоятельно решает, для каких подзадач выступить заказчиком, а какие решить самостоятельно, используя практику InnerSource.
InnerSource определяется как практика применения уроков, извлечённых из разработки программных продуктов с открытым исходным кодом (англ. open source), для разработки ПО в пределах корпоративной IT-среды. При таком подходе код, которым владеет одна команда, может дорабатываться другими — при условии модерации изменений командой-владельцем.

культура производства

Культура производства — одна из главных, но, к сожалению, недооценённых составляющих эффективного управления. Она представляет собой совокупность материальных, организационных и духовных ценностей, которые и определяют уровень развития предприятия.
Эффективного управления не может быть без высокой культуры производства. А её создать невозможно — можно только взрастить. Подобно саду, в неё нужно вкладываться, сеять семена и саженцы, поливать и укрывать от непогоды. И заниматься этим постоянно, иначе сад перестанет приносить плоды и, в конце концов, засохнет.
Важнейшее условие взращивания культуры — комфортная корпоративная атмосфера, это как подготовленная почва для сада. Не стану подробно останавливаться на этой теме, уже достаточно широко освещённой в других источниках. Замечу лишь, что корпоративная атмосфера должна помогать закрывать первые четыре уровня потребностей сотрудников по пирамиде Маслоу (так называется упрощённая модель иерархической теории человеческих потребностей, разработанная американским психологом Абрахамом Маслоу).
Если же взглянуть на проблему с точки зрения теории спиральной динамики, комфортной корпоративной атмосферой считаются уровни, начиная с синего, носящего название «культура правил». На этом уровне процессы в компании регламентированы, и работают по ним все сотрудники. Когда речь идёт о внедрении гибких методологий, для получения большей отдачи лучше всего подходит зелёный уровень — «культура согласия», когда сотрудники вовлечены в совместное принятие решений. Строго говоря, и без культуры согласия здесь можно достичь значимого результата — но именно гибкие методологии становятся частью общего движения организации к культуре согласия.
Вернёмся к нашему «саду». Подготовив почву и установив автоматический полив, пора приступать к непосредственному уходу за деревьями — в нашем случае, сотрудниками. Люди — самое главное в производстве программных продуктов. Но их жизненно необходимо вовлекать в определённую культуру.
Иными словами, сотрудникам нужно прививать правильное отношение к выполняемому ими труду, разрабатываемому продукту, своей роли в коллективе, взаимопомощи, пониманию личной и командной ответственности за результаты своего труда. Как ни удивительно, но даже простые действия люди воспринимают по-разному. Скажем, начинающие программисты слишком утилитарно понимают, какой от них требуется результат от шага процесса «закодировать функцию». Реализовали код по техническому заданию и считают, что всё сделали, раз уж сборка прошла без ошибок. Более опытные, прежде чем выложить код в репозиторий, запускают unit-тесты. Понятно, что хорошо организованный конвейер сборки, тестирования, выпуска и доставки (CI/CD-конвейер) дополнительно содержит статический и динамический контроль качества кода, этап code-review (проверка кода опытными программистами) и т. д. Всё это правильно и должно обязательно использоваться, но, к сожалению, не способствует улучшению качества продукта. Даже наоборот, ведёт к его неуклонному снижению. Как ни парадоксально это звучит.
Чтобы качество не падало, а росло, нужно учить программистов изначально писать качественный код, в том числе и не содержащий ошибок. Сейчас в разработке программных продуктов набирает популярность подход, что нужно концентрироваться на скорости исправления ошибок, а не на снижении количества вносимых в код ошибок. Я считаю такой подход в корне неверным. Нужно совмещать быстрое исправление ошибок и снижать количество вносимых в код ошибок.
Как повысить качество разрабатываемых продуктов? Чтобы ответить на этот вопрос, сначала необходимо ответить на вопрос, кто отвечает за качество продукта. Есть такое понятие — заданный уровень качества, то есть выполнение продуктом всех заявленных в нём функций, без блокирующих их выполнение ошибок, а также без регресса по отношению к предыдущим релизам. Конечно, за качество отвечает команда, разрабатывающая продукт, но этот ответ не сильно помогает нам ответить на вопрос, как повысить качество продукта. Кто и что должен делать и сделать в команде, чтобы качество росло или хотя бы не падало?

ПИРАМИДА МАСЛОУ

спиральная динамика

Переформулируем вопрос следующим образом. Какая роль отвечает за качество продукта? За что отвечает каждая роль в команде в этой сфере? За качество продукта отвечает программист. Именно он вносит в него все ошибки. Причём делает это добровольно, ведь никто не заставляет его ошибаться — подобных требований вы не найдёте ни в одном техническом задании. Однако подавляющее большинство программистов уверено, будто ответственность за качество — дело тестировщика, и тогда зачем самому-то прилагать усилия, чтобы изменить свой стиль работы? Но тестировщик отвечает не за качество продукта, а за процессы тестирования и выпуска. Поэтому возвращаемся к первому, правильному ответу, из которого следует: чтобы улучшить качество продукта, надо повышать культуру разработки. Программист должен понимать и осознавать, что именно он в первую очередь влияет на качество разрабатываемого продукта. И — научиться писать код без ошибок.

культура разработки

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

стандарты и правила разработки

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

Кодекс программиста

В любой IT-компании программисты играют ключевую роль. Создание программного продукта — процесс сложный и увлекательный. И как всем творческим людям, программистам свойственно сомневаться и иногда ошибаться, выбирая из десятков решений единственно верное. Ориентиром в поиске путей реализации задуманного послужит набор простых правил в «Кодексе программиста».
  • Правило бойскаута — оставлять лагерь чище, чем ты его застал:
  • не увеличивать технический долг по программному продукту;
  • всегда сводить количество ошибок, выявляемых инспектором кода, к нулю;
  • самому проверять основной сценарий работы (англ. happy path);
  • создавать unit-тесты, покрывающие минимум 80% кода метода.
  • Правило сапёра — знать, что у программиста нет права на ошибку:
  • писать безопасный код, предотвращающий случайное внедрение уязвимостей и обеспечивающий устойчивость к воздействию вредоносных программ и несанкционированному доступу;
  • писать код без ошибок, разбирать причины возникновения ошибок, используя метод «пять почему», и находить меры для предотвращения подобных ошибок в будущем;
  • анализировать влияние вносимых изменений на смежные продукты;
  • понимать, что ответственность за функционал остаётся на программисте даже после установки продукта в рабочей среде;
  • постоянно учиться на своих и на чужих ошибках.
  • Правило Нострадамуса — не считать других экстрасенсами:
  • писать программный код понятно, документировать и комментировать его важные секции;
  • создавать простой программный код, исключать сложные для понимания и многострочные конструкции;
  • учитывать при реализации анализ влияния, а после реализации — грамотно его заполнять;
  • добавлять в код диагностику работы алгоритмов для пользователя;
  • добавлять в код логирование, чтобы упростить понимание работы алгоритмов для служб сопровождения;
  • писать осмысленные описания коммитов;
  • давать осмысленные имена всем артефактам кода: методам, переменным, классам и т. д.
  • Правило коммивояжёра — всегда искать самый оптимальный путь/маршрут:
  • разбираться в существующих системных механизмах, используя их по максимуму;
  • знать и использовать платформы экосистемы цифровой трансформации;
  • экономить ресурсы аналитики — реализовывать доработки по концепции;
  • использовать столько ресурсов (память, процессор, потоки, диск и т. д.), сколько нужно, и незамедлительно возвращать их в пул ресурсов;
  • выбирать оптимальные технологии и инструменты, дающие максимальный эффект «цена/качество», «производительность/ресурсоемкость» и т. д.;
  • стремиться к минимальной сложности кода (избегать циклов, глубокой вложенности, громоздких конструкций);
  • реализация должна идти от понимания бизнеса-процесса, а не от технического задания.
  • Правило Колумба — не открывать Америку:
  • не реализовывать заново то, что уже существует;
  • активно использовать предыдущий опыт;
  • активно делиться своими знаниями.

Кодекс аналитика

  • Активно используй накопленный опыт:
  • не дублируй ранее созданный функционал;
  • не занимайся плагиатом, ставь ссылки на источники;
  • соблюдай стандарты и сложившиеся практики;
  • не нашел аналог решения — спроси старшего;
  • используй мировой опыт и включай в решение готовые компоненты с открытым исходным кодом;
  • «разработать с нуля» — последнее, о чём стоит думать.
  • Активно используй системный подход:
  • декомпозируй сложное решение на простые;
  • используй MindMap;
  • приведи решение или его часть к ранее решённым вариантам и дай на них ссылку;
  • не пиши сочинений: выделяй фрагменты для повторного использования в отдельные статьи;
  • предлагай альтернативные решения; делай независимый анализ решений и выбирай лучшее на основе формальной логики;
  • мысли широко, не ограничивая себя рамками задачи; предлагай решение в рамках стратегии развития продукта и линейки продуктов;
  • ставь себя на место пользователя: предлагай решения, которые самому были бы удобны и полезны;
  • решай задачу комплексно и убедись, что все бизнес-процессы будут корректно исполняться;
  • не решай задачу «в лоб»; получи ответ на вопросы, зачем пользователю нужен функционал и что он планирует с ним делать.
  • Активно используй коллективный разум:
  • один в поле не воин — твоя команда тебе в помощь;
  • прежде чем писать концепцию, обсуди её с командой.
  • Активно используй инструменты:
  • схема, образ, процесс лучше передадут смысл задачи, нежели несколько страниц текста.

практики для качества продукта

Кто отвечает за качество продукта? Конечно, за качество отвечает команда, разрабатывающая продукт. А какая роль отвечает за качество продукта? За качество продукта отвечает программист. Именно он вносит все ошибки в продукт.
  • Уважай потребителей твоего труда:
  • «краткость — сестра таланта», не заставляй читать многостраничные документы;
  • пиши понятно, не допуская многозначных трактовок;
  • пиши красиво — документ не должен походить на грязный черновик.
  • Помни об эксплуатационных характеристиках продукта:
  • внедряемость: опиши поведение по умолчанию и не заставляй вручную настраивать функционал для начала его использования;
  • удобство и практичность — не заставляй пользователя менять опыт взаимодействия с системой;
  • сопровождаемость — твои изменения не должны влиять на смежный функционал;
  • диагностируемость — в любой ситуации, включая нештатную, пользователь должен понимать, где он находится, что происходит и как вернуть систему в штатный режим; помогай ему не совершать ошибок;
  • производительность — все задачи дня пользователь должен успеть выполнить в свои рабочие часы: у него тоже есть семья, и он хочет вовремя уходить домой;
  • безопасность — «враг не дремлет»; обеспечь защиту компонентов и данных;
  • гибкость — предусмотри для пользователя возможность подстраивать систему, причём настройка должна быть проста и понятна без документации.

Ответственность за качество продукта

Кто отвечает за качество продукта? Конечно, за качество отвечает команда, разрабатывающая продукт. А какая роль отвечает за качество продукта? За качество продукта отвечает программист. Именно он вносит все ошибки в продукт. Причём делает это добровольно. Никто не заставляет его вносить ошибки в продукт. К сожалению, большинство программистов считают, что за качество отвечает тестировщик. Если спросить программиста, почему в коде произошла ошибка, то наиболее частым, если не единственным, будет ответ, что продукт был плохо протестирован или, другими словами, продукт не имеет необходимого покрытия кода тестами. Не считая себя ответственным за ошибки в продукте, программист не предпринимает усилий по изменению своего стиля работы. Программист должен понимать и осознавать, что именно он напрямую влияет на качество разрабатываемого продукта. Именно он вносит все ошибки, и именно он должен научиться писать код без ошибок.
Тестировщик может и должен повышать качество тестов для выявления всё большего количества ошибок, но принципиально справиться с проблемой он не сможет. Потому как тестировщик не влияет на входящий поток ошибок, а только на исходящий. Это как с поддержанием чистоты в городе — есть два подхода. Если часов в 8 утра прогуляться по улицам европейских городов, можно увидеть горы мусора, но уже в 9 часов всё чисто — поработали клининговые службы. У нас другая традиция — выбрасывать мусор в ведро или в урну на улице. Таким образом, в европейских странах вкладываются в клининговые службы или, переводя на язык разработки программных продуктов, в службы тестирования, а мы вкладываемся в культуру поведения людей. И, на мой взгляд, наш подход более эффективен.

Принцип «не пачкать код»

Нужно прививать разработчикам принцип «не пачкать код».
  • Писать «чистый» код, то есть код, соответствующий конвенции по оформлению кода.
  • Писать простой код. Чем проще конструкция, тем проще его прочитать и понять.
  • Код должен читаться, как книга. Названия переменных должны передавать смысл сохраняемых в них данных. Например, переменная для хранения суммы кредитного договора в рублях должна иметь название «ContractCreditSumRUR» вместо «a» или «xyz».
  • Писать код без ошибок и соблюдать другие правила кодекса программиста.
Надо сказать, писать код без ошибок в конечном счёте менее трудоёмко, нежели с ошибками и последующими затратами на их исправление. Поясню на простом примере. Наиболее сложными в обнаружении и локализации являются ошибки, связанные с утечкой ресурсов, например, памяти. Подкованные программисты могут ответить на это следующим образом: современные средства разработки используют программы сборки мусора (англ. garbage collector) для автоматического освобождения ресурсов. Да, это так. Но не всё так просто. Во-первых, программы сборки мусора ориентируются на маркеры использования ресурса, например, на наличие ссылок. Пока ссылки действуют (даже если и не используются), память не освобождается. Такое безответственное отношение к работе со ссылками приводит к образованию перекрёстных или циклических ссылок, с которыми сборщики мусора не справляются. Во-вторых, даже сборщик мусора освобождает память с задержкой, а это значит, что другие функции, которым в этот момент память действительно нужна, вынуждены встать в режим ожидания.
Так что грамотный программист должен взять себе за правило — бережливо работать с любыми ресурсами. Брать их только непосредственно перед использованием, а не заранее, «на всякий случай», и незамедлительно их освобождать, как только необходимость в них отпала. Для этого программист должен сначала написать код по резервированию ресурса и его безопасному освобождению и только после писать код по работе с ресурсом. Безопасное освобождение ресурса — это код, который будет вызван при любом исходе работы основного кода работы с ресурсом, даже при нештатном его завершении. В итоге код по резервированию и безопасному освобождению ресурса становится шаблонным. Современные среды разработки включают средства автоматизации генерации шаблонного кода. Следовательно, такой код может генерироваться автоматически. Вот и получается, что писать код без ошибок утечки ресурсов менее трудоёмко, нежели обычным способом.

Принцип «сделал работу хорошо»

Следующий важный принцип — «сделай свою работу хорошо». Изначально каждый сотрудник по-своему понимает как собственную ответственность, так и её границы. Необходимо обучить сотрудников одинаковому пониманию границ своей ответственности. Здесь нужен общий подход, для чего и выработаны понятные критерии. Например, программист сделал свою работу хорошо, когда он:
  • полностью реализовал требуемую функциональность;
  • прокомментировал важные секции кода;
  • написал unit-тесты и убедился в их успешном выполнении;
  • проверил за собой по чек-листу:
  • исправил все выявленные инспектором кода ошибки;
  • проверил основные сценарии работы продукта, включающие разработанный функционал;
  • выложил код в репозиторий исходного кода;
  • в результате, изменение кода:
  • не приводит к ошибкам сборки программного продукта;
  • не приводит к ошибкам выполнения unit-тестов продукта;
  • не приводит к ошибкам выполнения функциональных, нагрузочных и других автоматических тестов, включённых в конвейер непрерывного тестирования;
  • не приводит к обнаружению критических и блокирующих ошибок, которые обнаруживаются средствами статического контроля качества кода;
  • основная или задуманная логика по требованию работает;
  • основные бизнес-процессы продукта работают, даже не будучи покрыты автоматическими тестами;
  • не вызывает серьезной деградации производительности;
  • не приводит к неконтролируемому росту объёмов базы данных;
  • не приводит к ошибкам автоматического развёртывания программного продукта.
Нужно понимать, что программист — не барин, которого все обязаны обслуживать. И пресекать ситуации, когда программист, просто закодировав функционал, отдал его на тестирование: «Не царское это дело — проверять». Программист должен отдавать гарантированно работающий код, чтобы тестировщику не пришлось тратить время на проверку работы основных сценариев. Задача тестировщика — сломать функционал, найти в коде изъян, написать автоматические тесты.

Unit-тестирование

Unit-тестирование (англ. unit testing), или модульное тестирование, — процесс в программировании, позволяющий достаточно быстро проверить на корректность отдельные модули исходного кода программы: не привело ли очередное изменение кода к регрессу (то есть к появлению ошибок в уже оттестированных местах программы), а также облегчает обнаружение и устранение таких ошибок. Unit-тест, по сути, такой же код, как и тестируемый им. Поэтому к коду unit-тестов применяются все требования обычного кода. На написание unit-тестов программист тратит до половины времени кодирования требования. Поэтому нужно обращать внимание на качество unit-тестов и затрачиваемое на них время. Нет прямой связи между затрачиваемым временем и качеством unit-тестов. Приведу несколько причин, приводящих к некачественным unit-тестам и, следовательно, к зря потраченному на них времени.
  • Сильно связанная архитектура. Программист выбирает, какой unit-тест написать легче, а не тот, который нужно.
  • Парность ошибок логики. Если программист неправильно понял и закодировал основную логику, то и unit-тест будет неправильно закодирован для успешной проверки неправильной логики.
  • Unit-тест на конкретную исправленную ошибку в коде. Если какой-то участок кода уже исправили, нет смысла это постоянно проверять. Если при реализации нового требования этот участок кода изменят, изменят и unit-тест. Как показывает практика, вероятность повтора одной ошибки в том же участке кода крайне мала.
Одним из способов проверки качества unit-теста, как и качества кода, является рецензирование кода. Программист выкладывает код в свою персональную ветку в системе управления версиями кода. По завершении реализации задачи отдает свой код и unit-тесты на него на рецензию другим программистам.

Рецензирование кода

Рецензирование кода (англ. code review) — инженерная практика систематической проверки исходного кода программы для обнаружения и исправления ошибок, допущенных при его написании. Главная цель рецензирования — сокращение затрат на сопровождение программных продуктов. Рецензирование кода одного программиста проводит программист-рецензент. Помимо нахождения ошибок, рецензирование способствует улучшению общего качества кода:
  • полезные практики, применяемые одним программистом, перенимают другие члены команды;
  • программисты больше узнают о коде (и логике, реализуемой им) из других частей проекта, которые в противном случае они бы вообще не увидели;
  • помимо качества кода, проверяются качество и достаточность unit-тестов;
  • программисты-новички получают быструю обратную связь от наставников и улучшают навыки кодирования;
  • владелец или архитектор продукта контролирует, соответствуют ли вносимые изменения текущей архитектуре программного продукта и видению её развития;
  • понимание, что твой код обязательно посмотрят коллеги, заставляет писать ответственно, чтобы потом не было стыдно.
На что нужно обращать внимание при рецензировании кода?
  • Код хорошо спроектирован. Он структурирован, разбит на функциональные блоки.
  • Функциональность сделана корректно с точки зрения пользователей кода, кем бы они ни были.
  • Код читается, как книга, и прост в понимании.
  • Код не переусложнён.
  • У кода есть unit-тесты, и они хорошо спроектированы.
  • Наименования переменных, методов, классов, модулей, интерфейсов и т. д. отражают суть их назначения.
  • Комментарии к коду понятны и необходимы. Они должны объяснять, почему так сделано, а не как это сделано.
  • Что не нужно проверять при рецензировании кода.
  • Соответствие стандарту оформления кода. Редактор кода должен автоматически приводить его к стандарту оформления.
  • Типовые ошибки, которые нужно проверять средствами статического анализа кода.
  • Соответствие различным стандартам, например, стандарту технологического стека. Всё это проверяется автоматически.
Рекомендации по проведению качественного рецензирования кода.
  • Идеальность вносимых в код изменений. Изменения в коде не должны быть идеальными, но они должны улучшать кодовую базу. Каждое изменение призвано сделать код только лучше. Если изменение добавляет много хорошего, не надо придираться к мелочам; выгоднее, чтобы это улучшение попало в кодовую базу быстрее. И, что важно, изменение не должно ухудшать код.
  • Факты важнее персональных предпочтений. Стандартные принципы построения программных продуктов, базирующиеся на лучших практиках, всегда лучше, нежели самостоятельно придуманный «велосипед».
  • Описание вносимых в код изменений крайне важны. Оно должно храниться вместе с кодом в истории системы контроля версий, и с большой вероятностью в будущем его будут читать очень многие. В первой строчке (в заголовке) одной фразой описывается, что сделано в данном изменении. Само описание должно быть информативным, содержать краткое описание решаемой проблемы, ссылки на необходимые документы (если необходимо), номера решаемых задач в Jira и другой контекст, улучшающий понимание. Например, описание типа «исправлена ошибка», понятное дело, не считается информативным.
  • Изменение должно быть как можно меньше. Слишком большие изменения нужно разбивать на части. Почти всегда это возможно. Например, выделяйте реализацию требования и рефакторинг кода в разные вносимые изменения. Рецензент имеет право отклонить изменение кода, если оно слишком велико.
  • Изменение кода должно быть небольшим, но самодостаточным. Всё, что необходимо рецензенту для понимания вносимого изменения, должно быть в нём, включая unit-тесты. Изменения не должны быть настолько мелкими, чтобы трудно было понять, как они будут использоваться и к каким последствиям приведут.
  • Объясняйте кодом. Если просят объяснить какой-то момент в коде, подумайте, нельзя ли поправить код так, чтобы он был понятен без объяснений. Потому что если один не понял, то и другие могут не понять.
  • Невероятно важна скорость проверки изменений кода. Если раз в несколько дней выдавать по одному замечанию, автор изменений будет считать, будто к нему придираются.
  • Порядок просмотра вносимого изменения в код. Сначала надо просмотреть изменение в целом. Нужно ли оно вообще? В правильном ли месте код (или это должно быть в отдельной библиотеке)? Есть ли какие-то глобальные проблемы? Нет смысла смотреть детали реализации, если код в целом не соответствует принятым правилам проектирования программных продуктов, размещён в неправильном месте или в принципе не нужен. Затем надо пройтись по основным файлам, то есть проверить главное. Если непонятно, что главное, спросите у автора изменений. И далее пройдитесь по оставшимся файлам. Ни один файл не должен быть пропущен. Важно как можно быстрее дать автору обратную связь.
  • «Я исправлю это потом». Бывает так, что автор изменений согласен с тем, что в коде есть проблема, но просит подтвердить их, обещая, что исправит позже. Увы, на практике это «позже» чаще всего никогда не наступает. Поэтому, если изменение не является срочным исправлением критической ошибки в промышленном контуре, нужно настаивать на немедленном исправлении.

Анализ влияния

Программист должен создавать код так, чтобы его изменения не привели к регрессу основных функций модуля, как по функциональности, так и по производительности. При этом он должен быть уверен в том, что его изменения не приведут к регрессу смежных модулей системы. Для этого применяются принципы проектирования и разработки SOLID. Если же регресса избежать не удалось, разработчик обязан разобрать причины, побудившие его внести в код ошибки, и реализовать мероприятия, которые снизят вероятность их повторения.
SOLID — это набор принципов, призванных помочь в проектировании и создании качественного объектно-ориентированного кода приложения. Они позволяют создавать чистый код, который будет в дальнейшем легко поддерживать и развивать. Принципы сформулировал Роберт Мартин в начале 2000-х годов.
Существует ряд признаков, указывающих на то, что код плохой.
  • Закрепощённость — под этим подразумевается, что при попытке внести изменения в один участок кода, возникает необходимость изменить другой участок кода, а за ним третий, пятый и так далее.
  • Неустойчивость — когда при изменении одного участка кода внезапно перестаёт работать другой, казалось бы, непосредственно не связанный с ним участок.
  • Неподвижность — невозможность повторного использования написанного ранее кода.
  • Неоправданная сложность — применение слишком сложных синтаксических конструкций, не несущих никакой выгоды.
  • Плохая читаемость — некрасивый и сложный для понимания код программы.
Акроним SOLID образован из заглавных букв входящих в набор принципов.
Принцип единственной ответственности (англ. Single Responsibility Principle). Существует лишь одна причина, приводящая к изменнию объекта. Один класс должен решать только какую-то одну здачу. Не нужно создавать классы-гиганты, которые будут решать много разных задач.
Принцип открытости/закрытости (англ. Open-closed Principle). Программные сущности должны быть открыты для расширения, но закрыты для модификации. Классы, модули, функции и прочее должны быть расширяемыми без изменения содержимого, вплоть до запрета изменения текстового файла, содержащего исходный код. Если ещё жёстче — то программные сущности должны расширяться без пересборки расширяемого кода.
Принцип подстановки Барбары Лисков (англ. Liskov Substitution Principle). Функции, использующие указатели ссылок на базовые классы, умеют использовать объекты производных классов, даже не зная об этом. Другими словами, объекты в программе можно заменить их наследниками без изменения свойств программы. Если программист расширяет ваш класс и использует его в приложении, он не должен изменять ожидаемое поведение переопределённых методов.
Принцип разделения интерфейса (англ. Interface Segregation Principle). Нельзя заставлять клиента реализовывать интерфейс, которым он не пользуется. Интерфейсы нужно разбивать на более мелкие, лучше удовлетворяющие конкретным потребностям клиентов. Много специализированных интерфейсов лучше, чем один — общего назначения.
Принцип инверсии зависимостей (англ. Dependency Inversion Principle). Абстракции не должны зависеть от деталей, наоборот, детали должны зависеть от абстракций. Программные продукты нужно разрабатывать так, чтобы различные модули были автономными и соединялись друг с другом с помощью абстракции.
Made on
Tilda