Отладка - это «процесс запуска программы / метода в интерактивном режиме, прерывающий выполнение после каждого оператора и показывающий ...» В двух словах это очень полезная техника ... для плохого программиста или для старого программиста, который все еще пишет процедурный код на 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 () (глагол). Напротив, второй пример касается того, что будет достигнуто. Это либо текст файла, либо слова текста (оба являются существительными).
Я считаю, что нет места отладке(дебагу) в чистом объектно-ориентированном программировании. Только модульное тестирование!
Комментариев нет:
Отправить комментарий