Pull to refresh

Душа в программе

Reading time4 min
Views579
Помните начало фильма «Я — робот»? Там профессор читает лекцию о явлении «Души» в коде программы:

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

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

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

Сначала обработчик имел такой вид:

private void txtMain_TextChanged(object sender, EventArgs e)<br>{<br>  //проверяем, что количество символов увеличилось (при нажатии на backspace - ничего не делать)<br>  if (txtMain.Text.Length > textlen)<br>  {<br>    //проверяем последний символ<br>    if (txtMain.Text[txtMain.TextLength - 1] == 'a')<br>    {<br>      //оборачиваем try - чтобы ловить ошибку<br>      try<br>      {<br>        string newSpace = "<inputted a>";//какая-то условная строка для вставки<br>        int curPosition = txtMain.SelectionStart;//получаем текущую позицию<br>        txtMain.Text = txtMain.Text.Insert(curPosition, newSpace);//вставляем текст<br>        txtMain.SelectionStart = curPosition + newSpace.Length;//перематываем на место редактирования<br>      }<br>      catch (Exception ex)<br>      {<br>        MessageBox.Show(ex.Message);//сообщение об ошибке<br>      }<br>    }<br>  }<br>  textlen = txtMain.Text.Length;//каждый раз при изменении текста, изменяется сохраненная длина<br>}<br><br>* This source code was highlighted with Source Code Highlighter.


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

if (txtMain.Text[txtMain.SelectionStart - 1] == 'a')<br><br>* This source code was highlighted with Source Code Highlighter.


И вот тут началось самое интересное — метод стал выкидывать IndexOutOfRangeException на 14 строчке:
txtMain.Text = txtMain.Text.Insert(curPosition, newSpace);//вставляем текст<br><br>* This source code was highlighted with Source Code Highlighter.


При этом при входе в функцию (для случая последовательного ввода текста — т.е. когда мы нужный символ добавляем в конец) в обоих случаях оба свойства имели одинаковое значение — и TextLength, и SelectionStart. Проход по телу функции происходил тоже одинаково, но в случае SelectionStart выпадал exception.

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

А оказалось все довольно просто. В момент вставки нужной строчки (как раз место, где падал exception), вызывалось опять событие TextChanged (логично!!) но для него SelectionStart был равен 0, в результате получали попытку сравнить -1 символ строки с заданным, но дебаггер показывал ошибку именно на 14 строчке, а не на 7й.

Собственно, финальный код функции таков:

private void txtMain_TextChanged(object sender, EventArgs e)
{
  //проверяем, что количество символов увеличилось (при нажатии на backspace - ничего не делать)
  // и проверяем на положительность старта
  if (txtMain.Text.Length > textlen && txtMain.SelectionStart > 0)
  {
    //проверяем последний символ
    if (txtMain.Text[txtMain.SelectionStart - 1] == 'a')
    {
      //оборачиваем try - чтобы ловить ошибку
      try
      {
        string newSpace = "<inputted a>";//какая-то условная строка для вставки
        int curPosition = txtMain.SelectionStart;//получаем текущую позицию
        txtMain.Text = txtMain.Text.Insert(curPosition, newSpace);//вставляем текст
        txtMain.SelectionStart = curPosition + newSpace.Length;//перематываем на место редактирования
      }
      catch (Exception ex)
      {
        MessageBox.Show(ex.Message);//сообщение об ошибке
      }
    }
  }
  textlen = txtMain.Text.Length;//каждый раз при изменении текста, изменяется сохраненная длина
}


* This source code was highlighted with Source Code Highlighter.
Tags:
Hubs:
Total votes 14: ↑9 and ↓5+4
Comments13

Articles