Русский
Русский
English
Статистика
Реклама

Качество кода

Как внедрить статический анализатор кода в legacy проект и не демотивировать команду

20.06.2020 18:10:45 | Автор: admin
PVS-Studio охраняет сон программиста

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


Введение


Недавно моё внимание привлекла публикация "Getting Started With Static Analysis Without Overwhelming the Team". С одной стороны, это хорошая статья, с которой стоит познакомиться. С другой стороны, как мне кажется, в ней так и не прозвучало полного ответа, как безболезненно внедрить статический анализ в проект с большим количеством legacy кода. В статье говорится, что можно смириться с техническим долгом и работать только с новым кодом, но нет ответа на то, что делать с этим техническим долгом потом.

Наша команда PVS-Studio предлагает свой взгляд на эту тему. Давайте рассмотрим, как вообще возникает проблема внедрения статического анализатора кода, как преодолеть эту проблему и как безболезненно постепенно устранить технический долг.

Проблематика


Запустить и посмотреть, как работает статический анализатор, как правило, несложно [1]. Можно увидеть в коде интересные ошибки или даже страшные потенциальные уязвимости. Можно даже что-то поправить, но вот дальше у многих программистов опускаются руки.

Все статические анализаторы выдают ложные срабатывания. Такова особенность методологии статического анализа кода, и с ней ничего нельзя сделать. В общем случае это нерешаемая задача, что подтверждается теоремой Райса [2]. Не помогут и алгоритмы машинного обучения [3]. Если даже человек не всегда может сказать, ошибочен тот или иной код, то не стоит ждать этого от программы :).

Ложные срабатывания не являются проблемой, если статический анализатор уже настроен:
  • Отключены неактуальные наборы правил;
  • Отключены отдельные неактуальные диагностики;
  • Если мы говорим о C или C++, то размечены макросы, содержащие специфические конструкции, из-за которых появляются бесполезные предупреждению в каждом месте, где такие макросы используются;
  • Размечены собственные функции, выполняющие действия аналогичные системным функциям (свой аналог memcpy или printf) [4];
  • Точечно с помощью комментариев отключены ложные срабатывания;
  • И так далее.

В таком случае, можно ожидать низкий уровень ложных срабатываний на уровне порядка 10-15% [5]. Другим словами, 9 из 10 предупреждений анализатора будут указывать на реальную проблему в коде или, по крайней мере, на код с сильным запахом. Согласитесь, такой сценарий крайне приятен, и анализатор является настоящим другом программиста.
Предупреждения

В реальности в большом проекте первоначальная картина будет совсем иная. Анализатор выдаёт сотни или тысячи предупреждений на legacy код. Что из этих предупреждений по делу, а что нет, быстро понять невозможно. Сесть и начать разбираться со всеми этими предупреждениями нерационально, так как основная работа в этом случае остановится на дни или недели. Как правило, команда не может позволить себе такой сценарий. Также возникнет огромное количество diff-ов, которые портят историю изменений. А быстрая массовая правка такого количества фрагментов в коде неизбежно выльется в новые опечатки и ошибки.

И самое главное, подобный подвиг в борьбе с предупреждениями имеет мало смысла. Согласитесь, что раз проект много лет успешно работает, то и большинство критических ошибок в нём уже исправлено. Да, эти исправления были очень дорогими, приходилось отлаживаться, получать негативные отзывы пользователей о багах и так далее. Статический анализатор помог бы исправить многие из этих ошибок ещё на этапе написания кода, быстро и дешево. Но в данный момент, так или иначе эти ошибки исправлены, и анализатор в основном обнаруживает некритические ошибки в старом коде. Этот код может не использоваться, использоваться очень редко, и ошибка в нём может не приводить к заметным последствиям. Возможно, где-то тень от кнопки не того цвета, но пользоваться продуктом это никому не мешает.

Конечно, даже несерьезные ошибки всё равно остаются ошибками. А иногда за ошибкой может прятаться настоящая уязвимость. Однако, бросить всё и днями/неделями заниматься дефектами, которые слабо проявляют себя, выглядит сомнительной идеей.

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

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

Здесь та же аналогия, что и с предупреждениями компилятора. Неспроста рекомендуют держать количество предупреждений компилятора на уровне 0. Если предупреждений 1000, то, когда их станет 1001, на это никто не обратит внимание, да и не понятно, где искать это самое новое предупреждение.
Неправильное внедрение статического анализа

Самое худшее в этой истории, если кто-то сверху в этот момент заставит использовать статический анализ кода. Это только демотивирует команду, так как с их точки зрения появится дополнительная бюрократическая сложность, которая только мешает. Отчёты анализатора никто не будет смотреть, и всё использование будет только на бумаге. Т.е. формально анализ встроен в DevOps процесс, но на практике от этого никому никакой пользы нет. Мы слышали подробные истории при общении на стендах от посетителей конференций. Подобный опыт может надолго, если не на всегда, отбить желание у программистов связываться с инструментами статического анализа.

Внедрение и устранение технического долга


На самом деле, нет ничего сложного и страшного во внедрении статического анализа даже в большой старый проект.

CI/CD


Причём анализатор можно сразу сделать частью процесса непрерывной разработки. Например, в дистрибутиве PVS-Studio есть утилиты для удобного просмотра отчёта в нужном вам формате, и уведомления разработчиков, написавших проблемные участки кода. Тем, кто более подробно интересуется запуском PVS-Studio из-под CI/CD систем, рекомендую ознакомиться с соответствующим разделом документации и циклом статей:
Но вернемся к вопросу большого количества ложных срабатываний на первых этапах внедрения инструментов анализа кода.

Фиксация существующего технического долга и работа с новыми предупреждениями


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

Чтобы быстро начать использовать статический анализ, мы предлагаем пользователям PVS-Studio воспользоваться механизмом массового подавления предупреждений [6]. Общая идея в следующем. Пользователь запустил анализатор и получил множество предупреждений. Раз проект, разрабатываемый много лет, жив, развивается и приносит деньги, то, скорее всего, в отчёте не будет много предупреждений, указывающих на критические дефекты. Другими словами, критические баги так или иначе уже поправлены более дорогими способами или благодаря фидбеку от клиентов. Соответственно, всё, что сейчас находит анализатор, можно считать техническим долгом, который непрактично стараться устранить сразу.

Можно указать PVS-Studio считать эти предупреждения пока неактуальными (отложить технический долг на потом), и он больше не будет их показывать. Анализатор создаёт специальный файл, где сохраняет информацию о пока неинтересных ошибках. И теперь PVS-Studio будет выдавать предупреждения только на новый или измененный код. Причем, всё это реализовано умно. Если, например, в начало файла с исходным кодом будет добавлена пустая строка, то анализатор понимает, что, по сути, ничего не изменилось, и по-прежнему будет молчать. Этот файл разметки можно заложить в систему контроля версий. Файл большой, но это не страшно, так как часто его закладывать смысла нет.

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

Итак, первая проблема, с внедрением анализатора в большой старый проект решена. Теперь давайте разберёмся, что делать с техническим долгом.

Исправление ошибок и рефакторинг


Самое простое и естественное это выделить некоторое время на разбор массово подавленных предупреждений анализатора и постепенно разбираться с ними. Где-то следует исправить ошибки в коде, где-то провести рефакторинг, чтобы подсказать анализатору, что код не является проблемным. Простой пример:
if (a = b)

На подобный код ругается большинство С++ компиляторов и анализаторов, так как высока вероятность, что на самом деле хотели написать (a == b). Но существует негласное соглашение, и это обычно отмечено в документации, что если стоят дополнительные скобки, то считается, что программист сознательно написал такой код, и ругаться не надо. Например, в документации PVS-Studio к диагностике V559 (CWE-481) явно написано, что следующая строчка будет считаться корректной и безопасной:
if ((a = b))

Другой пример. Забыт ли в этом C++ коде break или нет?
case A:  foo();case B:  bar();  break;

Анализатор PVS-Studio выдаст здесь предупреждение V796 (CWE-484). Возможно, это не ошибка, и тогда следует дать анализатору подсказку, добавив атрибут [[fallthrough]] или, например, __attribute__((fallthrough)):
case A:  foo();  [[fallthrough]];case B:  bar();  break;

Можно сказать, что подобные изменения кода не исправляют ошибки. Да, это так, но выполняется два полезных действия. Во-первых, отчёт анализатора избавляется от ложных срабатываний. Во-вторых, код становится более понятным для людей, занимающихся его сопровождением. И это очень важно! Ради одного этого уже стоит проводить мелкий рефакторинг, чтобы код стал понятней и легче в сопровождении. Раз анализатору непонятно, нужен break или нет, это будет непонятно и коллегам-программистам.

Помимо исправления ошибок и рефакторинга, можно точечно подавлять явно ложные предупреждения анализатора. Какие-то неактуальные диагностики можно отключить. Например, кто-то считает бессмысленными предупреждения V550 о сравнении значений типа float/double. А кто-то относит их к важным и заслуживающим изучения [7]. Какие предупреждения считать актуальными, а каким нет, решать только команде разработчиков.

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

Также, мы всегда помогаем нашим клиентам настроить PVS-Studio, если возникают какие-то сложности. Более того, были случае, когда мы сами устраняли ложные предупреждения и исправляли ошибки [8]. На всякий случай решил упомянуть, что возможен и такой вариант расширенного сотрудничества :).

Метод храповика


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

Храповик


Фиксируется количество предупреждений, которое выдаёт статический анализатор. Quality gate настраивается таким образом, что теперь можно заложить только такой код, который не увеличивает количество срабатываний. В результате запускается процесс постепенного уменьшения количества срабатываний за счёт настройки анализатора и правки ошибок.

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

Более подробно эта методология описана в очень интересной статье Ивана Пономарева "Внедряйте статический анализ в процесс, а не ищите с его помощью баги", с которой я рекомендую ознакомиться каждому, кто интересуется вопросами повышения качества кода.

У автора статьи есть и доклад на эту тему: "Непрерывный статический анализ".

Заключение


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

Существуют и другие типовые сомнения, сможет ли статический анализатора действительно быть удобен и полезен. Я постарался развеять большинство таких сомнений в публикации Причины внедрить в процесс разработки статический анализатор кода PVS-Studio [9].

Спасибо за внимание, и приходите скачать и попробовать анализатор PVS-Studio.

Дополнительные ссылки


  1. Андрей Карпов. Как быстро посмотреть интересные предупреждения, которые выдает анализатор PVS-Studio для C и C++ кода?
  2. Wikipedia. Теорема Райса.
  3. Андрей Карпов, Виктория Ханиева. Использование машинного обучения в статическом анализе исходного кода программ.
  4. PVS-Studio. Документация. Дополнительная настройка диагностик.
  5. Андрей Карпов. Характеристики анализатора PVS-Studio на примере EFL Core Libraries, 10-15% ложных срабатываний.
  6. PVS-Studio. Документация. Массовое подавление сообщений анализатора.
  7. Иван Андряшин. О том, как мы опробовали статический анализ на своем проекте учебного симулятора рентгенэндоваскулярной хирургии.
  8. Павел Еремеев, Святослав Размыслов. Как команда PVS-Studio улучшила код Unreal Engine.
  9. Андрей Карпов. Причины внедрить в процесс разработки статический анализатор кода PVS-Studio.



Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov. How to introduce a static code analyzer in a legacy project and not to discourage the team.
Подробнее..

Почему обзоры кода это хорошо, но недостаточно

23.09.2020 18:06:49 | Автор: admin
image1.png

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

Попробуйте найти ошибку в коде функции, взятой из библиотеки structopt:

