среда, 17 мая 2017 г.

Все еще дебажите?

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

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




Скажем, я плохой императив процедурного программиста, и это мой Java-код:
class FileUtils {
  public static Iterable<String> readWords(File f) {
    String text = new String(
      Files.readAllBytes(Paths.get(f)),
      "UTF-8"
    );
    Set<String> words = new HashSet<>();
    for (String word : text.split(" ")) {
      words.add(word);
    }
    return words;
  }
}
Этот статический служебный метод читает содержимое файла, а затем находит в нем все уникальные слова. Довольно просто. Однако, если код не работает, что мы делаем? Предположим, что это файл:

We know what we are,
but know not what we may be.


Из него мы получаем этот список слов:

"We"
"know"
"what"
"we"
"are,\n"
"but"
"not"
"may"
"be\n"

не выглядит правильно ... какой следующий шаг? Либо чтение файла не работает правильно, либо парсер не работает. Давайте отладим это? Давайте дадим коду файл и шаг за шагом, отслеживая и наблюдая за переменными. Мы обязательно найдем ошибку и исправим ее. Но когда появится подобная проблема еще раз, нам снова придется отлаживать! И это то, что модульное тестирование должно предотвращать автоматически.

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

Если вы считаете, что отладка выполняется быстрее и проще, подумайте о качестве своего кода

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

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

Таким образом, если вы считаете, что отладка выполняется быстрее и проще, подумайте о качестве вашего кода. Бьюсь об заклад, у нее есть много возможностей для рефакторинга, как и код из приведенного выше примера. Вот как я мог бы его модифицировать. Прежде всего, я бы превратил его в класс, потому что статические методы - плохая практика:
class Words implements Iterable<String> {
  private final File file;
  Words(File src) {
    this.file = src;
  }
  @Override
  public Iterator<String> iterator() {
    String text = new String(
      Files.readAllBytes(Paths.get(this.file)),
      "UTF-8"
    );
    Set<String> words = new HashSet<>();
    for (String word : text.split(" ")) {
      words.add(word);
    }
    return words.iterator();
  }
}
Код выглядит лучше уже, но сложность еще присутствует. Далее, я бы разбил его на более мелкие классы:
class Text {
  private final File file;
  Text(File src) {
    this.file = src;
  }
  @Override
  public String toString() {
    return new String(
      Files.readAllBytes(Paths.get(this.file)),
      "UTF-8"
    );
  }
}
class Words implements Iterable<String> {
  private final String text;
  Words(String txt) {
    this.text = txt;
  }
  @Override
  public Iterator<String> iterator() {
    Set<String> words = new HashSet<>();
    for (String word : this.text.split(" ")) {
      words.add(word);
    }
    return words.iterator();
  }
}

Что вы думаете теперь? Написание теста для класса Words является довольно тривиальной задачей:
import org.junit.Test;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
public class WordsTest {
  @Test
  public void parsesSimpleText() {
    assertThat(
      new Words("How are you?"),
      hasItems("How", "are", "you")
    );
  }
}
Сколько времени это заняло? Меньше минуты. Нам не нужно создавать временный файл и загружать его данными, потому что класс Words ничего не делает с файлами. Он просто анализирует входящую строку и находит в ней уникальные слова. Теперь это легко исправить, поскольку тест небольшой, и мы можем легко создать больше тестов; например:
import org.junit.Test;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
public class WordsTest {
  @Test
  public void parsesSimpleText() {
    assertThat(
      new Words("How are you?"),
      hasItems("How", "are", "you")
    );
  }
  @Test
  public void parsesMultipleLines() {
    assertThat(
      new Words("first line\nsecond line\n"),
      hasItems("first", "second", "line")
    );
  }
}
Мое мнение состоит в том, что отладка необходима, когда количество времени, затрачиваемое для написания модульного теста, значительно больше, чем время, необходимое для нажатия кнопок Trace-In / Trace-Out. И это логично. Мы все ленивы и хотим быстрых и легких решений. Но отладка (дебаг) сжигает время и энергию. Это помогает нам находить проблемы, но не препятствует их повторному появлению.

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

Я считаю, что нет места отладке(дебагу) в чистом объектно-ориентированном программировании. Только модульное тестирование! 

Комментариев нет:

Отправить комментарий