С моим первым опытом TDD на меня снизошло озарение. Последние 2-3 года со всех сторон атакует информация «TDD это хорошо», «Тебе нужно TDD», отчего
За последний год прочел несколько хороших книг, даже не по TDD, а просто по хорошим практикам программирования/проектирования, которые прямо заявляют «если вы не практикуете TDD — бросьте все и начните прямо сейчас». Но все равно перечитав достаточно теории не совсем было ясно откуда такая настойчивость и уверенность авторов, что оно прям так всем надо.
Зачем Вам компилятор?
Компилировать? Я как-то раз понял, что не только. Точнее не для того, чтоб получить какие то бинарники, DLLки. Набирая код, я время от времени смотрю в окно с ошибками и исправляю если что-то появилось. Такую замечательную возможность мне предоставляет парсер, который крутится в фоне и проверяет код на наличие простейших ошибок. Но иногда этого недостаточно. Иногда компилятору надо скомпилировать/собрать все исходники проекта, чтоб обнаружить ошибку. Такая ситуация случается например в ASP.NET, когда из своего usercontrol'а мы удаляем публичный метод, но ошибки не видим, так как компилятор должен пересобрать этот юзерконтрол. В таких случаях нужно полностью пересобрать проект, но заметьте, что нам по сути не нужно то, что мы получили в результате сборки, нам на данном этапе была важна только информация об отсутствии/наличии ошибок.
К чему я клоню?
К тому, что компилятор в подобной ситуации нужен в основном для того, чтобы указать на наши ошибки программирования. Обычно это простые ошибки, но мы любим компилятор за то, что он прямо указывает на них. Помните один из минусов слабо связанных архитектур? Из-за того, что в них преобладает позднее связывание и вместо наследования предпочитают композицию, то много ошибок мы уже не можем отловить во время написания кода.
class Darth
{
public void FeelForce()
{
...
}
public void DoHeavyBreath()
{
...
}
}
class Luke : Darth
{
public void DefeatAllEnemies()
{
...
}
}
void main()
{
Luke luke = new Luke();
luke.FeelForce();
luke.DefeatAllEnemies();
luke.DoHeavyBreath(); // нет, это не то, что может делать Люк
}
Вызов метода DoHeavyBreath у класса Luke — логическая ошибка не видимая компилятору. Это ошибка более высшего уровня. Но программирование с каждым годом стремится вверх, становится все абстрактнее, поэтому нам нужны инструменты, чтоб справляться со сложностью. Один из необходимых инструментов является парсер ошибок более высшего уровня. Так же как нам компилятор указывает на синтаксические ошибки.
Код выше стоило бы переписать так для большей грамотности:
abstract class Jedi
{
public void FeelForce()
{
...
}
public virtual void DefeatAllEnemies()
{
throw new NotImplementedException();
}
public virtual void DoHeavyBreath()
{
throw new NotImplementedException();
}
}
class Darth : Jedi
{
override void DoHeavyBreath()
{
... // do heavy breath
}
}
class Luke: Jedi
{
override void DefeatAllEnemies()
{
... // defeat all enemies
}
}
void main()
{
Luke luke = new Luke();
luke.FeelForce();
luke.DefeatAllEnemies();
luke.DoHeavyBreath();
}
Что изменилось? Мы все имеем ту же логическую ошибку и компилятор все так же не может ее обнаружить. Но теперь мы можем воспользоваться TDD для обнаружения этой ошибки. Мы создаем метод для тестирования метода main. Простейшим способом
void test_that_luke_cannot_do_heavy_breath()
{
try
{
main();
}
catch(NotImplementedException ex)
{
Assert.Fail("Unimplemented method was called!");
}
}
Я лично вижу в таком подходе не что иное, как инструмент для борьбы со сложностью высокоуровневых архитектур. У
Автор: amorphius