static inline bool is_valid_number(const std::string &input) {  if (is_binary_notation(input) ||      is_hex_notation(input) ||      is_octal_notation(input)) {    return true;  }  if (input.empty()) {    return false;  }  std::size_t i = 0, j = input.length() - 1;  // Handling whitespaces  while (i < input.length() && input[i] == ' ')    i++;  while (input[j] == ' ')    j--;  if (i > j)    return false;  // if string is of length 1 and the only  // character is not a digit  if (i == j && !(input[i] >= '0' && input[i] <= '9'))    return false;  // If the 1st char is not '+', '-', '.' or digit  if (input[i] != '.' && input[i] != '+' && input[i] != '-' &&      !(input[i] >= '0' && input[i] <= '9'))    return false;  // To check if a '.' or 'e' is found in given  // string. We use this flag to make sure that  // either of them appear only once.  bool dot_or_exp = false;  for (; i <= j; i++) {    // If any of the char does not belong to    // {digit, +, -, ., e}    if (input[i] != 'e' && input[i] != '.' &&        input[i] != '+' && input[i] != '-' &&        !(input[i] >= '0' && input[i] <= '9'))      return false;    if (input[i] == '.') {      // checks if the char 'e' has already      // occurred before '.' If yes, return false;.      if (dot_or_exp == true)        return false;      // If '.' is the last character.      if (i + 1 > input.length())        return false;      // if '.' is not followed by a digit.      if (!(input[i + 1] >= '0' && input[i + 1] <= '9'))        return false;    }    else if (input[i] == 'e') {      // set dot_or_exp = 1 when e is encountered.      dot_or_exp = true;      // if there is no digit before 'e'.      if (!(input[i - 1] >= '0' && input[i - 1] <= '9'))        return false;      // If 'e' is the last Character      if (i + 1 > input.length())        return false;      // if e is not followed either by      // '+', '-' or a digit      if (input[i + 1] != '+' && input[i + 1] != '-' &&          (input[i + 1] >= '0' && input[i] <= '9'))        return false;    }  }  /* If the string skips all above cases, then  it is numeric*/  return true;}

Чтобы случайно сразу не прочитать ответ, добавлю картинку.

image2.png

Не знаю, нашли Вы ошибку или нет. Даже если нашли, то уверен согласитесь, что найти такую опечатку непросто. Тем более, вы знали, что в функции есть ошибка. Если не знать, то сложно заставить внимательно читать и проверять весь этот код.

В таких ситуациях статический анализатор кода отлично дополнит классический обзор кода. Анализатор не устаёт и дотошно проверит весь код. Как результат, в этой функции анализатор PVS-Studio замечает аномалию и выдаёт предупреждение:

V560 A part of conditional expression is always false: input[i] <= '9'. structopt.hpp 1870

Для тех, кто не заметил ошибку, дам пояснения. Самое главное:

else if (input[i] == 'e') {  ....  if (input[i + 1] != '+' && input[i + 1] != '-' &&      (input[i + 1] >= '0' && input[i] <= '9'))      return false;}

Вышестоящее условие проверяет, что i-тый элемент является буквой 'e'. Соответственно следующая проверка input[i] <= '9' не имеет смысла. Результат второй проверки всегда false, о чём и предупреждает инструмент статического анализа. Причина ошибки проста: человек поспешил и опечатался, забыв написать +1.

Фактически получается, что функция не до конца выполняет свою работу по проверке корректности введённых чисел. Правильный вариант:

else if (input[i] == 'e') {  ....  if (input[i + 1] != '+' && input[i + 1] != '-' &&      (input[i + 1] >= '0' && input[i + 1] <= '9'))      return false;}

Интересное наблюдение. Эту ошибку можно рассматривать как разновидность "эффекта последней строки". Ошибка допущена в самом последнем условии функции. В конце внимание программиста ослабло, и он допустил эту малозаметную ошибку.


Если вам понравится статья про эффект последней строки, то рекомендую познакомиться с другими аналогичными наблюдениями: 0-1-2, memset, сравнения.

Всем пока. Ставлю лайк тем, кто самостоятельно нашёл ошибку.
Подробнее..

CodeQL SAST своими руками (и головой). Часть 1

09.02.2021 16:13:11 | Автор: admin

Привет Хабр!

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

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

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

Также я активно призываю попробовать этот инструмент на своих собственных кодовых базах и помочь совместно создать сообщество CodeQL-экспертов (и вообще специалистов по анализу кода) для обмена опытом и наработками, обсуждения проблем и их возможных решений - ссылка в конце статьи.

Содержание

1. Что такое CodeQL
2. Сценарии использования CodeQL
3. Демонстрация работы
4. Консоль LGTM
5. Установка CodeQL
6. Кодовая база
7. Как выглядит простой CodeQL запрос
8. Что дальше?
9. Дополнительные материалы

Что такое CodeQL

CodeQL это open-source инструмент и язык запросов, немного напоминающий SQL и позволяющий программным образом обращаться к тем или иным участкам кода и выполнять заданные аналитиками проверки графа потоков данных/управления и структуры исходного кода в целом. Аналогом этому подходу являются конфигурируемые правила поиска уязвимостей в инструментах SAST (Static Application Security Testing).

Проще говоря, это инструмент, при помощи которого можно проверить некоторые гипотезы относительно кода или идущих через него данных. Например можно составить запросы, которые проверят есть ли путь для данных от места пользовательского ввода до небезопасного участка кода, где эти данные выводятся. С точки зрения исследования кода на качество можно, например, найти все функции, принимающие на вход более 5 аргументов или найти пустые циклы for/while.

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

Изначально CodeQL это разработка компании Semmle, которая в сентябре 2020 была куплена GitHub и внедрена в их Security Lab. С этого момента развитием продукта занимается сам GitHub и небольшое сообщество энтузиастов. На данный момент официальный репозиторий содержит суммарно свыше 2000 QL-запросов, которые покрывают большое количество разнообразных проблем с кодом, начиная от поиска некорректных регулярных выражений в JavaScript и заканчивая обнаружением использования небезопасных криптографических алгоритмов в коде на C++.

Одним из преимуществ CodeQL является то, что он может не просто искать проблемные участки кода по определенному шаблону (как, например, инструмент SemGrep), но также и понимать структуру кода на уровне отдельных инструкций и выражений, отличать вызов функции от вызова метода класса, а также отслеживать путь прохождения значения через выражения и дальнейшие операции с этими выражениями (например присвоение значения переменной, вызов функции с этой переменной в качестве параметра и последующее присвоение результата новой переменной).

На данный момент с разной степенью полноты поддерживаются следующие языки: C/C++, C#, Java, Go, Python, JavaScript/TypeScript. Помимо этого для каждого языка есть набор поддерживаемых фреймворков, упрощающий написание запросов.

CodeQL предоставляется в нескольких вариантах:

  1. Консольная утилита, позволяющая встроить проверки в CI/CD цикл и осуществлять сканирование кода из командной строки.

  2. Расширение для Visual Studio Code для удобного написания и ad-hoc исполнения запросов.

  3. Онлайн-консоль LGTM, позволяющая писать запросы и проводить тестовое сканирование приложения из заданного GitHub-репозитория.

Кроме этого можно подключить сканирование своих репозиториев непосредственно в CI/CD на GitHub.

Всё ещё не убеждены попробовать? Тогда подкину дополнительную мотивацию. GitHub проводит соревнования CTF и вознаграждаемые bug bounty программы для энтузиастов, которые предлагают новые запросы, помогающие обнаруживать как уже известные и документированные уязвимости (CVE), так и 0-day уязвимости.

Сценарии использования CodeQL

Давайте посмотрим, какие есть потенциальные варианты использования CodeQL в нашем проекте:

  1. Самый простой сценарий состоит в том, что мы просто запускаем сканер со всем набором стандартных запросов и разбираем результаты, среди которых будут и проблемы с качеством кода, и проблемы с безопасностью.

  2. Сценарий посложнее включает в себя два этапа запуска сканера со встроенными запросами. Первый только с запросами, относящимися к качеству кода (пустые блоки, избыточные комментарии, большое количество параметров функции и т. п.), а второй с запросами непосредственно на проверку безопасности. В дальнейшем результаты разбирают две независимые группы ответственных сотрудников.

  3. Самый продуктивный сценарий включает в себя модификацию базовых запросов и/или написание собственных новых запросов, исходя из специфики конкретного приложения и появляющихся угроз.
    Например при выходе очередной 0-day уязвимости аналитик безопасности может составить запрос, который будет проверять все проекты на ее наличие. Или при анализе дефектов, найденных в процессе внутреннего аудита, каждый такой дефект может быть переписан на языке QL, чтобы не допустить проблем в других проектах.

  4. Также CodeQL может быть использован для исследования кода в целом (например определить все точки входа в приложение, чтобы впоследствии эту информацию передать аналитикам, занимающимся динамическим анализом приложения).

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

Демонстрация работы

Впрочем давайте сразу посмотрим, как выглядит синтаксис запроса и результат работы CodeQL на примере запроса в консоли LGTM.

Ниже представлен простой вариант запроса, при помощи которого мы ищем все пустые блоки кода на некоем тестовом приложении. Дальше в статье мы более детально посмотрим что есть что здесь.

Простой запрос CodeQL по поиску пустых блоковПростой запрос CodeQL по поиску пустых блоковОбнаруженный пустой участок кодаОбнаруженный пустой участок кода

Или более сложный случай, когда мы ищем проблемы с Cross-Site Scripting:

Запрос CodeQL, обнаруживающий XSS путём отслеживания путей недоверенных данныхЗапрос CodeQL, обнаруживающий XSS путём отслеживания путей недоверенных данных

Результат включает в себя все промежуточные шаги (в данном примере только стартовый и конечный), которые привели данные от места ввода до вывода и участки кода, в которых заключена проблема:

А вот так тот же результат выглядит в расширении для VSCode:

На приведенном скриншоте мы видим инструкции CodeQL (верхняя панель), которые отслеживают путь данных от удалённых точек ввода пользовательских данных (например параметры GET-запросов) до конструкций кода, способных отобразить эти недоверенные данные пользователю. При этом отдельной инструкцией isSanitizer указывается, что в коде присутствует санитизирующая функция и соответственно если поток подозрительных данных проходит через эту функцию, он не должен дальше восприниматься как недоверенный. Это один из нескольких способов, которыми мы можем уменьшить количество заведомо ложных срабатываний.

В свою очередь, в результатах выполнения запроса (нижняя панель) мы можем посмотреть участок кода, где в приложении появляются недоверенные данные (т. н. source) и участок кода, где они выводятся (т. н. sink).

Консоль LGTM

Чтобы поэкспериментировать с языком без локальной установки пакета CodeQL можно воспользоваться онлайн-консолью LGTM (Looks Good To Me). Она включает в себя простой редактор запросов и возможность выполнить этот запрос на уже предустановленных кодовых базах нескольких open-source проектов, либо на своем GitHub-проекте.

Давайте сразу попробуем исполнить простейшие запросы и начать практическое знакомство с CodeQL:

  1. Переходим в онлайн-консоль: https://lgtm.com/query/.

  2. Выбираем в качестве языка JavaScript, а в качестве проекта meteor/meteor.

  3. Копируем нижеприведенные запросы.

  4. Нажимаем Run и смотрим результаты в панели под полем ввода кода.

Простой запрос, отображающий все места в анализируемом исходном коде, где объявляются классы выглядит так:

import javascriptfrom ClassExpr ceselect ce

Более сложный запрос, который покажет нам все места в файле client.js, где происходит вызов функции eval(), а также аргументы этой функции:

import javascriptfrom CallExpr callwhere call.getCalleeName() = "eval" and call.getLocation().getFile().getRelativePath().matches("%client.js")select call, call.getAnArgument()

Еще более продвинутые запросы позволяют задать точку входа данных (например конкретный параметр некоторой функции) и точку выхода (например параметр той же функций eval()) для того, чтобы отследить проходят ли данные от начальной до конечной точки. Но про такие запросы мы поговорим в следующей части статьи.

Установка CodeQL

Для использования в своих проектах на постоянной основе консоль LGTM не очень удобна, поэтому есть смысл установить CodeQL CLI и библиотеки локально.

Процесс установки всего пакета в целом несложен, но требует понимания ряда нюансов.

Простой вариант, с которого можно начать, выглядит так:

  1. Установить VSCode и CodeQL extension.

  2. Скачать и распаковать CodeQL CLI в директорию, например, codeql.

  3. Прописать путь до директории codeql в %PATH%.

  4. Скачать стартовый воркспейс VSCode для работы с CodeQL (впоследствии можно будет сделать свой, но для начала работы можно использовать готовый):
    git clone https://github.com/github/vscode-codeql-starter/

    git submodule update --init --remote

    В нем мы будем работать (то есть писать наши запросы) в папке, соответствующей интересующему нас языку (например для JS это codeql-custom-queries-javascript).

  5. Скачиваем тестовую кодовую базу (то есть базу, в которой определенным образом хранятся все необходимые данные о коде и внутренних взаимосвязях в нем, о чем подробнее будет рассказано ниже), например (для JS) https://github.com/githubsatelliteworkshops/codeql/releases/download/v1.0/esbenabootstrap-pre-27047javascript.zip
    Чуть ниже мы посмотрим как создавать свои собственные кодовые базы для наших проектов.

  6. Опционально распаковываем архив с кодовой базой.

  7. В VSCode выбираем Open workspace и открываем файл стартового воркспейса.

  8. В VSCode на закладке CodeQL добавляем папку (или архив) с кодовой базой, против которой будет запускаться анализ кода.

  9. Готово. Теперь в соответствующей папке воркспейса (см. шаг 4) можно открыть файл example.ql и в нем начать писать свои запросы.

  10. Исполняем тестовый запрос и убеждаемся, что всё работает

import javascriptfrom Expr eselect Wazzup!

Кодовая база

В CodeQL весь анализируемый код должен быть специальным образом организован в т. н. кодовую базу, к которой мы будем впоследствии выполнять запросы. В ней содержится полное иерархическое представление этого кода, включая абстрактное синтаксическое дерево (AST), граф потока данных и граф потока управления. Языковые библиотеки CodeQL задают классы, которые добавляют уровень абстракции относительно таблиц в этой базе. Другими словами у нас появляется возможность писать запросы к кодовой базе, используя принципы ООП. Это как раз та особенность, которая отличает CodeQL от инструментов, которые ищут проблемы в коде при помощи заранее заданных шаблонов и regex'ов.

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

Для разных языков процесс создания базы немного отличается. Например создание кодовой базы для JS в папке my-js-codebase выполняется следующей командой в директории, которая содержит исходный код:

codeql database create my-js-codebase --language=javascript

Для компилируемых языков требуется, чтобы в системе был соответствующий компилятор и сборщик (например Maven для Java)

Следующий шаг загрузить информацию о базе в VSCode. Это делается в редакторе на соответствующей вкладке CodeQL Choose Database from Folder

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

Как выглядит простой CodeQL запрос

Давайте разберем, из чего вообще состоит типичный запрос в CodeQL на примере кодовой базы на языке JavaScript.

Самый базовый запрос, который выводит все jQuery-функции с именем $ (типа $(arg1, arg2)) и их первый аргумент, выглядит так, как показано ниже. Вы можете самостоятельно выполнить его для любой кодовой базы с jQuery:

/*** @name QueryName* @kind problem* @id my_id_1*/// метаданныеimport javascript // Выражение для подключения библиотеки CodeQL для работы с конструкциями JavaScript.// Также есть возможность подключения других библиотек для работы с различными фреймворками и технологиями.// Например semmle.javascript.NodeJS или semmle.javascript.frameworks.HTTP.from CallExpr dollarCall, Expr dollarArg // Объявление переменной dollarCall типа CallExpr и переменной dollarArg типа Expr.// CallExpr - это класс из стандартной библиотеки, представляющий коллекцию всех вызовов функций в интересующем нас коде.// Expr - класс, представляющий коллекцию всех выражений. Например в Object.entries = function(obj) выражениями являются //   вся строка целиком, Object, Object.entries, entries, function(obj), obj.where dollarCall.getCalleeName() = "$"// Логические формулы, которые мы применяем к объявленным переменным.// Мы проверяем, что результат выполнения предиката (т.е. логической инструкции) getCaleeName() (который возвращает название // вызываемой функции) нашего объекта dollarCall (который содержит все вызовы функций) равен "$"and dollarArg = dollarCall.getArgument(0)// Эта логическая формула операцией AND соединяется с предыдущей и уточняет условие, применяемое в запросе.// В итоге мы из всех вызовов функций, в названии которых есть $ выбираем в переменную //  dollarArg первые аргументы (как сущности, а не как конкретные значения аргументов).select dollarCall, dollarArg // указание на то, какие выражения (значение каких переменных или предикатов) мы хотим вывести в результате.

Как вы можете заметить, синтаксис языка схож с синтаксисом SQL, но при этом в основе лежат концепции ООП. В последующих частях мы познакомимся чуть поглубже с нюансами, терминами и идеями, которые лежат в основе CodeQL.

Что дальше?

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

Рекомендую поэкспериментировать с запросами на разных приложениях (либо собственных, либо open-source) хотя бы в онлайн-консоли LGTM.

Помимо это есть два неплохих обучающих мини-курса от самих разработчиков, которые помогут вам лучше понять основы и базовые механики CodeQL. Они не требуют знания соответствующих языков, но раскрывают возможности CodeQL для анализа кода. Советую их посмотреть и при желании пройти до конца:

https://lab.github.com/githubtraining/codeql-u-boot-challenge-(cc++) интерактивный курс по работе с CodeQL на примере C/C++

https://lab.github.com/githubtraining/codeql-for-javascript:-unsafe-jquery-plugin интерактивный курс по анализу JavaScript Bootstrap с помощью CodeQL.

Плюс к этому для начала подойдет очень полезный двухчасовой мастер-класс от GitHub, на котором рассматривается база CodeQL и где при помощи лектора зритель учится шаг за шагом писать запрос для поиска небезопасной десериализации в коде Java-приложения (фреймворк XStream):

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

It is dangerous to go alone! CodeQL инструмент достаточно сложный, с большим количеством нюансов и, к сожалению, с пока еще не очень большой экспертизой в мире. Поэтому мы бы хотели уже сейчас заняться развитием русскоязычного сообщества экспертов в CodeQL для обмена опытом и совместного решения проблем (которые, разумеется, тоже существуют). Для этой цели мы создали отдельный канал в Telegram, посвященный обсуждениям нюансов этого инструмента и расширению круга экспертизы. Там же мы публикуем новости, обучающие материалы и другую информацию по CodeQL.

Присоединяйтесь - https://t.me/codeql !

Дополнительные материалы

Нижеприведенные ссылки помогут найти основную часть информации по языку, его стандартным библиотекам и возможностям:

https://help.semmle.com/codeql/ общая помощь по CodeQL от изначальных разработчиков.
https://help.semmle.com/QL/ql-handbook/ референс по синтаксису языка.
https://help.semmle.com/QL/learn-ql/ детальная помощь по CodeQL для разных языков.
https://securitylab.github.com/get-involved информация по тому, как можно узнать больше про CodeQL, помочь его развитию, а также по тому, как получить инвайт в Slack-канал (англоязычный) с ведущими экспертами со всего мира и разработчиками самого CodeQL.

Подробнее..

PVS-Studio, Blender цикл заметок о пользе регулярного использования статического анализа

03.03.2021 20:06:54 | Автор: admin

PVS-Studio мониторит код Blender
В статьях мы регулярно повторяем важную мысль: статический анализатор должен использоваться регулярно. В этом случае многие ошибки выявляются на самом раннем этапе, а их исправление максимально дёшево. Однако теория это одно, но намного лучше подкреплять слова практическими примерами. Рассмотрим несколько свежих ошибок, появившихся в новом коде проекта Blender.


Недавно мы настроили регулярную проверку проекта Blender, о чём мой коллега рассказал в статье "Just for fun: команда PVS-Studio придумала мониторить качество некоторых открытых проектов". В дальнейшем планируем начать мониторить ещё некоторые интересные проекты.


Сразу скажу, что мы не ставим перед собой задачу найти как можно больше ошибок. Целью является периодическое написание небольших заметок (таких как эта), в которых мы будем на практике показывать достоинства регулярного анализа кода. Другими словами, мы иногда будем описывать некоторые интересные ошибки в новом коде, найденные при очередном ночном запуске PVS-Studio, и тем самым популяризировать правильное использование методологии статического анализа кода.


Итак, давайте посмотрим, что найдено в свежем коде проекта Blender.


Фрагмент первый: double-checked locking


typedef struct bNodeTree {  ....  struct NodeTreeUIStorage *ui_storage;} bNodeTree;static void ui_storage_ensure(bNodeTree &ntree){  /* As an optimization, only acquire a lock if the UI storage doesn't exist,   * because it only needs to be allocated once for every node tree. */  if (ntree.ui_storage == nullptr) {    std::lock_guard<std::mutex> lock(global_ui_storage_mutex);    /* Check again-- another thread may have allocated the storage       while this one waited. */    if (ntree.ui_storage == nullptr) {      ntree.ui_storage = new NodeTreeUIStorage();    }  }}

Предупреждение PVS-Studio. V1036: Potentially unsafe double-checked locking. node_ui_storage.cc 46


Перед нами неправильная реализация блокировки с двойной проверкой. Для пояснения проблемы процитирую фрагмент статьи "C++ and the Perils of Double-Checked Locking", написанной Scott Meyers и Andrei Alexandrescu ещё в 2004 году. Как видите, проблема давно известна, но это не защищает разработчиков от того, чтобы наступать на одни и те же грабли. Хорошо, что анализатор PVS-Studio помогает выявлять подобные проблемы :). Итак, фрагмент из статьи:


