Иди вперед и заканчивай работу над своим библиотечным кодом

Иди вперед и заканчивай работу над своим библиотечным кодомУ тебя будет достаточно много времени, что узнать, что о нем думают другие. Просто перебрось его им через стену прямо сейчас. Уверен, все будет хорошо".

Многие успешные компании живут под девизом “Ешь то, что сам производишь”(your own dog food”). Другими словами, чтобы сделать свой продукт как можно лучше, нужно активно его применять для себя.

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

При использовании техники под названием Разработка на базе тестирования (Test Driven Development) сам код пишется лишь после того, как написан недостающий или непроходной (failing) тест для нового кода. Тест всегда создается первым. Как правило, изначально такой тест не проходит по двум причинам: либо тестируемый код не существует, либо он пока не содержит логики, необходимой для прохождения теста.

Когда вы сначала пишете тесты, вы смотрите на свой будущий код глазами его пользователей, а не разработчика. И это самое главное. Вы увидите, что вы способны спроектировать более удобные и совместимые друг с другом интерфейсы, поскольку вам самим приходится их использовать.

Кроме того, написание тестов до кода помогает избавиться от слишком запутанного дизайна и сосредоточиться на выполнении самой задачи. Рассмотрим следующий пример —создание программы игры в крестики - нолики для двух пользователей.

Когда вы только задумываетесь о дизайне кода для этой игры, вы можете иметь в виду следующие классы: TicTacToeBoard, Cell, Row, Column, Player, User, Peg, Score, и Rules. Давайте начнем с класса TicTacToeBoard, который представляет само игровое поле для игры (в терминах игровой логики, а не интерфейса пользователя).

Вот первый возможный тест для класса TicTacToeBoard, написанный на С if с использованием среды тестирования NUnit. Он создаст игровое поле (доску) и контролирует состояние (конец) игры.

[TestFixture]
public class TicTacToeTest
(
private TicTacToeBoard board;
[Setup]
public void CreateBoard()
(
board = new TicTacToeBoard();
)
[Test]
public void TestCreateBoardO
(
Assert.lsNotNull(board);
Assert.1sFalse(board.GameOver);
)
>

Этот тест не проходит в силу того, что класса TicTacToeBoard не существует —компилятор сообщит об ошибке. Если тест будет пройден успешно, вас это удивит, не так ли? Это может случиться не так уж часто, но такое бывает. Всегда убедитесь, что ваши тесты вначале падают —до того, как проходятся успешно: это необходимо для того, чтобы выявить потенциальные ошибки в самом тесте. Напишем реализацию для этого класса:

public class TicTacToeBoard (
public bool GameOver (
get ( return false;
)
)
)

Свойство GameOver сейчас возвращает значение false. В общем случае вам захочется написать как можно меньше кода, чтобы заставить его проходить тест. В этом есть некий самообман, вы ведь знаете, что код неполный. Но это в данном случае не играет роли, поскольку дальнейшие тесты вынуждают вас возвращаться назад и добавлять функциональность.

Каков следующий шаг? Сначала вы должны решить, кто начинает игру, т. е. задать первого игрока. Напишем тест, устанавливающий первого игрока:

[Test]
public void TestSetFirstPlayer() (
// что здесь должно быть?
)

В этом месте тест требует принятия решения. Перед тем как он может быть пройден, вам необходимо решить, каким образом представлять игроков в программе и как приписывать их к игровому нолю. Вот одна из возможностей:

board.SetFirstPlayer(new Р1ауег("Afark"):

Это сообщает игровому полю, что игрок Mark будет ставить крестики.

Все эго, без сомнения, будет работать. Но требуется ли вам класс Player или имя первого игрока? Вполне возможно, позже вам придется отслеживать победителей. Но пока в этом нет особой нужды. Принцип YAGNI1 (You Arent Gonna Need It —Вам не нужно) предписывает вам не браться за реализацию какого-либо свойства до тех пор, пока в нем нет необходимости. На данном этапе нет острой нужды в классе Player.

Вспомним, что мы еще не написали метод SetFi rstР1ауег() в классе TicTacToeBoard и не написали класс Player. Мы все еще пытаемся написать тест. Предположим, что следующий код задает первого игрока:

board.SetFi rstPlayer(”);

Это сообщает коду, что первый игрок ставит крестики. Этот вариант проще, чем первый. Но он скрывает в себе некоторую опасность: передача произвольного символа методу SetFirstPlaycr() означает, что потребуется код для проверки, принимает ли данный параметр значение О или X, и вам придется также предусмотреть ситуацию, когда этот параметр принимает другое значение. Итак, попробуем упростить еще больше. Мы будем использовать флажок для индикации первого игрока. Исходя из этого, напишем наш модульный тест следующим образом:

[Test]
public void TestSetFirstPlayer() (
board.FirstPlayerPeglsX = true;
Assert.lsTrue(board TirstPlayerPeglsX);
)

Мы можем объявить FirstPlayerPeglsX логическим свойством и присвоить ему требуемое значение. Это выглядит простым и удобным в использовании —намного проще, чем в случае создания класса Player. Как только тест готов, вы можете запустить его после написания реализации для свойства FirstPlayerPeglsX в классе TicTacToeBoard.

Вы видите, что мы начали с предположения, что у нас есть класс Player, а пришли к тому, что можно просто ввести логическую переменную? Такое упрощение произошло в процессе создания тестов для кода, еще до написания собственно тестируемого кода.

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

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

Когда вы проектируете и разрабатываете объектно-ориентированные системы, вы чувствуете необходимость использовать объекты. Многие склонны считать, что системы просто обязаны состоять только из объектов, неважно, нужны они или нет. Добавление кода без особой надобности —всегда плохая идея.

Дизайн на базе тестирования заставляет вас поразмышлять о том, как вы будете использовать код еще до того, как вы получаете возможность его писать (или, по крайней мере, пока вы зашли слишком далеко в реализации). Это заставляет вас думать об удобстве его использования и открывает вам более практичный дизайн.

И, конечно, дизайн не может быть окончательным в самом начале. Вы непрерывно будете добавлять все новые тесты, писать новые фрагменты кода и перепроектировать классы в течение их жизненного цикла.

Используйте код до его сборки. Используйте разработку на базе тестирования как инструмент проектирования. Он позволит вам найти более практичный и простой дизайн кода.

На что это похоже?

Вы в любой момент ощущаете реальный повод к написанию кода.

Вы можете сосредоточиться на проектировании интерфейса, не

отвлекаясь на детали его реализации.

tel-icq