Pull to refresh

Comments 10

Хороший пример, но в данном случае не будут ли классы инкапсулированы? Ведь предполагается, что нужны только в паре. Или модуль, в котором они будут лежать, будет "инкапсулирован" и будет использоваться уже "публичным" модулем.
Мотивацию автор описывает абсолютно правильно, но пример использует плохой. В его предлагаемом решении класс Text является бесполезным, а классу Words стоило бы принимать параметром Reader, а не строку, что дало бы ему сразу возможность работать с файлами/строками и т.д. Плюс к этому, это позволило бы написать более оптимальную реализацию итератора для больших объемов данных.
Почему вы преподносите отладку, как что-то плохое и недопустимое в программировании? В данном конкретном случае — вполне возможно.
"Специалисты по ООП не отлаживают код" — совсем что ли не отлаживают, как им это удается?
Лично я считаю что отладка — это неизбежный пока процесс, особенно в сложных приложениях и на начальной стадии разработки.
Отладка необходима как ответ на проблему "человеческого фактора" — если программист забыл включить некий элемент из модели которую он программирует.
"Любая проблема проектирования может быть решена введением дополнительного абстрактного слоя, за исключением проблемы слишком большого количества дополнительных абстрактных слоев" в действии.
В идеальном сферически-конно-вакуумном мире это все смотрится отлично. В более-менее реальных задачах и проектах идеальная теория начинает буксовать. Начнем с того, что сам ООП патерн не есть венец творенья сего мира, а скорей некий отросток сбоку. Никто еще не доказал, что ООП лучше процедурного стиля, более того, имеются мнения, что все как раз наоборот.
Во-вторых, подход «тестировать каждый пук», в реальной задаче приводит к сильной атомизации кода. Простой и понятный метод дробится на кучу вспомогательных сущностей, которым бывает даже трудно дать вразумительное название и определить четко их назначение.
Кроме того, контекстно-зависимые задачи сложно бывает даже разбить на независимые куски, поскольку они постоянно обращаются к общему контексту и их выполнение зависит от его текущего состояния. Кто реализовывал задачу разбора синтаксиса, меня поймет.
Ну и, наконец, смысл? Есть дебаггер для отлова ошибочных ситуаций. Есть комплексный тест, который покрывает различные ситуации. Зачем дробить, тестировать по отдельности каждое действие, а затем их комбинацию, а затем и все вместе, если можно сразу протестировать все вместе? Общий тест точно также свалится. Ошибку можно откопать долбаггером.
Это Вы ещё про функциональное программирование не сказали.
И в этой статье очень сильный уклон туда (пассаж про существительные против глаголов).
Кто реализовывал задачу разбора синтаксиса, меня поймет.

Во! Только хотел сказать.

В начале 1990-х я был большим поклонником ООП, но потом стал писать некий специфический компилятор и обнаружил вот что:

Если делать всякие деревья и графы из узлов сотни типов, после чего ползать по ним и менять поддеревья, то гораздо удобнее просто сделать функцию walk с аргументов-указателем на другую функцию с switch внутри, где манипулировать с поддеревьями в зависимости от типа узла — чем делать "объектно-ориентированным" образом — т.е. наплодить сто классов и в каждом несколько десятков виртуальных функций.
Одна из целей ООП — избежать модификации существующего кода, заменив ее наследованием.
Это актуально при использовании всяких framework'ов, и обычно мы используем эту мощь даже не задумываясь о том, что без ООП это бы просто не работало.

В части написания компиляторов множество классов — это более простой путь в части поддержки, т.к. это позволяет не забыть добавить обработку для всех возможных типов узлов.
switch просто писать, но очень легко пропустить обработку нового узла, и компилятор тебе не поможет. Это можно нивелировать другими практиками (наличие default-ветки с Exception'ом + полное покрытие тестами), но не все это делают, сами понимаете. :-)

В любом случае, ООП предназначен для вполне конкретной цели: уменьшить сложность понимания логики кода. Если при использовании ООП у вас сложность только повышается — вы не правильно используете ООП, или используете его там, где он не нужен.
IMHO, автор не понимает всей сущности автотестов.
Что даст автотест на класс Words?
Тестирование маленькой функции с вполне определенной семантикой.
Это всегда просто, обычно как раз такие тесты и пишутся.

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

Например, возьмем простой проект — калькулятор (имитирующий реальный калькулятор с памятью, это важно!). Написать тесты на сами функции — тривиальная задача. А в чем здесь наиболее вероятны ошибки? Например, в таких последовательностях нажатий кнопок: "5", "+", "=", "=" (для тех, кто не знает — попробуйте на калькуляторе Windows или на реальном). И тестировать здесь нужно не отдельные операции, а их связки.
Если посмотреть на любой объект со стороны теории, то он выглядит как конечный автомат. И вызовы его методов меняют внутреннее состояние, после чего вызовы методов могут поменять принцип своей работы. Следовательно, нужно тестировать не отдельные вызовы, а сценарии.

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

Вот так-то.
А теперь небольшое слово про отладку.
Отладка нужна тогда, когда по коду кажется, что все должно работать, но оно почему-то не работает.
Забыли какой-то сценарий, и не можем быстро понять, какой именно. Как правило, ошибка при этом содержится в данных, а не в логике кода, а причина находится довольно далеко от неверно работающего кода. И в этом случае stack trace позволяет однозначно выцепить ветку, в которой происходит ошибка, выкинув все побочные варианты. Анализировать все варианты в голове просто дольше.
Т.е. дебаггер — это просто средство экономии времени для таких сложных случаев. И как показывает практика, чаще ошибку проще найти путем анализа кода, ведь для дебага нужно поднять соответствующее окружение, а это часто дороже, чем анализ всех веток.
Sign up to leave a comment.