Consider again the line that initializes pInstance: pInstance = newSingleton;

This statement causes three things to happen:

Step 1: Allocate memory to hold a Singleton object.

Step 2: Construct a Singleton object in the allocated memory.

Step 3: Make pInstance point to the allocated memory.

Of critical importance is the observation that compilers are not constrainedto perform these steps in this order! In particular, compilers are sometimes allowed to swap steps 2 and 3. Why they might want to do that is a question we'll address in a moment. For now, let's focus on what happens if they do.

Consider the following code, where we've expanded pInstance's initialization line into the three constituent tasks we mentioned above and where we've merged steps 1 (memory allocation) and 3 (pInstance assignment) into a single statement that precedes step 2 (Singleton construction). The idea is not that a human would write this code. Rather, it's that a compiler might generate code equivalent to this in response to the conventional DCLP source code (shown earlier) that a human would write.

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


Подобные ошибки очень коварны! Они могут очень редко проявлять себя. Программа вроде как работает, проходит все тесты и так далее. Но время от времени она неожиданно падает у пользователей, и понять причину может быть крайне проблематично. Воспроизвести такую ошибку может быть очень сложно. Другим словами, исправление этой ошибки по жалобе от пользователей может обойтись в 1000 раз дороже, чем правка кода, сделанная по результату анализа кода с помощью PVS-Studio или аналогичного инструмента.


Примечание. Ошибка может и не существовать сейчас в двоичном коде, и всё зависит от компилятора, и ключей оптимизации. Однако, даже если сейчас всё работает хорошо, это не значит, что так будет всегда. Ошибка может проявить себя при смене компилятора/ключей оптимизации.


Фрагмент второй: realloc


static void icon_merge_context_register_icon(struct IconMergeContext *context,                                             const char *file_name,                                             struct IconHead *icon_head){  context->read_icons = realloc(context->read_icons,    sizeof(struct IconInfo) * (context->num_read_icons + 1));  struct IconInfo *icon_info = &context->read_icons[context->num_read_icons];  icon_info->head = *icon_head;  icon_info->file_name = strdup(path_basename(file_name));  context->num_read_icons++;}

Анализатор PVS-Studio выдаёт здесь два предупреждения, и это правильно. Здесь действительно допущено сразу две ошибки различного плана.


Первая: V701: realloc() possible leak: when realloc() fails in allocating memory, original pointer 'context->read_icons' is lost. Consider assigning realloc() to a temporary pointer. datatoc_icon.c 252


Если память не удастся выделить, функция realloc вернёт значение NULL. Нулевой указатель будет записан в переменную context->read_icons, а её предыдущее значение будет потеряно. Раз предыдущее значение указателя потеряно, то и невозможно освободить ранее выделенный блок памяти, на который ссылался этот указатель. Произойдёт утечка памяти.


Вторая: V522: There might be dereferencing of a potential null pointer 'context->read_icons'. Check lines: 255, 252. datatoc_icon.c


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


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


Фрагмент третий: разыменование указателя для проверки


static int node_link_invoke(bContext *C, wmOperator *op, const wmEvent *event){  ....  bNodeLinkDrag *nldrag = node_link_init(bmain, snode, cursor, detach);  nldrag->last_picked_multi_input_socket_link = NULL;  if (nldrag) {    op->customdata = nldrag;  ....}

Предупреждение PVS-Studio: V595: The 'nldrag' pointer was utilized before it was verified against nullptr. Check lines: 1037, 1039. node_relationships.c


Один из самых частых паттернов ошибок (proof). В начале указатель nldrag разыменовывается. Но из следующего условного оператора становится видно, что на самом деле этот указатель может быть нулевым.


Всё просто и понятно. Но, согласитесь, намного лучше поправить такую ошибку сразу, ещё на этапе написания кода, а не после того, когда на неё наткнётся тестировщик или пользователь.


Кстати, нашлась ещё одна такая-же ошибка, но описывать её неинтересно. Приведу только сообщение: V595: The 'seq' pointer was utilized before it was verified against nullptr. Check lines: 373, 385. strip_add.c


Заключение


Используйте статические анализаторы кода регулярно. От этого выиграют как разработчики, так и пользователи. Вы можете скачать и попробовать PVS-Studio здесь. Спасибо за внимание.


Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov. PVS-Studio, Blender: Series of Notes on Advantages of Regular Static Analysis of Code.

Подробнее..

Пример, как в PVS-Studio появляются новые диагностики

20.03.2021 18:12:42 | Автор: admin

Новая Си++ диагностика для PVS-Studio
Пользователи иногда спрашивают, как появляются новые диагностики в статическом анализаторе PVS-Studio. Мы отвечаем, что черпаем вдохновение из разнообразнейших источников: книг, стандартов кодирования, собственных ошибок, писем наших пользователей и так далее. Сегодня мы придумали новую интересную диагностику и решили рассказать историю, как это произошло.


Всё началось с проверки проекта COVID-19 CovidSim Model и статьи про неинициализированную переменную. Проект оказался маленьким и написанным с использованием современного стандарта языка C++. Это значит, что он отлично может пополнить базу тестовых проектов для регрессионного тестирования ядра анализатора PVS-Studio.


Однако, прежде чем пополнить базу новым проектом, полезно просмотреть выдаваемые предупреждения, чтобы выявить новые закономерности (паттерны) ложных срабатываний и выписать их для дальнейшей доработки анализатора. Также это дополнительная возможность заметить, что ещё что-то не так. Например, сообщение неудачно описывает ошибку для определённой конструкции кода.


Программист, которому поручили добавить проект в тестовую базу, подошёл к задаче основательно и решил заглянуть в раздел MISRA-диагностик. Вообще, это делать было необязательно, так как это весьма специфичная группа диагностик, которую нет смысла включать для таких проектов, как CovidSim.


MISRA С и MISRA C++ диагностики предназначены для разработчиков встраиваемых систем, и их суть сводится к ограничению использования небезопасных конструкций программирования. Например, не рекомендуется применять оператор goto (V2502), так как он провоцирует создание сложного кода, в котором легко допустить логическую ошибку. Подробнее с философией стандарта кодирования MISRA можно познакомиться в статье "Что такое MISRA и как её готовить".


Для прикладного программного обеспечения, которым как раз и является проект CovidSim, включать набор MISRA диагностик не имеет смысла. Пользователь просто утонет в огромном количестве малополезных для него сообщений. Например, экспериментируя с этим набором диагностик, мы получали более миллиона предупреждений для некоторых открытых проектов среднего размера. Грубо говоря, с точки зрения MISRA, в каждой третьей строчке кода может быть что-то не так :). Естественно, никто всё это смотреть и тем более править не будет. Проект или сразу разрабатывается с учётом MISRA рекомендаций, или для него этот стандарт кодирования неактуален.


Но мы отвлеклись от темы. Так вот, бегло просматривая MISRA предупреждения, коллега зацепился взглядом за предупреждение V2507, выданное для этого фрагмента кода.


if (radiusSquared > StateT[tn].maxRad2) StateT[tn].maxRad2 = radiusSquared;{  SusceptibleToLatent(a->pcell);  if (a->listpos < Cells[a->pcell].S)  {    UpdateCell(Cells[a->pcell].susceptible, a->listpos, Cells[a->pcell].S);    a->listpos = Cells[a->pcell].S;    Cells[a->pcell].latent[0] = ai;  }}StateT[tn].cumI_keyworker[a->keyworker]++;

Правило V2507 заставляет оборачивать тела условных операторов в фигурные скобки.


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


Давайте присмотримся. Код только кажется корректным, но таковым не является! Фигурные скобки не относятся к оператору if.


Отформатируем код для наглядности:


if (radiusSquared > StateT[tn].maxRad2)  StateT[tn].maxRad2 = radiusSquared;{  SusceptibleToLatent(a->pcell);  ....}

Согласитесь, это красивый баг. Он наверняка войдёт в Top10 C++ ошибок, найденных нами в 2021 году.


И что из этого следует? Подход, диктуемый стандартом MISRA, работает! Да, он заставляет писать везде фигурные скобки. Да, это утомительно. Но это оправданная плата за повышение надёжности встроенных приложений, используемых в медицинской технике, автомобилях и других системах высокой ответственности.


Хорошо, у разработчиков, использующих стандарт MISRA, всё нормально. Однако рекомендовать всем подряд использовать фигурные скобки это плохая идея. Используя такой подход, очень легко довести анализатор до состояния, когда им станет невозможно пользоваться. Будет так много сообщений, что их просто никто не будет смотреть.


Вот мы и добрались для появления идеи для новой диагностики общего назначения. Можно сформулировать следующее правило.


Выдать предупреждение, если для оператора if выполняются следующие условия:


  • весь условный оператор if записан в одну строчку и имеет только then-ветку;
  • следующий statement после if это compound statement, и он находится не на той же строке, что и if.

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


Именно так эта идея сейчас оформлена в нашей системе учёта задач. Возможно, в процессе реализации что-то будет сделано по-другому, но это уже неважно. Главное, появится хорошее диагностическое правило, которое начнёт выявлять новый паттерн ошибки. Далее мы распространим его на C# и Java ядра анализатора PVS-Studio.


Только что мы рассмотрели интересный пример, как было сформулировано новое диагностическое правило, которое затем будет реализовано в PVS-Studio. Скажем спасибо проекту CovidSim, стандарту кодирования MISRA и наблюдательности нашего коллеги.


Спасибо за внимание и следуйте за мной в мир С++ и багов :). Twitter. Facebook.


Дополнительный ссылки:


  1. Технологии, используемые в анализаторе кода PVS-Studio для поиска ошибок и потенциальных уязвимостей.
  2. Под капотом PVS-Studio для Java: разработка диагностик.
  3. Использование машинного обучения в статическом анализе исходного кода программ.

Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov. Example of How New Diagnostics Appear in PVS-Studio.

Подробнее..

Статический анализ baseline файлы vs diff

25.06.2020 12:16:27 | Автор: admin

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


Эта статья не помощник по внедрению. Мы будем говорить о технических деталях: как такие механизмы подавления предупреждений реализуются, какие у разных способов плюсы и минусы.



baseline или так называемый suppress profile


Этот метод имеет несколько названий: baseline файл в Psalm и Android Lint, suppress база (или профиль) в PVS-Studio, code smell baseline в detekt.


Данный файл генерируется линтером при запуске на проекте:


superlinter --create-baseline baseline.xml ./project

Внутри себя он содержит все предупреждения, которые были выданы на этапе создания.


При запуске статического анализатора с baseline.xml, все предупреждения, которые содержатся в этом файле, будут проигнорированы:


superlinter --baseline baseline.xml ./project

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


Обычно, мы хотим достичь следующего:


  • На новый код выдаются все предупреждения
  • На старый код предупреждения выдаются только если его редактировали
  • (опционально) Переносы файлы не должны выдавать предупреждения на весь файл

То, что мы можем конфигурировать в этом подходе это какие поля предупреждения формируют хеш (или "сигнатуру") срабатывания. Чтобы уйти от проблем смещения строк кода, лучше не добавлять номер строки в эти поля.


Вот примерный список того, что вообще может являться полем сигнатуры:


  • Название или код диагностики
  • Текст предупреждения
  • Название файла
  • Строка исходного кода, на которую сработало предупреждение

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


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


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


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


Хорошо подобранный набор признаков увеличивает эффективность baseline подхода.


Коллизии в методе baseline


Допустим, диагностика W104 находит вызовы die в коде.


В проверяемом проекте есть файл foo.php:


function legacy() {  die('test');}

Предположим, используются признаки {имя файла, код диагностики, строка исходного кода}.


Наш анализатор при создании baseline добавляет вызов die('test') в свою базу исключений:


{  "filename": "foo.php",  "diag": "W104",  "line": "die('test');"}

Теперь добавим немного нового кода:


+ function newfunc() {+   die('test');+ }  function legacy() {    die('test');  }

По всем используемым признакам новый вызов die('test') будет распознаваться как игнорируемый код. Совпадение сигнатур для потенциально разных кусков кода мы и называем коллизией.


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


Что делать, если вызов die('test') был добавлен в ту же функцию? Этот вызов может иметь одинаковые соседние строки в обоих случаях, поэтому добавление предыдущей и следующей строки в сигнатуре не поможет.


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


При этом мы теряем некоторую точность: нельзя определить, какая из строк новая, а какая старая. Мы будем ругаться на ту, что будет идти после проигнорированных.


Метод, основанный на diff возможностях VCS


Исходной задачей было выдавать предупреждение только на "новый код". Системы контроля версий как раз могут нам в этом помочь.


Утилита revgrep принимает на stdin поток предупреждений, анализирует git diff и выдаёт на выход только те предупреждения, которые исходят от новых строк.


golangci-lint использует форк revgrep как библиотеку, так что в основе его вычисления diff'а лежат те же алгоритмы.

Если выбран этот путь, придётся искать ответ на следующие вопросы:


  • Как выбрать окно коммитов для вычисления diff?
  • Как будем обрабатывать коммиты, пришедшие из основной ветки (merge/rebase)?

Вдобавок нужно понимать, что иногда мы всё же хотим выдавать предупреждения, выходящие за рамки diff. Пример: вы удалили класс директора по мемам, MemeDirector. Если этот класс упоминался в каких-либо doc-комментариях, желательно, чтобы линтер сообщил об этом.


Нам нужно не только получить правильный набор затронутых строк, но и расширить его так, чтобы найти побочные эффекты изменений во всём проекте.


