Пространство проектирования исправлений ошибок в коде.

(Emerson Murphy-Hill, Thomas Zimmermann, Christian Bird, and Nachiappan Nagappan. 2014. The Design Space of Bug Fixes and How Developers Navigate It. Published in: IEEE Transactions on Software Engineering ( Volume: 41, Issue: 1, Jan. 1 2015 ) DOI: 10.1109/TSE.2014.2357438)

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

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

using System;
  public class Program {
    public static int Puzzle(int x) {
      return x * (x-1) / 2;
  }
  

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

int i = 0;
int z = 0;
while (x > 0 && ++i <= x)
{
  z = z+i-1; 
}
return z;

Другой предложил такое красивое решение:

return Enumerable.Range(0, x).Sum(); 

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

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

Вот два основных вопроса, на которые данное исследование призвано ответить:

1. Какие способы обычно существуют для исправления обнаруженной в коде ошибки?

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

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

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

1. Степень распространение информации по компонентам системы.

2. Степень изменения поведения пользователя для взаимодействия с системой.

3. Видимость ошибки

4. Объем удаляемого функционала.

5. Объем изменений внутреннего кода.

6. Точность

7. Степень hardcoding-а.

8. Степень рефакторинга кода.

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

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

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

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

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

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

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

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

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

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

1.Управление рисками на этапе разработки.

2.Степень изменения интерфейса программной системы.

3.Целостность системы.

4.Понимание причины произошедшей ошибки.

5.Поведение пользователя.

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

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

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

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

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