Окно коммитов тоже можно определить по-разному. Скорее всего, вы не захотите проверять только последний коммит, потому что тогда будет возможно пушить сразу два коммита: один с предупреждениями, а второй для обхода CI. Даже если это не будет происходить умышленно, в системе появляется возможность пропустить критический дефект. Также стоит учесть, что предыдущий коммит может быть взят из основной ветки, в этом случае проверять его тоже не следует.


diff режим в NoVerify


NoVerify имеет два режима работы: diff и full diff.


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


Full diff запускает анализатор дважды: один раз на старом коде, затем на новом коде, а потом фильтрует результаты. Это можно сравнить с генерацией baseline файла на лету с помощью того, что мы можем получить предыдущую версию кода через git. Ожидаемо, этот режим увеличивает время выполнения почти вдвое.


Изначальная схема работы предполагалась такая: на pre-push хуках запускается более быстрый анализ, в режиме обычного diff'а, чтобы люди получали обратную связь как можно быстрее. На CI агентах полный diff. В результате время от времени люди спрашивают, почему на агентах проблемы были найдены, а локально всё чисто. Удобнее иметь одинаковые процессы проверки, чтобы при прохождении pre-push хука была гарантия прохождения на CI фазы линтера.


full diff за один проход


Мы можем делать приближенный к full diff аналог, который не требует двойного анализа кода.


Допустим, в diff попала такая строка:


- class Foo {

Если мы попробуем классифицировать эту строку, то определим её как "Foo class deletion".


Каждая диагностика, которая может как-то зависеть от наличия класса, должна выдавать предупреждение, если имеется факт удаления этого класса.


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


Переименования не требуют дополнительной обработки. Мы считаем, что символ со старым именем был удалён, а с новым добавлен:


- class MemeManager {+ class SeniorMemeManager {

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


Выводы


baseline: простой подход, который используется во многих анализаторах. Очевидным недостатком данного подхода является то, что этот baseline файл нужно будет где-то разместить и, время от времени, обновлять его. Точность подхода зависит от удачного подбора признаков, определяющих сигнатуру предупреждения.


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


baseline diff
+ легко сделать эффективным + не требует хранить файлов
+ простота реализации и конфигурации + проще отличать новый код от старого
- нужно решать коллизии - сложно правильно приготовить

Могут существовать гибридные подходы: сначала берёшь baseline файл, а потом разрешаешь коллизии и вычисляешь смещения строк кода через git.


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

Подробнее..

Статический анализ от знакомства до интеграции

06.08.2020 18:12:42 | Автор: admin
Устав от нескончаемого code review или отладки, временами задумываешься, как бы упростить себе жизнь. И немного поискав, ну или случайно наткнувшись, можно увидеть волшебное словосочетание: "Статический анализ". Давайте посмотрим, что это такое и как он может взаимодействовать с вашим проектом.

Evolution

Собственно говоря, если вы пишете на каком-либо современном языке, тогда, даже не догадываясь об этом, вы пропускали его через статический анализатор. Дело в том, что любой современный компилятор предоставляет пусть и крохотный, но набор предупреждений о потенциальных проблемах в коде. Например, компилируя C++ код в Visual Studio вы можете увидеть следующее:

issues

В этом выводе мы видим, что переменная var так и не была использована нигде в функции. Так что на самом деле вы почти всегда пользовались простеньким статическим анализатором кода. Однако, в отличие от профессиональных анализаторов, таких как Coverity, Klocwork или PVS-Studio, предоставляемые компилятором предупреждения могут указывать только на небольшой спектр проблем.

Если вы не знаете наверняка, что такое статический анализ и как его внедрять, прочтите эту статью, чтобы более подробно ознакомиться с этой методологией.

Зачем нужен статический анализ?


В двух словах: ускорение и упрощение.

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

auto x = obj.x;auto y = obj.y;auto z = obj.z;

Вы написали следующий код:

auto x = obj.x;auto y = obj.y;auto z = obj.x;

Как видите, в последней строке появилась опечатка. Например, PVS-Studio выдаёт следующее предупреждение:

V537 Consider reviewing the correctness of 'y' item's usage.

Если хотите потыкать в эту ошибку руками, то попробуйте готовый пример на Compiler Explorer: *клик*.

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

Однако это явная ошибка. А если разработчик написал неоптимальный код из-за того, что позабыл какую-либо тонкость языка? Или же вовсе допустил в коде undefined behavior? К сожалению, подобные случаи совершенно обыденны и львиная часть времени тратится на то, чтобы отладить специфично работающий код, который содержит опечатки, типичные ошибки или undefined behavior.

Именно для этих ситуаций и появился статический анализ. Это помощник для разработчика, который укажет ему на различные проблемы в коде и объяснит в документации почему так писать не нужно, к чему это может привести и как это исправить. Вот пример как это может выглядеть: *клик*.

Больше интересных ошибок, которые может обнаружить анализатор, вы можете найти в статьях:


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

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

0. Знакомство с инструментом


Всё начинается с пробной версии. Действительно, сложно решиться внедрять что-либо в процесс разработки, если никогда до этого не видел инструмента вживую. Поэтому первым делом стоит скачать пробную версию.

Что вы узнаете на этом этапе:

  • Какие есть способы взаимодействия с анализатором;
  • Совместим ли анализатор с вашей средой разработки;
  • Какие проблемы есть сейчас в ваших проектах.

После того, как вы установили себе всё необходимое, то первым делом стоит запустить анализ всего проекта (Windows, Linux, macOS). В случае с PVS-Studio в Visual Studio вы увидите подобную картину (кликабельно):

list

Дело в том, что обычно на проекты с большой кодовой базой статические анализаторы выдают огромное количество предупреждений. Нет необходимости исправлять их все, так как ваш проект уже работает, а значит эти проблемы не являются критичными. Однако вы можете посмотреть на самые интересные предупреждения и исправить их при необходимости. Для этого нужно отфильтровать вывод и оставить только наиболее достоверные сообщения. В плагине PVS-Studio для Visual Studio это делается фильтрацией по уровням и категориям ошибок. Для наиболее точного вывода оставьте включёнными только High и General (тоже кликабельно):

list

Действительно, 178 предупреждений просмотреть значительно проще, чем несколько тысяч

Во вкладках Medium и Low часто попадаются хорошие предупреждения, однако в эти категории занесены те диагностики, которые имеют меньшую точность (достоверность). Подробнее про уровни предупреждений и варианты работы под Windows можно посмотреть тут: *клик*.

Успешно просмотрев самые интересные ошибки (и успешно исправив их) стоит подавить оставшиеся предупреждения. Это нужно для того, чтобы новые предупреждения не терялись среди старых. К тому же статический анализатор это помощник для программиста, а не список для багов. :)

1. Автоматизация


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

Что вы узнаете на данном этапе:

  • Какие варианты автоматизации предоставляет инструмент;
  • Совместим ли анализатор с вашей сборочной системой.

Так как идеальной документации не существует, иногда приходится писать в поддержку. Это нормально, и мы рады вам помочь. :)

А теперь приступим к сервисам непрерывной интеграции (CI). Любой анализатор можно внедрить в них без каких-либо серьезных проблем. Для этого нужно создать отдельный этап в pipeline, который обычно находится после сборки и юнит-тестов. Делается это при помощи различных консольных утилит. Например, PVS-Studio предоставляет следующие утилиты:


Для интеграции анализа в CI нужно сделать три вещи:

  • Установить анализатор;
  • Запустить анализ;
  • Доставить результаты.

Например, для установки PVS-Studio на Linux (Debian-base) нужно выполнить следующие команды:

wget -q -O - https://files.viva64.com/etc/pubkey.txt \    | sudo apt-key add -sudo wget -O /etc/apt/sources.list.d/viva64.list \  https://files.viva64.com/etc/viva64.list  sudo apt-get update -qqsudo apt-get install -qq pvs-studio

В системах под управлением Windows отсутствует возможность установить анализатор из пакетного менеджера, однако есть возможность развернуть анализатор из командной строки:

PVS-Studio_setup.exe /verysilent /suppressmsgboxes /norestart /nocloseapplications

Подробнее о развёртывании PVS-Studio в системах под управлением Windows можно почитать *тут*.

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

Так как способ запуска зависит от платформы и особенностей проекта, я покажу вариант для C++ (Linux) в качестве примера:

pvs-studio-analyzer analyze -j8 \                            -o PVS-Studio.logplog-converter -t errorfile PVS-Studio.log --cerr -w

Первая команда выполнит анализ, а вторая конвертирует отчёт в текстовый формат, выведет его на экран и вернёт отличный от 0 код возврата в случае наличия предупреждений. Подобный механизм удобно использовать для блокировки сборки при наличии сообщений об ошибках. Однако, вы всегда можете убрать флаг -w и не блокировать сборку, содержащую предупреждения.

Примечание. Текстовый формат это неудобно. Он приводится просто для примера. Обратите внимание на более интересный формат отчёта FullHtml. Он позволяет осуществлять навигацию по коду.

Подробнее про настройку анализа на CI можно прочитать в статье "PVS-Studio и Continuous Integration" (Windows) или "Как настроить PVS-Studio в Travis CI" (Linux).

Хорошо, вы настроили работу анализатора на сборочном сервере. Теперь, если кто-то залил непроверенный код, будет падать этап проверки, и вы сможете обнаружить проблему, однако это не совсем удобно, так как эффективнее проверять проект не после того, как произошло слияние веток, а до него, на этапе pull request'а.

В целом настройка анализа pull request'а не сильно отличается от обычного запуска анализа на CI. За исключением необходимости получить список изменённых файлов. Обычно их можно получить, запросив разницу между ветками при помощи git:

git diff --name-only HEAD origin/$MERGE_BASE > .pvs-pr.list

Теперь нужно передать анализатору на вход этот список файлов. Например, в PVS-Studio это реализовано при помощи флага -S:

pvs-studio-analyzer analyze -j8 \                            -o PVS-Studio.log \                            -S .pvs-pr.list

Подробнее про анализ pull request'ов можно узнать *тут*. Даже если вашего CI нет в списке указанных в статье сервисов, вам будет полезен общий раздел, посвященный теории этого типа анализа.

Настроив анализ pull request'ов вы сможете блокировать содержащие предупреждения коммиты, тем самым создав границу, которую непроверенный код не сможет пересечь.

Это всё безусловно хорошо, однако хотелось бы иметь возможность посмотреть все предупреждения в одном месте. Не только от статического анализатора, но и от юнит-тестов или от динамического анализатора. Для это существуют различные сервисы и плагины. PVS-Studio, например, имеет плагин для интеграции в SonarQube.

2. Интеграция на машины разработчиков


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

Как самый простой вариант разработчики сами могут установить необходимый анализатор. Однако это займёт много времени и отвлечёт их от разработки, поэтому вы можете автоматизировать этот процесс, используя установщик и нужные флаги. Для PVS-Studio есть различные флаги для автоматизированной установки. Впрочем, всегда есть пакетные менеджеры, например, Chocolatey (Windows), Homebrew (macOS) или десятки вариантов для Linux.

Затем нужно будет установить необходимые плагины, например, для Visual Studio, IDEA, Rider etc.

3. Ежедневное использование


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

В случае, если анализатор обнаружит в недавно измененном коде проблемы, то сообщит об этом самостоятельно. Например, PVS-Studio скажет вам об этом при помощи оповещения:

notification

Само собой недостаточно сказать разработчикам использовать инструмент. Нужно как-то им рассказать, что это вообще и как это есть. Вот, например, статьи про быстрый старт для PVS-Studio, однако подобные туториалы вы сможете найти для любого предпочитаемого вами инструмента:


Подобные статьи дают всю необходимую для повседневного использования информацию и не отнимают много времени. :)

Ещё на этапе знакомства с инструментом мы подавили очень много предупреждений во время одного из первых запусков. Увы, но статические анализаторы не идеальны, поэтому время от времени выдают ложные срабатывания. Подавить их обычно легко, например в плагине PVS-Studio для Visual Studio достаточно нажать на одну кнопку:

suppress

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

После интеграции


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

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

А если вы или ваши коллеги всё ещё не уверены, стоит ли внедрять анализатор, то предлагаю сейчас перейти к чтению статьи "Причины внедрить в процесс разработки статический анализатор кода PVS-Studio". В ней разобраны типовые опасения разработчиков о том, что статический анализ будет отнимать их время и так далее.



Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Maxim Zvyagintsev. Static Analysis: From Getting Started to Integration.
Подробнее..

Код ревью как быть хорошим автором

02.03.2021 16:05:07 | Автор: admin

Привет! Меня зовут Сергей Загурский, я работаю в Joom в команде инфраструктуры. В своей практике ревьюера кода я регулярно сталкиваюсь с тем, что автор не понимает, что ревьюер не является волшебным чёрным ящиком, в который можно закинуть любые изменения и получить по ним обратную связь. Ревьюер, как и автор, будучи человеком, обладает рядом слабостей. И автор должен (если, конечно, он заинтересован в качественном ревью), помочь ревьюеру настолько, насколько это возможно.

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

Зачем мы делаем ревью кода

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

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

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

На примитивном уровне ревью кода также является моделью того, как с написанным кодом будет идти взаимодействие в дальнейшем. Если у ревьюера не возникает никаких проблем с пониманием, значит и при чтении или отладке кода в будущем также сложностей не будет. И наоборот, если ревьюер не может ничего понять, то это write only код. Его развитие и отладка будут стоить дороже.

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

Дальше мы сосредоточимся на самой фундаментальной составляющей процесса ревью на понимании кода.

Как ревьюер делает ревью

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

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

Если речь идёт о каких-то нетривиальных изменениях, то в голове ревьюера происходит тот же процесс анализа, что и в голове автора. И если в ходе этого анализа могут возникать разные решения, не имеющие друг перед другом очевидных преимуществ, то ревьюеру придётся потратить время, чтобы понять, почему автор остановился на определённом решении. Автор, в итоге, мог принять единственно верное решение, но ревьюер, в условиях отсутствия обоснования от автора, потратит своё время на это обоснование.

Бывает такое, что автор вместе с основными изменениями делает что-то, не связанное с решением своей задачи. Когда ревьюер сталкивается с таким, то он тратит дополнительное время, чтобы понять, как эти изменения связаны с решением основной задачи.

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

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

Как помочь ревьюеру провести качественное ревью

Главная задача автора, который болеет за качество процесса ревью, состоит в том, чтобы максимально упростить и ускорить работу ревьюера. Автор, который без дополнительной обработки вываливает на ревьюера результат своей работы, проявляет крайнюю степень неуважения к работе ревьюера.

Перед тем, как превратить свои изменения в Pull Request, следует разбить их на логические куски, если в этом есть необходимость. Комфортный объём ревью заканчивается примерно на 500 строках кода изменений. Допустимый примерно на 1000 строках. Всё, что за пределами 1000 строк, должно быть разбито на более мелкие Pull Requestы.

Если вы, как и я, не разбиваете свои изменения на куски комфортного размера заранее, то это придётся проделать перед отправкой ваших изменений на ревью. Отмазка, что на это нужно потратить время, не катит. Принимая решение об отправке на ревью 1000+ строк кода, вы, фактически, оцениваете своё время дороже времени ревьюера. По нашим правилам у ревьюера всегда есть право потребовать разбить изменения на более мелкие куски. Мы всегда просим коллег относиться с пониманием, если он этим воспользуется. С опытом становится проще строить свою работу так, чтобы у вас не появлялись гигантские Pull Requestы, в которых ничего нельзя отделить.

Отдельно стоит упомянуть изменения, внесённые в код с помощью авторефакторинга или sedа. Объём таких изменений может быть очень большим. Наличие автоматических изменений вместе с осмысленными изменениями усложняет ревью. Всегда отделяйте авторефакторинги в отдельные Pull Requestы, если их объём сопоставим с объёмом нового кода, написанного вами.

Следующая часть подготовки состоит в информировании ревьюера о сути изменений. Как я уже писал выше, ревьюеру нужно знать: что сделано, зачем сделано, как сделано, и почему именно так сделано. Часть информации у ревьюера уже может быть. Часть есть в коде. Всё, что не очевидно из кода, должно быть прописано в описании изменений.

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

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

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

В целом, я считаю допустимым потратить порядка 10% от времени, затраченного на написание кода, на подготовку к ревью. Это время автора, которое мы обменяем на экономию времени ревьюера, и на улучшение качества ревью. Следует помнить, что ревьюеру на качественное ревью легко может потребоваться и 20%, и 50% от времени, затраченного автором на написание кода.

Вот только теперь автор может с чистой совестью отправить изменения ревьюеру.

Дальше начинается жизненный цикл Pull Requestа. Ревьюер должен либо одобрить его, либо попросить внести изменения. Чтобы упростить работу ревьюера, автору стоит добавить комментарий к каждому запрошенному изменению. Это вполне может быть краткое OK или Исправил, если ничего другого не требуется. Убедитесь, что вы понимаете, что просит ревьюер и что вам понятна его аргументация. Не стоит безоговорочно принимать любые запросы на внесение изменений, возводя тем самым ревьюера в околобожественный ранг. Ревью это обоюдный процесс. Если ревьюеру что-то не понятно, то он спрашивает об этом автора, и наоборот. В случае, если автор не очень опытен, ревьюеру следует приложить особенные усилия к описанию запросов на изменения, чтобы воспользоваться возможностью поделиться с автором своим опытом. Бывают и спорные моменты, когда аргументация недостаточно сильная, чтобы склонить обе стороны к единой точке зрения. С учётом того, что автор находится в более уязвимой позиции, считаю, что при прочих равных преимущество должно оставаться за автором.

Не вносите изменений в свой Pull Request, не относящихся к тому, что попросил вас ревьюер. Это крайне сбивает с толку. Также следует воздержаться от rebase и подобных действий.

Получили одобрение от ревьюера? Отлично, ещё одно качественно написанное и оформленное изменение было добавлено в проект!

Подробнее..

Пример полезного комментария

22.03.2021 16:16:43 | Автор: admin

Пример полезного комментария


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


Код, который мы рассмотрим далее, был выписан в процессе работы над статьёй "Обработка дат притягивает ошибки или 77 дефектов в Qt 6".


Анализатор PVS-Studio обратил внимание на этот фрагмент кода, выдав предупреждение: V575 [CWE-628] The 'memcpy' function doesn't copy the whole string. Use 'strcpy / strcpy_s' function to preserve terminal null. qplaintestlogger.cpp 253. Собственно, вот он:


const char *msgFiller = msg[0] ? " " : "";QTestCharBuffer testIdentifier;QTestPrivate::generateTestIdentifier(&testIdentifier);QTest::qt_asprintf(&messagePrefix, "%s: %s%s%s%s\n",                   type, testIdentifier.data(), msgFiller, msg,                   failureLocation.data());// In colored mode, printf above stripped our nonprintable control characters.// Put them back.memcpy(messagePrefix.data(), type, strlen(type));outputMessage(messagePrefix.data());

Обратите внимание на вызов функции memcpy. Сам по себе этот код вызывает сразу два вопроса:


  1. Зачем что-то записывается в буфер, содержимое которого было только что сформировано с помощью printf-подобной функции?
  2. Точно не ошибка, что не копируется терминальный ноль? Это как раз и не нравится анализатору.

К счастью, комментарий сразу всё проясняет. Нужно восстановить некие непечатаемые символы.


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


Для сравнения рассмотрим другой фрагмент кода из этого же файла:


char buf[1024];if (result.setByMacro) {  qsnprintf(buf, sizeof(buf), "%s%s%s%s%s%s\n", buf1, bufTag, fill,            buf2, buf2_, buf3);} else {  qsnprintf(buf, sizeof(buf), "%s%s%s%s\n", buf1, bufTag, fill, buf2);}memcpy(buf, bmtag, strlen(bmtag));outputMessage(buf);

Здесь забыли сделать аналогичный комментарий. И картина радикально меняется. Этот код способен ввести в замешательство нового члена команды, который будет его сопровождать или модифицировать. Совершенно не понятно, зачем нужен этот memcpy. Более того, непонятно, почему в начало строки печаталось содержимое некоего буфера buf1, а затем в начало строки помещается содержимое буфера bmtag. Как много вопросов, как мало ответов. Не стоит писать такой код.


Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov. One Useful Comment.

Подробнее..

Перевод Thunderbird, RNP и важность хорошего API

12.05.2021 14:11:23 | Автор: admin


Недавно мне довелось побеседовать с разработчикомThunderbirdо проектировании API. В ходе этой беседы я поделился соображениями о RNP,новой реализации OpenPGP, которую Thunderbird недавно стал использовать вместо GnuPG.

Собеседник скептически отнесся к моему тезису о том, что API RNP нуждается в улучшении, и спросил, разве это не субъективно какие API лучше, а какие хуже?. Согласен, у нас нет хороших метрик для оценки API. Но не соглашусь, что мы в принципе не в силах судить об API.

На самом деле, подозреваю, что большинство опытных программистов узнают плохой API, если увидят его. Думаю, далее в этой статье получится разработать хорошую эвристику, которую я попытаюсь выстроить на моем собственном опыте работы с (и над) GnuPG,Sequoia и RNP. Затем я рассмотрю API RNP. К сожалению, этот API не только можно запросто использовать неправильно он к тому же обманчив, поэтому пока его не следует использовать в контекстах, где принципиальная роль отводится соблюдению безопасности. Но целевая аудитория Thunderbird это люди, известные своей уязвимостью, в частности, журналисты, активисты, юристы и их партнеры, отвечающие за коммуникацию; все эти люди нуждаются в защите. На мой взгляд, это означает, что в Thunderbird должны лишний раз подумать, стоит ли использовать RNP.

Примечание: также предлагаю ознакомиться с этим электронным письмом:Lets Use GPL Libraries in Thunderbird!, которое я отправилв постовую рассылку по планированию развития Thunderbird.

Каковы черты плохого API?


Прежде, чем мы вместе с Юстусом и Каем приступили к проектуSequoia, мы все втроем работали над GnuPG. Мы не только сами копались в gpg, но и беседовали, и сотрудничали со многими последующими пользователями gpg. Люди смогли сказатьмного хорошего по поводу GnuPG.


Что касается критикиgpg, наиболее значительными нам показались два вида замечаний по поводу API. Первое сводится к следующему: API gpg слишком догматичен. Например, вgpgприменяется подход, основанный на использовании личной базы ключей (keyring). Таким образом, просмотреть или использовать сертификат OpenPGP можно лишь в том случае, если он импортирован в личную базу ключей. Но некоторые разработчики желают сначала просмотреть сертификат, и лишь потом импортировать его. Например, при поиске сертификата на сервере ключей по его отпечатку, можно проверить и убедиться, что возвращенный сертификат действительно тот, что нужен, поскольку его URL является самоаутентифицируемым. Это можно сделать при помощи gpg, но только обходным путем, огибая принципы той модели программирования, которая в него заложена. Базовая идея такова: создать временный каталог, добавить в него конфигурационный файл, приказать gpgиспользовать альтернативный каталог, импортировать туда сертификат, проверить сертификат, после чего очистить временный каталог. Это официальная рекомендация, добавленнаяЮстусомна основе наших бесед с последующими пользователями gpg. Да, этот метод работает. Но для него требуется писать код, специфичный для операционной системы, этот код медленный, и в нем часто заводятся ошибки.

Другой класс замечаний, с которыми мы сталкивались неоднократно, заключается в том, что для работы с gpgтребуется знать массу неочевидных вещей чтобы не злоупотреблять этим механизмом. Или, выражаясь иначе, нужно проявлять крайнюю осмотрительность при использовании APIgpg, чтобы ненароком не привнести в код уязвимость.

Чтобы лучше понять второй повод для беспокойства, рассмотрим уязвимости EFAIL. Основная проблема, связанная с API дешифрования gpg: при дешифровании сообщенияgpgвыдает обычный текст, даже если ввод был поврежден.gpgв таком случае действительно возвращает ошибку, но некоторые программы все равно выводят обычный текст в поврежденном виде. Так как, почему нет? Определенно, лучше показать хотя бы часть сообщения, чем не показать ничего, верно? Так вот, уязвимости EFAIL демонстрируют, как злоумышленник может этим воспользоваться, чтобы внедрить веб-багв зашифрованное сообщение. Когда пользователь просматривает это сообщение, веб-баг просачивается из сообщения. Уф.

Итак, на чьей совести этот баг? РазработчикиGnuPGнастаивали, чтопроблема на уровне приложений, в том, что они используют gpgнеправильно:

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

gpgпросигнализировала об ошибке; приложения не соблюдают контракт API. Должен согласиться с разработчиками GnuPG и добавить: интерфейсgpg был (и остается) бомбой замедленного действия, поскольку не подсказывает пользователю, как действовать правильно. Напротив, легкое и, казалось бы, полезное действие является неверным. ИAPI такоготипа, к сожалению, обычны в GnuPG.

Из чего слагается хороший API?


Осознание двух этих вещей что APIgpg слишком догматичен, и что им сложно пользоваться как следует сформировало мои планы. Когда мы приступили к проекту Sequoia, мы сошлись на том, что подобных ошибок хотим избежать. Основываясь на наблюдениях, мы ввели в практику два теста, которыми продолжаем пользоваться в качестве ориентиров при разработке API Sequoia. Во-первых,дополнительно к любому высокоуровневому API должен существовать и низкоуровневый, который не догматичен в том смысле, что не мешает пользователю делать что-либо, что не запрещено. В то же время,API должен наводить пользователя на верные (жестко прописанные) вещи, делая так, чтобы правильные действия были просты в исполнении и наиболее очевидны при выборе действия.

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

Типы


Покажу на примере, как мы используем типы в Sequoia, и как они помогают нам сделать хороший API. Чтобы пример был понятнее, полезно будет напомнить некоторый контекст, касающийся OpenPGP.


Простой сертификат OpenPGP

В OpenPGP существует несколько фундаментальных типов данных, а именно: сертификаты, компоненты (например, ключи и пользовательские ID), а также подписи привязки. Корень сертификата это первичный ключ, полностью определяющий отпечаток сертификата (fingerprint = Hash(primary key)). В состав сертификата обычно входят такие компоненты как подключи и пользовательские ID. OpenPGP привязывает компонент к сертификату при помощи так называемой подписи привязки. Когда мы используем в качестве отпечатка обычный хеш первичного ключа и используем подписи для привязки компонентов к первичному ключу, создаются условия, чтобы впоследствии можно было добавить и дополнительные компоненты. В состав подписей привязки также входят свойства. Поэтому есть возможность изменить компонент, например, продлить срок действия подключа. Вследствие этого с конкретным компонентом может быть ассоциировано несколько действительных подписей. Подписи привязки являются не только фундаментальной, но и неотъемлемой частью механизма безопасности OpenPGP.

Поскольку может существовать множество действительных подписей привязки, требуется способ выбирать из них нужную. В качестве первого приближения предположим, что нужная нам подпись самая недавняя, неистекшая, неотозванная действительная подпись, создание которой не отложено на будущее. Но что такое действительная подпись? В Sequoia подпись должна не только пройти математическую проверку, но и согласовываться с политикой. Например, в силу противодействия скомпрометированным коллизиям, мыдопускаем SHA-1 только в очень небольшом количестве ситуаций. (Пол Шауб, работающий надPGPainless, недавноподробно написал об этих сложностях.) Вынуждая пользователя API держать в уме все эти соображения, мы создаем почву для уязвимостей. В Sequoia легкий способ получить время истечения это безопасный способ. Рассмотрим следующий код, который работает как надо:

let p = &StandardPolicy::new(); let cert = Cert::from_str(CERT)?;for k in cert.with_policy(p, None)?.keys().subkeys() {    println!("Key {}: expiry: {}",             k.fingerprint(),             if let Some(t) = k.key_expiration_time() {                 DateTime::<Utc>::from(t).to_rfc3339()             } else {                 "never".into()             });}


cert это сертификат. Начинаем с применения политики к нему. (Политики определяются пользователем, но, как правило,StandardPolicyне только достаточна, но и наиболее уместна). Фактически здесь создается представление сертификата, в котором видны только компоненты с действительной подписью привязки. Важно, что она также изменяет и предоставляет ряд новых методов. Метод keys, к примеру, изменен так, что возвращаетValidKeyAmalgamationвместоKeyAmalgamation. (Это слияние, так как результат включает не только Key, но и все подписи, связанные с ним; некоторые считают, что этот процесс было бы удачнее назвать Катамари.\_()_/) УValidKeyAmalgamationесть действительная подпись привязки, согласно вышеприведенным критериям. А также предоставляет такие методы как key_expiration_time, что имеет смысл только с действительным ключом! Также отметим: возвращаемый тип, используемый сkey_expiration_time, эргономичен. Вместо того, чтобы возвращать необработанное значение, key_expiration_timeвозвращает SystemTime, безопасный и удобный в работе.

В соответствии с нашим первым принципом позволить использовать все, разработчик по-прежнему сохраняет доступ к единичным подписям иисследует субпакеты, чтобы узнать из иной привязки подписи, когда истекает срок действия ключа. Но, по сравнению с тем, как в API Sequoia положено правильно узнавать срок истечения действия ключа, любой иной подход противоречил бы API. По нашему мнению, это хороший API.

Примеры


Релиз 1.0библиотеки Sequoia состоялся в декабре 2020 года. За девять месяцев до того мы вышли на ситуацию полной работоспособности (feature complete) и были готовы к релизу. Но выжидали. Следующие девять месяцев ушли у нас на добавление документации и примеров к публичному API. Посмотрите документацию к структуре данныхCertв качестве примера, посмотрите, что у нас получилось. Как указано в нашем посте, нам не удалось предоставить примеры для всех функций до одной, но мы успели довольно много. В качестве бонуса к написанию примеров мы также успели найти несколько шероховатостей, которые при этом отполировали.

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

API RNP


RNP это свежая реализация OpenPGP, разработанная преимущественно Ribose. Примернодва года назад в Thunderbird решили интегрироватьEnigmailв Thunderbird и одновременно с этимзаменить GnuPG на RNP. Тот факт, что в Thunderbird выбрали RNP не только лестен для RNP; он также означает, что RNP стал, пожалуй, самой востребованной реализацией OpenPGP для шифрования почты.

Критику легко воспринять как негатив. Я хочу совершенно однозначно высказаться: думаю, что работа, которую делают в Ribose, хороша и важна, я благодарен им за то, что они вкладывают время и силы в новую реализацию OpenPGP. В экосистеме OpenPGP отчаянно требуется добавить разнообразия. Но это не оправдание за выпуск незрелого продукта для использования в контексте, где безопасность критически важна.

Инфраструктура с критически важными требованиями к безопасности


К сожалению, RNP пока не дошла до состояния в котором, на мой взгляд, ее можно безопасно развертывать. Enigmail пользовались не только люди, озабоченные приватностью своих данных, но и журналисты, активисты и адвокаты, которым важна собственная безопасность и безопасность их собеседников. В интервью, данном в 2017 году, Бенджамин Исмаил, глава Азиатско-Тихоокеанского отделения организации Репортеры без границ, сказал:

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

Интервью с Бенджамином Исмаиломиз организацииРепортеры без границ

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

RNP и подписи привязки подключа


Говоря о том, как мы используем типы в Sequoia для того, чтобы усложнить неправильное использование API, я показал, как узнать срок истечения действия ключа, написав всего несколько строк кода. Хотел начать с примера, демонстрирующего человеку, неискушенному в OpenPGP или RNP, как тот же самый функционал можно реализовать при помощи RNP. Следующий код перебирает подключи сертификата (key) и выводит на экран срок истечения действия каждого подключа. Напоминаю: время истечения действия хранится в подписи привязки подключа, а значение 0свидетельствует, что срок действия ключа не истечет никогда.

int i;for (i = 0; i < sk_count; i ++) {  rnp_key_handle_t sk;  err = rnp_key_get_subkey_at(key, i, &sk);  if (err) {    printf("rnp_key_get_subkey_at(%d): %x\n", i, err);    return 1;  }   uint32_t expiration_time;  err = rnp_key_get_expiration(sk, &expiration_time);  if (err) {    printf("#%d (%s). rnp_key_get_expiration: %x\n",           i + 1, desc[i], err);  } else {    printf("#%d (%s) expires %"PRIu32" seconds after key's creation time.\n",           i + 1, desc[i],           expiration_time);  }}

Я проверил этот код на сертификате с пятью подключами. Первый подключ имеет действительную подпись привязки и не истекает; второй имеет действительную подпись привязки и в будущем истекает; у третьего действительная подпись привязки, но срок его действия уже истек; у четвертого недействительная подпись привязки, согласно которой срок действия подключа истекает в будущем; у пятого подписи привязки нет вообще. Вот вывод:

#1 (doesn't expire) expires 0 seconds after key's creation time.#2 (expires) expires 94670781 seconds after key's creation time.#3 (expired) expires 86400 seconds after key's creation time.#4 (invalid sig) expires 0 seconds after key's creation time.#5 (no sig) expires 0 seconds after key's creation time.


Первым делом отмечаем, что вызов rnp_key_get_expirationоканчивается успешно, независимо от того, есть ли у подключа действительная подпись привязки, либо недействительная подпись привязки, либо вообще нет подписи привязки. Если почитатьдокументацию, то это поведение кажется немного удивительным. Там сказано:

Получить время истечения срока действия ключа в секундах.Обратите внимание: 0 означает, что ключ не истечет никогда.

Поскольку время истечения срока действия ключа сохранено в подписи привязки, я, эксперт по OpenPGP, понимаю это так: вызов кrnp_key_get_expirationувенчается успехом лишь в том случае, если у подключа есть действительная подпись привязки. На самом же деле оказывается, что, если действительная подпись привязки отсутствует, то функция просто принимает по умолчанию значение 0, что, учитывая вышеприведенное замечание, пользователь API ожидаемо интерпретирует так: данный ключ действует бессрочно.

Чтобы улучшить этот код, сначала необходимо проверить, есть ли у ключа действительная подпись привязки. Некоторые функции, делающие именно это, недавно были добавлены в RNP для решения проблемыCVE-2021-23991. В частности, разработчики RNP добавили функциюrnp_key_is_valid, чтобы возвращать информацию о том, действителен ли ключ. Это дополнение улучшает ситуацию, но требует от разработчика явно выбирать, должны ли проводиться эти критичные для безопасности проверки (а не явно отказываться от уже заданных проверок как было бы в случае работы с Sequoia). Поскольку проверки безопасности не относятся к выполнению полезной работы, о них легко забыть: код работает, даже если проверка безопасности проведена не была. А поскольку чтобы правильно выбрать, что проверять, нужны экспертные знания, о проверках забывают.

Следующий код предусматривает проверку безопасности и пропускает любые ключи, которые rnp_key_is_validсочтет недействительными:

int i;for (i = 0; i < sk_count; i ++) {  rnp_key_handle_t sk;  err = rnp_key_get_subkey_at(key, i, &sk);  if (err) {    printf("rnp_key_get_subkey_at(%d): %x\n", i, err);    return 1;  }   bool is_valid = false;  err = rnp_key_is_valid(sk, &is_valid);  if (err) {    printf("rnp_key_is_valid: %x\n", err);    return 1;  }   if (! is_valid) {    printf("#%d (%s) is invalid, skipping.\n",           i + 1, desc[i]);    continue;  }   uint32_t expiration_time;  err = rnp_key_get_expiration(sk, &expiration_time);  if (err) {    printf("#%d (%s). rnp_key_get_expiration: %x\n",           i + 1, desc[i], err);  } else {    printf("#%d (%s) expires %"PRIu32" seconds after key's creation time.\n",           i + 1, desc[i],           expiration_time);  }}


Вывод:

#1 (doesn't expire) expires 0 seconds after key's creation time.#2 (expires) expires 94670781 seconds after key's creation time.#3 (expired) is invalid, skipping.#4 (invalid sig) is invalid, skipping.#5 (no sig) is invalid, skipping.


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

Хотя бывает и так, что мы не хотим использовать тот ключ или сертификат, срок действия которого истек, иногда мы к ним прибегаем. Например, если пользователь забудет продлить срок действия ключа, то у него должна быть возможность увидеть, что ключ истек, и тогда проверить сертификат, а также продлить в таком случае срок действия ключа. Хотя gpg --list-keysне показывает истекшие ключи, при редактировании сертификата все-таки видны истекшие подключи, так, чтобы пользователь мог продлить срок их действия:

$ gpg --edit-key 93D3A2B8DF67CE4B674999B807A5D8589F2492F9Secret key is available.sec ed25519/07A5D8589F2492F9created: 2021-04-26 expires: 2024-04-26 usage: Ctrust: unknown    validity: unknownssb ed25519/1E2F512A0FE99515created: 2021-04-27 expires: never    usage: Sssb cv25519/8CDDC2BC5EEB61A3created: 2021-04-26 expires: 2024-04-26 usage: Essb ed25519/142D550E6E6DF02Ecreated: 2021-04-26 expired: 2021-04-27 usage: S[ unknown] (1). Alice <alice@example.org>


Есть и другие ситуации, в которых истекший ключ не должен считаться недействительным. Предположим, например, что Алиса посылает Бобу подписанное сообщение: я заплачу тебе 100 евро за год, а срок действия ключа подписи истекает через полгода. Когда год закончится, будет ли Алиса должна Бобу, если исходить из этой подписи? По-моему, да. Подпись была действительна, когда ставилась. Тот факт, что срок действия ключа уже истек, не имеет значения. Разумеется, когда ключ истек, подписи, скрепленные им после момента его истечения, должны считаться недействительными. Аналогично, сообщение не следует шифровать истекшим ключом.

Короче говоря, то, должен ли ключ считаться действительным, сильно зависит от контекста.rnp_key_is_validлучше, чем ничего, но, несмотря на название, эта функция предусматривает достаточно нюансов при определении того, действителен ли ключ.

В рамках того же коммита была добавлена вторая функция,rnp_key_valid_till. Эта функция возвращает метку времени, до наступления которого ключ может считаться действительным Если ключ никогда не был действителен, то в качестве значения возвращается ноль. При помощи этой функции можно определить, был ли ключ действителен когда-либо, для этого нужно проверить, возвращает ли эта функция ненулевое значение:

int i;for (i = 0; i < sk_count; i ++) {  rnp_key_handle_t sk;  err = rnp_key_get_subkey_at(key, i, &sk);  if (err) {    printf("rnp_key_get_subkey_at(%d): %x\n", i, err);    return 1;  }   uint32_t valid_till;  err = rnp_key_valid_till(sk, &valid_till);  if (err) {    printf("rnp_key_valid_till: %x\n", err);    return 1;  }   printf("#%d (%s) valid till %"PRIu32" seconds after epoch; ",         i + 1, desc[i], valid_till);   if (valid_till == 0) {    printf("invalid, skipping.\n");    continue;  }   uint32_t expiration_time;  err = rnp_key_get_expiration(sk, &expiration_time);  if (err) {    printf("rnp_key_get_expiration: %x\n", err);  } else {    printf("expires %"PRIu32" seconds after key's creation time.\n",           expiration_time);  }}


Результаты:

#1 (doesn't expire) valid till 1714111110 seconds after epoch; expires 0 seconds after key's creation time.#2 (expires) valid till 1714111110 seconds after epoch; expires 94670781 seconds after key's creation time.#3 (expired) valid till 1619527593 seconds after epoch; expires 86400 seconds after key's creation time.#4 (invalid sig) valid till 0 seconds after epoch; invalid, skipping.#5 (no sig) valid till 0 seconds after epoch; invalid, skipping.


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

Но давайте подробнее рассмотримrnp_key_valid_till. Во-первых, в OpenPGP время истечения ключа хранится как беззнаковый 32-разрядный отступ от времени создания ключа, также в беззнаковом 32-разрядном формате. Следовательно, функция должна была бы использовать более широкий тип или, как минимум, проверять код на переполнение. (Ясообщил об этой проблеме, и ее уже исправили.)

Но, даже если игнорировать этот косяк, функция все равно странная. В OpenPGP ключ может быть действителен в течение нескольких периодов времени. Допустим, срок действия ключа истекает 1 июля, а пользователь продлевает его только с 10 июля. В период с 1 по 10 июля ключ был недействителен, а подписи, сгенерированные в это время, также должны считаться недействительными. Итак, что же должна возвращать рассматриваемая функция для такого ключа? Гораздо важнее, как пользователь такого API должен интерпретировать результат? Уместно ли вообще использовать такой API? (Да, я спрашивал.)

В Sequoia мы пошли другим путем. Вместо того, чтобы возвращать информацию, что ключ действителен, мы переворачиваем ситуацию; пользователь API может спросить:действителен ли этот ключ в моментt. По нашему опыту, это все, что на самом деле требовалось во всех известных нам случаях.

Не подумайте, что я специально придираюсь именно к этой проблеме с API RNP. Это просто сложность, о которой я недавно размышлял. Когда мы заново реализовали API RNP, чтобы создать альтернативный бэкенд OpenPGPдля Thunderbird, мы столкнулисьсо многими подобными проблемами.

Заключение


Ошибки, допущенные разработчиками RNP, понятны и простительны. OpenPGP сложен, как и многие другие протоколы. Но его можно существенно упростить, если мы стремимся сохранить его гибкую и надежную ИОК, а не просто иметь инструмент для шифрования файлов.

Тем не менее, API RNP опасен. А Thunderbirdиспользуетсяв контекстах с критическими требованиями к безопасности. В интервью от 2017 годаМихал Рысьек Вознякиз Центра по исследованию коррупции и организованной преступности (OCCRP) четко сообщил, что на кону чьи-то жизни:

Я действительно категорически уверен, что, если бы мы не использовали GnuPG все это время, то многие наши информаторы и журналисты оказались бы в опасности или за решеткой

ИнтервьюсМихалом Рысьеком Вознякомиз Центра по исследованию коррупции и организованной преступности

Как это отразится на Thunderbird? Вижу три варианта. Во-первых, Thunderbird мог бы переключиться обратно на Enigmail. Можно подумать, что портирование Enigmail на Thunderbird 78 далось бы сложно, но я слышал от многих разработчиков Thunderbird, что это технически осуществимо вполне подъемными усилиями. Но одна из причин, по которым Thunderbird предпочла уйти от Enigmail огромное время, которое разработчикам Enigmail приходилось тратить, чтобы помочь пользователям правильно установить и сконфигурировать GnuPG. Поэтому такой путь неидеален.

Во-вторых, Thunderbird могла бы переключиться на иную реализацию OpenPGP. В наше время ихцелая кучана выбор. Лично я считаю, что Thunderbird следовало бы переключиться на Sequoia. Конечно же, я разработчик Sequoia, поэтому необъективен. Но дело здесь не в деньгах: мне платит фонд, а на свободном рынке мне предложили бы, пожалуй, вдвое больше, чем я зарабатываю сейчас. Я работаю ради того, чтобы защитить пользователей. Но, даже кроме API Sequoia и преимуществ реализации, Thunderbird в данном случае выигрывает и еще в одном отношении: мы уже заставили эту реализацию работать. Несколько недель назад мы выпустили Octopus, альтернативный бекенд OpenPGP для Thunderbird. У него не только функциональный паритет с RNP, но и есть ряд ранее недостававших фич, например, интеграция с gpg, а также залатаны некоторые бреши в безопасности и выполнено несколько нефункциональных требований.

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



VPS от Маклауд идеально подходят для разработки API.

Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!

Подробнее..

Перевод Почему в мире так много отстойного ПО

17.05.2021 14:19:21 | Автор: admin
Мы буквально окружены отстойным программным обеспечением. Пенсионные фонды спотыкаются об написанные десятки лет назад пакетные скрипты с ошибочными допущениями. Из кредитных организаций утекает более сотни миллионов номеров социального обеспечения и других конфиденциальных данных. И это ещё не говоря о куче забагованного и раздражающего ПО, создаваемых и мелкими поставщиками, и крупными корпорациями.

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

Чтобы понять причину этого, нам сначала нужно понять, как соотносится навык разработчика с полезностью ПО, которое он создаёт для решения определённой задачи.


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

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

И наконец, когда разработчики достигают определённого порога навыков, они пересекают его
и попадают в третью категорию. В категорию, где каждый достиг такого высокого уровня компетентности (относительно решаемой ими задачи), что дальнейший личный рост минимально отразится на конечном продукте. Например, любой случайно выбранный инженер из Google может создать CRUD-приложение столь же качественно, как и Джефф Дин.



В идеальном мире единственными разработчиками в первой и второй категории были бы студенты или молодые специалисты. А все профессиональные программные системы в основном создавались бы разработчиками из третьей категории. Разработчиками, в совершенстве освоившими все навыки, необходимые для решения их задачи и создания решений, очень близких к теоретическому идеалу. В таком изумительном мире всё окружающее нас ПО находилось бы на примерно одинаково высоком уровне качества, работало бы точно в соответствии с ожиданиями, с оптимальной производительностью и без дыр в безопасности. В этом мире общество реагировало бы на всё ПО с радостью, а не с раздражением.

Однако между нами и этой утопией стоят две проблемы.

Во-первых, количество разработчиков из третьей категории чрезвычайно мало по сравнению со второй категорией. Программирование легко изучить, но сложно освоить. Миллионы людей могут написать функционирующий скрипт, но очень немногие мастерски освоили искусство проектирования ПО. К тому же, не существует никаких препятствий к попаданию в отрасль разработки ПО для программистов нет аналога Американской медицинской ассоциации или Международной ассоциации юристов. Неудивительно, что в ней гораздо больше людей с начальной и средней компетентностью по сравнению с количеством компетентных специалистов.

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

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

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



Можно впасть в заблуждение, что подобная ситуация естественна для всего мира и всех профессионалов. Но на самом деле это не так.

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

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

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



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

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

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

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



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

Кроме того, в нём рассматривается только та часть уравнения, которая относится к разработчикам. Менеджеры и CEO, не дающие разработчикам достаточно времени для создания безупречного ПО и предпочитающие выпустить нечто сносное ещё одна серьёзная причина проблемы, которую мы исследуем в другой статье.

image
Подробнее..

Нужна ли новая методология разработки?

03.02.2021 18:19:31 | Автор: admin

Если вы планируете создать свою софтверную компанию, то вы задумываетесь как построить работу людей, как выбрать методологию для работы. Но если приглядеться к известным методологиям, то есть к ним некоторое недоверие, особенно если вы тратите на компанию собственные деньги

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

Предпосылки к созданию методологии

Рассуждения о современных методологиях и человеческой природе

У современных методологий, в порядке их возникновения, есть множество недостатков:

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

  • Проектная структура ведет к зависимости группы людей от одного проектного менеджера, который в 95% случаев из 100% некомпетентен. Нацеленность на короткие проекты, приводит к удорожанию продуктов с течением времени

  • В матричную структуру (все три вида), по определению, заложен конфликт, что изначально неэффективно

  • В проектной и матричной структуре также наблюдается большое недооценивание тестирования

  • В SCRUM (agile) методологии зачастую есть Product Owner, который стремится стать проектным менеджером, для чего ему могут отдать в подчинение аналитика, чтобы повысить его значимость. Ровно также, если есть амбициозный или конфликтный сотрудник команды, то это все разрушает

  • Низкая роль аналитики и тестирования в agile также не идет на пользу продуктам компании

  • В agile методологиях практически отсутствует возможность заключать контракты по фиксированной цене при старте проекта

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

  • Хотят работать и мотивированы

  • Хотят работать без видимой причины, просто потому, что должны

  • Хотят работать, потому что считают что получают за это деньги

  • Не хотят работать, но боятся осуждения и наказания

  • Не хотят работать и работают меньше, но скрывают это

  • Не хотят работать и работают сильно меньше, скрывают это

  • Не работают, сидят тихо, боятся, что их поймают

  • Не работают и открыто это декларируют, осуждают работающих сотрудников

Первые лидерские методологии направлены на то, чтобы заставить немотивированных и ленивых сотрудников работать. Agile методологии направлены на поощрение работоспособных и мотивированных сотрудников. А что вам делать, если у вас появился весь спектр сотрудников?

Что мы хотим получить:

  1. Методологию, которая ориентирована не на идеальных сотрудников, а на всех подряд. К сожалению, квалифицированных людей мало. Вы все равно наймете неквалифицированных сотрудников, у вас выхода нет

  2. Методологию, в которой максимально минимизирована роль руководства настолько насколько это вообще возможно. Где есть защитные механизмы от появления руководителей там, где их быть не должно

  3. Методологию, позволяющую быстро выпускать качественное программное обеспечение

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

  5. Методологию, в которой можно заключать договоры с фиксированной ценой

  6. Не универсальную методологию, которую можно применять на полях марса, на северном полюсе и в лесах латинской Америки, а конкретную методологию для разработки именно комплекса программного обеспечения

  7. Методологию, которая включается в себя последние современные методологии и технологии, например, DevOps, автоматизированное построение любой отчетности по проектам и продуктам и т.п.

  8. Методологию, в которой, по аналогии с agile методологиями, усилия направлены на похвалу и мотивацию команды

  9. Методологию, которая защищается ядовитых сотрудников, оставляя в командах только сотрудников, которые готовы работать

  10. Методологию с высоким взаимодействием сотрудников различных ролей

  11. Методологию, в которой у команды есть лидер, с высокими soft skills и эмпатией,, которому можно пожаловаться или у которого можно отпроситься, который защищает и мотивирует команду

  12. Методологию, в которой директорский состав работает и подотчетен компании и где вы не задумываетесь, что если убрать всех менеджеров, то в компании ничего не изменится

  13. Методологию, где правильно понимается роль аналитики и тестирования

Зачем нужна аналитика?

Аналитика нужна, чтобы понять ЧТО вы хотите сделать и КАК с точки зрения бизнеса.

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

Если у вас нет аналитики, то у меня к вам вопросы:

  1. Ваше приложение решает какую-то потребность бизнеса? Уверены ли вы, что кнопка, которую ваша команда разработки делала несколько месяцев, будет когда-либо нажата пользователями?

  2. Как вы понимаете, что нужно в приложении проверить, перед передачей приложения пользователям? А если пользователь заполнит форму немного с другими данными и нажмет вон на ту кнопку, приложение будет работать? Точно?

  3. Вашему проекту более 2 лет, я хотел бы узнать обо всех функциях, которые есть в вашем проекте, можете рассказать?

  4. Сколько должно пройти времени и проведено встреч, чтобы новый сотрудник узнал ваш проект?

  5. Вы хотите сотрудничать с новым крупным заказчиком, сколько сотрудников и с каким опытом вы должны послать к заказчику, чтобы рассказать про проект?

  6. Если случится кризис, который заденет вашу компанию, и часть опытных сотрудников уйдет, за сколько времени вы восстановите экспертизу по проекту?

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

Зачем нужно тестирование?

  1. Unit тестирование (внутреннее качество)

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

Как видно из описания, это тестирование делается разработчиками.

  1. Интеграционное тестирование (внутреннее качество)

Основная цель - это вызвать внешнее API так, чтобы вызов прошел через все модули начиная от пользовательского интерфейса (если есть) и заканчивая записью/чтением из БД, с правами доступа как на продуктовой среде. Может выявлять ошибки на стыке модулей, например, ошибки передачи данных с backend кода и ожидаемого набора данных в БД. Позволяет отследить ошибки в настройке routing, ошибки при использовании IoC-контейнеров и т.п. Сильно ускоряют разработку, минимизируя ручные проверки разработчиком

Как видно из описания, это тестирование делается разработчиками.

  1. Функциональное тестирование (внешнее качество)

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

Важно понимать, что принцип этого тестирования другой. Например, вот один из возможных алгоритмов:

  • На основе требований вы создаете классы эквивалентности (Equivalence Classes)

  • На основе классов эквивалентности вы определяете количество и сценарий тесте кейсов (test cases)

  • Далее вручную (уже реже) или автоматизированно проводится проверка в уже реализованного функционала, в соответствии с тест кейсами

Как видно из описания, это тестирование делается тестировщиками.

  1. Регрессионное тестирование (внешнее качество)

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

Как видно из описания, это тестирование делается тестировщиками.

При существовании тестирования несколько десятков лет, по прежнему, существует сильное непонимание этого процесса. Отсутствие тестирования напрямую сказывается на внешнем качестве продуктов, а это именно то качество, на основе которого пользователь судит о продукте.

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

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

В отсутствии тестирования у компании растут расходы на поддержку проектов. Вместо реализации нового функционала, разработчики тратят время на выявление и исправление уже выпущенного функционала. Соотношение может даже достигать 10% времени на выпуск нового функционала и 90% времени на исправление багов или доказательство, что ПО работает как надо (а иногда и 0%/100%). Компания несет убытки, сотрудники злятся и увольняются.

Методология разработки

Общее описание

Принципы методологии:

  1. Отсутствие менеджеров или их замен под другими названиями

  2. Автоматизация управленческих активностей

  3. Объединение в команду максимально возможного количества компетенций

  4. Наличие строго определенного шаблона требований с заданной структурой, обязательной для заполнения по каждому продукту

  5. Наличие ролей и четкое описание обязанностей ролей. Все роли должны быть назначены, даже если в команде один сотрудник. Один сотрудник может разделять множество ролей

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

  7. Получение обратной связи по всем людям с организаторской ролью: технический директор и тимлиды

  8. Работа вне проекта не ведется

Работа методологии обеспечивается следующими ключевыми моментами:

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

  2. Так как план перестраивается автоматически, вы в любой момент получаете актуальный план по запросу

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

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

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

  6. Максимальное возможное заимствование хороших вещей из agile методологий, а именно:

  • наличие беклогов

  • наличие спринтов

  • командное планирование

  • проведение demo

  • проведение ретроспективы

  • оценка в story points

  1. Важные отличия от agile (SCRUM):

  • отделенный от команд product owner заменен аналитиком внутри команды, который ведет как часть требований, так и беклог проектов

  • отсутствие работы по методике times & materials

  • наличие team leader

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

  2. Если что-то не охвачено методологией, то любой сотрудник может это вынести на обсуждение и решить как поступить в вашей компании, если оно не противоречит принципам

  3. У всех user story есть поле проект, с которого определяется финансирование

  4. Методология поощряет использование infrastructure as code

Важность обратной связи в сторону сотрудников:

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

  2. Технический директор также выдает обратную связь сотрудникам раз в квартал. Собирает вопросы и отчитывается о том, что сделано

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

  4. В компании раз в квартал проводится опрос на удовлетворенность тимлидом и техническим директором

Термины и определения

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

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

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

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

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

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

Беклога проекта беклог, который наполняется задачами необходимыми для реализации конкретного проекта. беклог наполняется аналитиком или командой. Контролируется аналитиком

Беклог команды - беклог, отображающий все user story команды в порядке приоритета. Общий беклог команды отображающий любые активности команды в порядке их приоритета

Примечание: по факту, любой беклог представляет собой срез данных компании в виде user stories. Все эти user story существуют в компании, просто в разное время нужно анализировать разное их подмножество

Роли и обязанности

  • Тимлид (внутри команды)

Основная цель:

  • Отвечает за то, чтобы производительность продуктовой (feature) разработки всегда была на максимальном уровне

Полномочия:

  • Контролирует, что в требованиях отсутствуют части, которые могут в будущем замедлить выпуск нового функционала. Факторы, которые могут замедлить новый функционал: возможное увеличение количества инцидентов от заказчиков; необходимость ручных операций; необходимость привлекать разработку к получению информации в случае проблем и т.п. Может остановить взятие требований в работу, если они нарушают эту метрику

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

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

  • Инициирует обсуждение с архитектором о выделении части проекта в отдельный проект, если поддержка одного проекта стала сложна в поддержке и развертывании

Обязанности:

  • Смотрит задачи на доске в статусе усложнение поддержки, где:

    • контролирует, что изменения по задаче не увеличивает затраты на поддержку проекта

    • проверяется достаточное логирование по задаче

    • можно ли уменьшить кодовую базу воспользовавшись сторонней библиотекой

  • Выносит на генерального директора рекомендации по повышению должности или зарплаты сотрудников команды

  • Хвалит сотрудников на регулярной, ежеспринтовой основе. Отчитывается за это перед генеральным директором

  • Ведет беклог продукта

  • Контролирует, что никто не выходит за рамки полномочий назначенных ролей

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

  • Транслирует в команду информацию о значимости сотрудников и их влияние на решения компании

  • Отвечает за старт итерации и за поведение итогов итерации

  • Согласует новые технологии с техническим директором, если это приводит к усложнению тестового контура от стандартного контура для компании (например, есть контур разворачивающий код на C#, а команда хочет программировать на Go и это потребует доработки тестового контура, в сторону увеличения поддержки и времени деплоя проекта)

  • Если технологии стоят дополнительных денег, они вносятся в план проекта и согласуются генеральным директором. При необходимости дополнительно согласуется с генеральным директором

  • Выслушивает жалобы от команды, организует команду на решение проблем или ищет того, кто может это решить

  • Отпускает сотрудников, если им нужно отпроситься

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

Ограничения:

  • Не является руководителем команды

  • Командный архитектор (внутри команды)

Обязанности:

  • Смотрит задачи на доске в статусе Архитектура, где:

    • контролирует, какие методы действительно должны быть публичными в любых классах на всех уровнях

    • Контролирует понятность любого публичного API во всех классах так, чтобы не требовалось входить в функцию, чтобы понять что она делает, а также одинаковые подходы при создании параметров публичных методов

    • Контролирует single responsibility principle по всем крупным частям системы

    • Контролирует ослабление связанности между библиотеками/компонентами

    • Отслеживает появление условных ветвлений в коде и, если они неуместны, то рекомендует паттерны состояния или стратегии

    • Проверяет, что в БД в столбцах хранится именно то, что следует из названия

    • Смотрит должно ли значение быть вынесено в настройки для изменения в отсутствии пересборки проекта или быть помещено в код для уменьшения роста количества настроек

  • Контролирует, какие части системы могут быть вынесены в независимые модули (NuGet пакеты) и переданы на поддержку другой команде, даже если сейчас они используются только в одном проекте. В случае нахождения таких частей, эскалирует это на технического директора для включения этих задач в специальный беклог

  • Ведет секцию в общем документе требований по секции архитектуры

  • Смотрит нужно ли разделять монолит на микросервисы или микрофронтеды или монолит будет выгоднее. Так как микрофронтенды ведут к удорожанию тестового контура, их необходимость обсуждается с техническим директором и утверждается генеральным директором, после включения изменений в план

  • Пишет документ "стратегия развития проекта", который на основе пришедших доработок определяет тренд пожеланий с перспективой на будущее, а также формирует понимание, что точно не ложится в концепцию развития проекта и какое требование заказчика по этой причине должно быть отклонено или вынесено за основной контур продукта

Ограничения:

  • Не является руководителем команды

  • Разработчик

Обязанности:

  • Смотрит задачи на доске в статусе code review

  • Участвует в выработке требований

  • Разрабатывает программу

  • Пишет unit тесты и интеграционные тесты

  • Участвует в доработке тестового фреймворка для тестировщиков, упрощая автоматизацию тестирования

  • Добавляют задачи для user stories при планировании или user stories в технический беклог

Тестировщик (внутри команды)

Обязанности:

  • Участвует в выработке требований

  • Пишет тест кейсы

  • Автоматизирует тест кейсы на основе тестового фреймворка, который пишут разработчики

  • Отвечает за контроль, что ПО удовлетворяет минимальному внешнему качеству

  • Аналитик (внутри команды)

Обязанности:

  • Общается с заказчиком

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

  • Участвует в выработке требований

  • Отвечает за систематизацию того чего на самом деле хочет заказчик (не с тем, что говорит заказчик) в виде части секций требований

  • Ведёт беклог проекта

  • Делает приоритезацию

  • Устанавливает на user stroy признаки required, desired, optional

  • Фиксирует всю информацию при общении с заказчиком или в требованиях или в скоупе проекта

  • Определяет стейкхолдеров со стороны заказчика (кто на самом деле влияет на проект со стороны заказчика)

Ограничения:

  • Не является руководителем команды

  • Документалист (внутри команды)

Обязанности:

  • Пишет выходную документацию по проекту, цель которой снизить возможную поддержку проекта со стороны разработки

  • Отвечает за перевод документации на разные языки

  • Технический директор (вне команд)

Обязанности:

  • Отвечает за отсутствие дублирования решений в проектах

  • Наделяется функцией enterprise архитектора

  • Инициирует поднятие версий сборок по проектам, а также отвечает за беклог на выделение части системы

  • Формирует задачи по плану обновления, которые включаются в беклог активностей компании

  • Утверждает новые технологии в перспективе усложнения работы тестового контура

  • Проводит отчет перед сотрудниками раз в квартал

Ограничения:

  • Не является руководителем команды

  • Не отвечает за найм и не участвует в этой активности

  • Генеральный директор (вне команд)

Обязанности:

  • Отвечает за финансовые показатели компании

  • Отвечает за прибыльность

  • Принимает решение о повышении должности или зарплаты сотрудника на основе рекомендаций тимлида, если это сотрудник команды

  • Для сотрудников вне команд, решения принимаются непосредственно генеральным директором, при индексации зарплат, например раз в полгода или год

  • Отвечает за финальное собеседование. На собеседовании определяется соответствие statements компании и определение роли по DISC. В случае занятости может делегировать эту роль компетентному сотруднику HR

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

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

  • По возможности, находится в одном пространстве с сотрудниками или в кабинете, из которого можно наблюдать курилку

  • Контролирует по отчетам, что задачи тех.долгов не превышают заложенный процент в продукте

  • Проводит отчет перед сотрудниками раз в квартал

  • Команда

Обязанности:

  • Отвечает за сроки, которые фиксируются после составления плана

  • Организует процессы DevOps в соответствии с требованиями

  • Производится командная оценка задач

  • Команда следит за тестовой инфраструктурой (мониторит делаются ли бекабы баз, есть ли свободное место на дисках, достаточно ли ресурсов на тестовых контурах)

  • Оценка работы технического директора на основе запрашиваемого опроса раз в квартал

  • Оценка работы тимлида на основе запрашиваемого опроса раз в квартал

  • Вне ролевые должности

Есть ряд должностей, которые находятся вне ролевой модели. Например, системный администратор (администрирование багтрекенга, настройка электронной почты)

Работа над проектным беклогом. Планирование проекта

  1. Определение потребности создать проект. Для этого можно использовать разные подходы:

  • выдвигаем гипотезу, которая требует проверки

  • получили запрос от заказчика на реализацию проекта

  • получили пожелание по доработке существующего проекта

  • вышли с идеей создание нового внутреннего проекта в компании

  1. Проект начинает планироваться в отдельной итерации

  2. Создается карточка проекта, которая свяжет все user stories одним проектом

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

  4. Генеральный директор ставит разрешение на начало аналитических работ

  5. Определяются команды, которые будут задействованы в проекте. На каждую вовлеченную команду создаются user story на сбор первичных данных по новому проекту и user story на получение экспертной оценке работ. Для любого продукта создаются требования. Шаблон требований можно посмотреть в Приложении 1.

  6. Создаются user stories на заполнение специальных секций требований архитектором и тимлидом

  7. Требования будут меняться, поэтому нет смысла их прорабатывать до совсем мелких деталей. Однако детализация должна быть на достаточном уровне, чтобы можно было посмотреть общий объем работ

  8. User stories по аналитике и первичной оценке оцениваются командой в SP, в соответствии с Приложением 2, и переводятся в итерацию команд

  9. При помещении задач в итерацию команд, будет пересчитан отчет по milestone в соответствии с Приложением 3. В случае если генерального директора не устраивают сроки работ, он может сделать новую приоритезацию user stories в беклогах команд

  10. После завершения аналитики по проекту, принимаются решения:

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

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

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

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

  2. Аналитик создает user story по реализации функционала на основе требований

  3. Требования и user story не обязаны соответствовать друг другу. Не нужно создавать отдельные разделы в требованиях, которые соответствуют один в один user story. Требования курируются аналитиком, им же и контролируется, что аналитическая информация полностью отражена в user story. Требования и user story все равно в какой-то момент разойдутся. Важно понять, что требования нужны для хранения информации о последнем статусе продукта в удобном для восприятия и хранения информации для человека виде. А user story нужны для разработки части функционала. Попытка тут сэкономить и объединить эти два понятия позже приведет к более сложному пониманию аналитического документа

  4. После создания аналитиком user stories, они оценивается в соответствии с Приложением 2.

  5. Если после создания и оценки user story в вас есть user story, превышающие 40 story points, то такие story должны будут разделены в будущем, перед взятием в работу

  6. По факту завершения разбиения проекта на продуктовый беклог из user stories выставляется признак завершения создания скоупа задач. Так как в команду входят аналитики, командные архитекторы, разработчики, тестировщики, DevOps, документалисты, то подразумевается, что участники команды следят за тем, чтобы трудоемкость их работ была отражена в оценке

  7. При выставлении в карточки проекта признака завершения создания скоупа, происходит автоматический расчет пиковой стоимости проекта в соответствии с Приложением 3.

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

  9. Если проект оценен в более 4 месяца работ, то проект делится на части и для каждой части создается своя карточка

  10. После этого созданные user story смотрятся в разрезе командного беклога

  11. При рассмотрении задач в командном беклоге, они размещаются их в соответствии с приоритетом, определенным командой (если не определен приоритет, то user story по новому проекту размещаются в конце беклога).

  12. После изменения приоритезации задач или выставления новым задачам итерации командного беклога, происходит перерасчет отчета в соответствии с Приложением 4.

  13. После получения первой стоимости проекта, они фиксируется в отчете как максимальная. В дальнейшем при разбиении user story, переоценке или добавлении новых user story будет делаться переоценка работ в соответствии с Приложением 3.

  14. Если в будущем, при работе с user stories, при построении автоматизированного отчета, будет обнаружено, что оценка скоупа user stories по проекту превысила изначальную оценку проекта, то команда будет оповещена о необходимости уменьшить скоуп проекта. В случае если команда не сделала это в течении заданного периода (по умолчанию 2 недели), будет выслано сообщение генеральному директору о необходимости вмешаться

Планирование спринта

Планирование нового спринта в целом происходит так же как и в методологии SCRUM

Закупка оборудования

  1. Генеральный директор может запросить данные по закупке нового оборудования

  2. Далее должна быть выполнена автоматическая рассылка тимлидам для сбора этой информации по командам

  3. При создании нового проекта генеральный директор также запрашивает информацию о необходимых закупках

Приложение 1. Шаблон требований

Описание продукта

Примечание раздела: в данном разделе указывается общая бизнес цель продукта

Внимание: если есть дизайн графической части, то он делается отдельно от требований, без ссылок на страницы прототипа. Связка требований и прототипа делается в рамках user stories.

Функциональные требования

Бизнес цель <Название первой цели>

Примечание раздела: тут описываются части продукта имеющие отдельную бизнес цель. Это может быть отдельный раздел пользовательского интерфейса или выставление публичного API. В разделе также описываются бизнес ветвления, например, ожидается, что вы получаете данные из источника А, проводите проверку данных и записываете данные в БД. Логично предположить, что есть успешный путь, когда все хорошо, и есть неудачные кейсы именно с точки зрения бизнеса, например, когда валидация не прошла. Все это описывается в требованиях к бизнес цели. Если есть повторяющиеся в других целях моменты, то они должны быть заново описаны в каждой цели.

Используемые сочетания клавиш

Архитектурные детали

Примечание подраздела: тут архитектор описывает верхнеуровневые детали, относящиеся к бизнес цели. Например, как будет выглядеть внешний API или как именно будет реализовываться безопасность передачи данных. Если можно сделать какую-то схему, вместо текста, то лучше разместить схему. Statefull или stateless система, как масштабируется и ведёт себя в многопоточный среде. Как система реагирует на новые изменения?

Особенности поддержки функционала

Примечание раздела: данный раздел ведётся тимлидом. Тут, например, смотрится как будет выполнен переход со старой функциональности на новую, требуется ли поддерживать одновременно старый и новый функционал и т.п.

Бизнес цель Название второй цели

<тут должно быть описание бизнес цели>

Бизнес цель Название третьей цели

<тут должно быть описание бизнес цели>

Личности согласно Алану Куперу

Нефункциональные требования

Требования к производительности

Требования к безопасности

Поддерживаемые устройства

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

Мониторинг

Примечания раздела: тут должны быть описаны механизмы мониторинга продукта на продуктовой среде (отзывчивость определенных API, наличие ошибок в логах, точные ответы при посылке определенных запросов, свободное место на дисках, загрузка процессора и т.п.)

Развертывание и обновление системы

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

Continuous integration

Примечание раздела: данный раздел ведется архитектором или тимлидом. Раздел описывает какие шаги есть при развертывании системы на тестовых средах, какие тесты и как выполняются, как происходит разворачивание системы.

Нефинансовые показатели

Примечание раздела: в этом разделе описываются нефинансовые показатели, которые должны достигаться, например:

  • Удовлетворенность пользователей

  • Eptda

  • Конверсия

  • Воронка продаж. Сколько людей заходили на сайт для просмотра ПО и сколько купили

  • Сколько одна подписка прожила (сколько людей ушли)

Тестирование пользователями

История изменений документа

Версия

Описание изменений

1.0

Создание документа

1.1

Добавлена бизнес цель Заведение данных в систему

Приложение 2. Соответствие SP и часов

Story point (SP)

Hours (4*n + n), ч.ч.

1

4

3

15

5

25

8

40

13

65

21

105

34

170

55

275

Трудоемкость - параметр оцениваемый в story point, который означает затраты энергии при реализации задачи. Важно понимать, что задача может быть трудоемкой из-за высокой сложности, но она также может быть трудоемкой по причине несложных но затратных действий для реализации. Очевидно, что трудоемкость можно перевести в часы (несмотря на рекомендации этого не делать).

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

Первая user story выбирается из расчета того, что она будет делаться 4 часа. Таким образом происходит настройка под систему. Далее задачи оцениваются в story points, в зависимости от того насколько они более трудоемкие по прогнозам команды.

Приложение 3. Расчет стоимости проекта

Алгоритм:

  1. За основу расчета берутся оценки feature в story point, которые конвертируются в часы согласно Приложению 2.

  2. По всем user story проекта считается общее количество часов, если в карточке проекта есть признак начала расчета проекта

  3. Первая рассчитанная оценка берется за максимальную (расчет можно сбросить снова через карточку проекта, тогда он снова начнется с начала)

  4. После получения первой оценки от нее берется процент для беклога продукта (15%) и беклога активностей компании (10%). Проценты могут варьироваться, но стоимость беклогов техдолга не может приближаться к цене зачал заказчика и, тем более, превышать его

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

  6. Позже user story беклога проекта, превышающие определенное число story point будут разбиваться на более мелкие. Также возможно будут добавляться новые user story. В случае превышения максимальной оценки, при построении отчете будет зафиксировано превышение максимальной цены

  7. В случае превышения максимальной цены проекта, команде будет выслано уведомление и дано время (по умолчанию время до завершения спринта, если только он не заканчивается) для корректировки скоупа

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

  9. User story по техдолгу считаются отдельно. В случае превышения лимита на эти беклоги, они корректируются в сторону уменьшения. Альтернатив тут нет

  10. К стоимости проекта добавляется бюджет на правку ошибок

  11. Если нужно купить оборудование или ПО для выполнения проекта, то оно тоже включается в бюджет

  12. Кроме того нужно установить коэффициенты для выделения денег на аренду офиса, возможные повышения зарплат, премию и чистую прибыль

Приложение 4. Отчет по milestones

Алгоритм:

  1. За основу расчета берутся оценки user stories в story point, которые конвертируются в часы согласно Приложению 2.

  2. За основу расчета берется производительность команды. Если изначально производительность неизвестна, то она может быть выставлена по предположению и далее корректироваться

  3. Беклог команды считается приоритизированным

  4. Беря за основу производительность команды и оценку в story point можно автоматически рассчитать дату завершения проекта

  5. При первом построении отчета, фиксируется определенная дата завершения проекта

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

  7. Если генеральный директор не согласен со сдвигом даты, генеральный директор может инициировать изменение приоритетов user story в командном беклоге

Подробнее..

Перевод Как заставить руководство проникнуться техническим долгом

18.11.2020 12:16:11 | Автор: admin

Руководство не даёт мне заняться рефакторингом legacy-кода! Знакомая ситуация? Раздражает жутко. Большинство разработчиков рано или поздно сталкивается лбами с менеджером, который совершенно не заинтересован в том, чтобы совершенствовать уже готовое. То нужно реализовать что-то новое, то срочно потушить пожар, то исправить какой-то баг В общем, причина отложить рефакторинг запущенной кодовой базы у них всегда найдётся.

И даже когда пытаешься им объяснять, какие преимущества даёт опрятный код, они то ли не понимают, то ли не хотят понимать. У них только затраты и сроки на уме, а до качества никому нет дела. И получается, что вы абсолютно бессильны что-то сделать с техническим долгом, который всё копится и копится. Программисты работают на прод, а прод на запросы нетерпеливых пользователей. За рефакторинг никто платить не будет. Положение выглядит безнадёжным.

Попадая в такую ситуацию, многие толковые разработчики просто пакуют вещи и уходят из компании. И очень скоро из-за текучки в отделе уже двери не закрываются: одни уходят, другие приходят

Менеджеры не программисты


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

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

Пять аргументов, которые вы можете привести


1) Рефакторинг поможет снизить волатильность предельных издержек на единицу функциональности ПО

Это точная цитата из замечательного выступления Дж. Б. Рейнсберга на конференции на тему Экономика в разработке ПО. Стойте, не уходите! Всё, что здесь сказано, вам прекрасно известно, просто оно сформулировано в абстрактно-заумном духе.

Пойдём по порядку:

  • Рефакторинг поможет снизить пока что всё нормально
  • волатильность иными словами, непредсказуемость
  • предельных издержек то есть сколько ресурса потребует производство ещё одной дополнительной единицы
  • на единицу функциональности ПО она же фича, имеющая ценность для бизнеса. Ура! Ценность для бизнеса это как раз то, что нам нужно.

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

2) За последние месяц 63% ресурса разработки ушло на исправление проблем с качеством продукта

Ну, цифры здесь подставите свои. Смысл в том, чтобы подкрепить посыл количественными данными. Технический долг не может не влиять на ваши повседневные рабочие процессы. Можно ли это доказать? Разумеется!

Вот несколько метрик, на которые вы можете обратить внимание:

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

Покажите руководству, во что именно выливается для них плохое качество кода. А ещё лучше найдите способ перевести эти цифры в денежный эквивалент.

Как-то я присутствовал на post-mortem бага, которого можно было бы избежать, если бы была проведена проверка типов данных. Код писался на JavaScript. Как раз в то время в компании велись споры о том, стоит ли переходить на TypeScript.

Разработчики, которые разбирались в случившемся, не поленились поднять данные и сумели оценить урон, который баг нанёс бизнесу. Оказалось, что за несколько месяцев своего существования баг высосал из компании миллион канадских долларов. Миллион! Одного этого с головой бы хватило, чтобы окупить стоимость перехода на TypeScript.

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

3) С технической точки зрения, чтобы быстрее выпустить продукт, мы работали в кредит. Теперь нужно выплатить часть долга, чтобы время выхода на рынок не возросло

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

Или можно, допустим, представить компанию как ресторан, а кодовую базу как его кухонную зону. По мере накопления грязной посуды обслуживать возрастающее число посетителей становится всё сложнее и сложнее. Сотрудникам нужно успевать перемыть посуду в перерывах между приготовлением пищи.

4) Если инвестировать 10% рабочего времени в качество кода, текучесть кадров существенно снизится.

До сих пор мы говорили об очевидных последствиях низкого качества кода. Но есть одна коварная вещь, которую легко упустить из вида: текучка.

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

А теперь давайте зададимся вопросом: во что компании обходится замена уволившегося разработчика? Нового специалиста нужно найти, провести через процесс найма, ввести в курс дела. Это требует вложений, занимает время и затормаживает всю команду. Руководство однозначно предпочло бы не заниматься подобными перестановками каждый год. Поэтому снижение текучести может стать серьёзным аргументом. А сам факт наличия плана по устранению технического долга сразу даст всей команде +100 к мотивации.

5) Если вложить 20% ресурса в рефакторинг, FRT сократится вдвое и для производительности разработчиков ROI будет положительным

FRT это время отклика, важная метрика для пользовательской поддержки. Бизнес стремится к тому, чтобы клиенты были всем довольны.

Здесь мы проделываем следующее:

  • Выбираем метрики, которые имеют большой вес в техподдержке;
  • Находим пару проблем, которые возникают регулярно и требуют вмешательства разработчиков;
  • Предлагаем план по сокращению числа обращений в техподдержку за счёт устранения исходной причины.
  • Если проблемы такого рода разрешатся, разработчиков реже будут привлекать к задачам, связанным с поддержкой пользователей. То есть у них освободится больше времени, чем было затрачено на рефакторинг, а значит, вложение окупится тот самый положительный ROI.

В конечном счёте, решать им


С этим никто не поспорит. Я предложил пять аргументов, которыми можно убедить людей в важности устранения технического долга, но теперь, думаю, следует дать ещё один дополнительный совет, прежде чем вы пойдёте обрабатывать какого-нибудь менеджера.

Проводите рефакторинг по ходу дела

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

Правило работает даже для баз с legacy-кодом. Ну ладно, в божеский вид вы её к завтрашнему дню точно не приведете. И пытаться проводить крупномасштабный рефакторинг между делом тоже дохлый номер. Но с таким подходом положение, по крайней мере, не будет ухудшаться. А в работе с lrgacy-кодом приходится ориентироваться не на хорошо, а на лучше.

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

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru