четверг, 10 апреля 2014 г.

Об автоматизированном тестировании

и книге Стива Фримана "Growing Object-Oriented Software Guided by Tests"

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

Содержание

В мире разработки ПО нет более неоднозначной темы, чем тестирование. Нет, я не о тестировании ПО в целом, тут все ясно, оно нужно, без него никак. А вот отношение к автоматическим тестам и особенно к юнит-тестам весьма неоднозначное.

Кто-то считает, что автоматическое тестирование ограничивается юнит-тестированием и 100% покрытие кода является обязательным. Есть и такие, кто считает юнит-тесты пустой тратой времени и максимум ограничиваются интеграционными тестами, а все остальное считают проблемами QA отдела. Ну есть и те, кто находится где-то посередине и просто старается использовать инструмент по назначению.

Чем обусловлен такой разброс мнений? Является ли автоматическое тестирование обязательным инструментом или без него можно обойтись? Замедляют ли они разработку и окупаются ли они позднее? Как они влияют на дизайн и процесс разработки? Все ли нужно тестировать и достаточно ли юнит-тестов? Нужно ли писать тесты до проакшн кода или же достаточно думать о контракте и спецификации модуля? Каким должно быть качество тестов?

Я постараюсь дать ответы на эти вопросы, опираясь на свой опыт и мнение авторов книги "Growing Object-Oriented Software Guided by Tests".

Почему столько мнений?

У этого вопроса есть простой ответ, хотя он вряд ли будет сильно полезным. Как и в любом вопросе, все дело в людях. Любой инструмент требует времени для изучения, и даже после этого никто не гарантирует, что он будет использован по назначению.

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

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

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

ЦИТАТА
Мы встречали команды, которые следуют базовым практикам (написанию и запуску тестов), но получают не тот результат, на который рассчитывали, поскольку не следуют более глубоким процессам, которые лежат за этими практиками.

Замедляют ли они разработку и окупаются ли они позднее?

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

Существует множество преимуществ автоматизированного тестирования, которые отлично описал Кент Бек в своем выступлении Developer Testing. Здесь и повторное использование затраченных усилий, и документация, и влияние на дизайн, и "ответственность" разработчика (accountability), но я напирал не на это.

Сколько нужно времени, чтобы проверить работоспособность новой возможности клиентской части нашей системы? Для этого нужно убедиться в работоспособности окружения, обновить базу данных, запустить сервер, 3 сервиса, запустить клиентское приложение, проклацать его до нужного места и ... увидеть, что она (новая возможность) не работает. Вполне возможно, что это именно на этом проекте было вечно разваленное окружение и для проверки даже самой простой возможности требовалось 15 минут, но я уверен, что это не уникальная ситуация.

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

Конечно я не говорю, что разработка любой возможности с автоматическими тестами будет быстрее, и сам я далеко не всегда придерживаюсь этого подхода. Но *довольно часто * дела обстоят именно так.

Хрупкие тесты вокруг хрупкого кода замедлят разработку и усложнят сопровождение. Но вменяемые тесты, которые проверяют "что" делает тестируемый код, не акцентируя внимание на том "как" он это делает упростят сопровождаемость кода.

ЦИТАТА
Также следует различать внешнее и внутреннее качество: внешнее качество определяет, насколько хорошо система отвечает потребностям заказчиков и пользователей (является ли она функциональной, надежной, доступной, отзывчивой и т.п.), а внутреннее качество говорит о том, насколько хорошо система отвечает потребностям ее разработчиков и администраторов (насколько ее легко понять, изменить и т.п.). Всем понятен смысл внешнего качества; обычно оно отражается в контракте на разработку ПО. Внутреннее качество не менее важно, но обычно его сложнее достичь. Внутреннее качество позволяет справляться с постоянными и непредвиденными изменения, который, на самом деле, являются неотъемлемой частью ПО. Поддержка внутреннего качества позволяет нам изменять поведение системы безопасным и предсказуемым образом, поскольку оно минимизирует риск того, что изменение потребует серьезных переработок системы.

Как тесты влияют на процесс разработки?

В некоторых методологиях, таких как XP, юнит-тестирование (особенно TDD) является частью методологии разработки, в большинстве же других случаях влияние тестов на методологию не столь явно. Но даже если такое влияние не является обязательным, попытка покрыть приложение автоматизированными тестами обязательно окажет влияние на многие аспекты разработки ПО.

clip_image002

Влияние тестов на дизайн достаточно очевидно. Чтобы проверить поведение класса, как минимум должна быть возможность создать класс в тестовом окружении. Если же класс неявно завязан на внешнее окружение, то сделать это будет крайне сложно. Именно поэтому наличие юнит-тестов приводит к большей гранулярности системы и более четкому интерфейсу и модульности классов (подробнее об этом см. в "Идеальная архитектура").

ЦИТАТА
Если мы сталкиваемся с возможностью, которую сложно протестировать, мы не задаемся вопросом, как это сделать; вместо этого мы спрашиваем себя: а почему это сложно сделать? Наш опыт говорит, что если код сложно протестировать, то улучшать нужно именно дизайн.

Типичным подходом к получению тестируемого дизайна является выделение зависимостей класса с передачей их через конструктор. Если это делать бездумно, то очень легко создать систему, каждый класс которой покрыт юнит-тестами, а в целом система является нерабочей (подробнее см. в "Тестируемый дизайн vs. Хороший дизайн").

Именно по этой причине юнит-тестов недостаточно и нужны более высокоуровневые автоматизированные тесты, которые тестируют целый модуль или подсистему (интеграционные тесты), и систему целиком (end-to-end тесты). Интеграционные тесты в этом случае покажут, как работают классы с реальным окружением и друг с другом, а end-to-end тесты покажут, что некоторая возможность работает корректно с точки зрения всей системы.

При интеграционные тесты также оказывают влияние на тестириуемый код, но на более высоком уровне. Для того, чтобы интеграционные тесты были возможными, система должна быть хорошо структурирована архитектурно: должны быть выделены четкие границы более крупных компонент, с понятными входами и выходами. End-to-end тесты тоже оказывают некоторое влияние на архитектуру, ведь автоматизация требует наличия некоторых программных интерфейсов для взаимодействия с системой, но они все же играют скорее роль пользовательской документации и спецификации системы.

Обычно, когда говорится о TDD, то подразумевается, что разработка "драйвится" именно модульными тестами, однако авторы книги "Growing Object-Oriented Programming Guided by Tests" идут дальше:

ЦИТАТА
Когда мы реализуем некоторую возможность системы, то мы начинаем с написания приемочного (acceptance) теста, который проверяет ожидаемую функциональность. Пока этот тест не проходит, мы знаем, что данная возможность системы еще не реализована; когда он проходит – функциональность готова. При разработке новой возможности, приемочный тест показывает, нужен ли нам код, который мы собираемся написать, что позволяет писать лишь то, что важно в данный момент[ST1] . После написания приемочного теста, для разработки конкретной возможности мы следует циклам тест/реализация/рефакторинг уже на уровне модулей.

image

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

Этот подход кажется более разумным, чем классический взгляд на TDD.

Как мы свами подходим к разработке некоторой функциональности? Вначале мы должны понять, что же мы хотим сделать с точки зрения пользователя: мы собираем или ожидаем от кого-то некоего описания возможности с высокоуровневой точки зрения. Лишь после этого мы начинаем двигаться (обычно) снизу вверх по иерархии системы, чтобы заполнить недостающие части для реализации этой возможности.

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

Но здесь мы можем столкнуться с теми же сложностями, что и при попытке покрыть юнит-тестами код, который написан без их учета: система может быть не предназначена для покрытия ее end-to-end тестами ни в каком виде. По своей природе acceptance-тесты могут возаимодействовать с базой данных, с пользовательским интерфейсом или вызывать batch-процесс из командной строки. Но даже в этом случае написание автоматизированного acceptance-теста может быть очень сложным процессом.

Авторы предлагают такой подход: работа над любым новым проектом должна начинаться с автоматизации сборки и развертывания системы, после чего начинается разработка работающего скелета приложения/модуля (см. Build Pattern: Shipping Skeleton). И только после этого начинается "выращивание" или наслаивание функциональности приложения.

ЦИТАТА
Ничего не дает лучшего представления о процессе, чем попытка его автоматизации.

Если посмотреть на автоматическое тестирование с такой стороны, то их влияние на процесс разработки (анализ, планирование, архитектуру, дизайн и кодирование) станет более очевидным.

Все ли нужно тестировать и достаточно ли юнит-тестов?

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

Вся возня с объектной декомпозицией и хорошим дизайном предназначены для борьбы со сложностью. Мы хотим выделить наиболее сложные аспекты системы в автономные модули, чтобы была возможность думать о них, не обращая внимание на незначительные детали. И здесь ценность юнит-тестов будет максимальной. Но что если основная сложность заключается в интеграции модулей между собой? Или вы пишите кастомный WCF канал, где вся логика заключается в переопределении нужных методов? Очевидно, что в этом случае значительно полезнее будут не модульные, а интеграционные тесты.

ЦИТАТА
Как много должно быть юнит-тестов, основанных на мок-объектах для разрыва внешних зависимостей, и как много должно быть интеграционных тестов? Мы не думаем, что есть единый ответ на этот вопрос. Все сильно зависит от контекста команды и ее окружения. Лучшее, что мы можем получить от тестов (которые являются важной частью TDD), так это уверенность в том, что наши изменения ничего не сломают: страх убивает прогресс. Сложность заключается в том, чтобы эта уверенность была оправданной.

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

Тесты вперед или назад?

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

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

ЦИТАТА
Любой код, включая тестовый код, должен подчеркивать, «что» он делает, а не «как»; чем больше деталей реализации содержится в тестовом методе, тем сложнее читателю кода понять, что из этого является важным. Мы стараемся убрать из тестового метода все, что не улучшает его описательных характеристик в терминах бизнес-области и тестируемой возможности. Иногда это требует реструктурирования кода, а иногда просто игнорирования синтаксического сахара.

На самом деле, TDD – это не столько test-first development, сколько specification first development. Юнит-тесты в этом случае не просто проверяют, что некоторый if statement был выполнен, они проверяют некоторый аспект поведения, о котором мы подумали еще до реализации. Но что если для спецификации я использую контракты, которые справляются с этой задачей замечательно, а для полного понимания, "что" должен делать класс мне удобнее набросать реализацию и понять "как" он это будет делать?

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

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

Я уважаю мнение авторов "Growing Object-Oriented Software Guided by Tests" по поводу написания тестов до кода, поскольку их подход основан на логике и аргументах, а не эмоциях. Тем не менее, я оставляю за собой и своими коллегами право самостоятельно решать, как им удобнее всего понимать, что же должна делать система: с помощью тестов или каким-то другим способом.

Каким должно быть качество тестов?

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

Как и в вопросах сложности, у хрупкости тестов есть неотъемлемая (essential) и привнесенная или случайная (accidental) составляющие. С одной стороны, тесты должны и будут изменяться при изменении требований; поскольку тест говорит о том, "что" должен делать тестируемый код, то при изменении предполагаемого поведения тест будет изменен (это часть неотъемлемой хрупкости тестов).

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

ЦИТАТА
Хрупкость тестов определяется не только тем, как написан сам тест; она также связана с дизайном системы. Если объект сложно отвязать от его окружения, поскольку зависимостей слишком много или они скрыты, тест будет падать при изменении отдаленных частей системы. При этом становится сложным оценить «эффект домино» при изменении кода. Поэтому хрупкость тестов можно использовать в качестве ценной обратной связи о качестве дизайна системы.

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

Подходы к улучшению кода тестов

Проверять "что", а не "как". Юнит-тесты не должны тестировать if statement, они должны тестировать соответствующий аспект класса. Не нужно тестировать закрытые методы, нужно относится к тестируемому коду, как к черному (максимум полупрозрачному) ящику. Чем абстрактнее тест, тем он более устойчивый, поскольку не подрывает инкапсуляции тестируемого кода.

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

Читабельность тестов и сообщений об ошибках. Как и любой хороший метод, тест должен оперировать на одном уровне абстракции и быть максимально декларативным. Вместо вызова нескольких методов для инициализации тестируемого кода можно сделать фабричный метод, а вместо пяти утверждений можно вызвать метод VerifyThatClassContainsRequiredData(), особенно если такая проверка нужна в нескольких местах.

В книге "Growing Object-Oriented Software Guided by Tests" этой теме уделяется очень серьезное внимание. Даже продакшн код стоит писать так, чтобы он читался как книга, так что уже говорить за тесты, которые вполне могут выступать в форме спецификации.

[Test]
public void
reportsTotalSalesOfOrderedProducts()
{
    havingReceived(anOrder()
       
.withLine("Deerstalker Hat", 1
)
       
.withLine("Tweed Cape", 1
));

    havingReceived(anOrder()
       
.withLine("Deerstalker Hat", 1
));

   
TotalSalesReport report = gui.
openSalesReport();
    report
.displaysTotalSalesFor("Deerstalker Hat", equalTo(2
));
    report
.displaysTotalSalesFor("Tweed Cape", equalTo(1));
}

Приведенный код не был написан с самого начала, а неоднократно изменялся и эволюционировал в то, что вы видите. Обратите внимание на его читабельность и полное отсутствие явных секций Arrange/Act/Assert, все это скрыто за более высокоуровневыми методами.

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

image

Использовать соответствующие паттерны. В тестах существует проблема создания тестовых данных. Существует два распространенных подхода: Object Mother и Test Data Builders (вот пост от автора данной книги Нета Прайса – Test Data Builders: an alternative to the Object Mother pattern). Первый паттерн содержит набор фабричных методов, возвращающих тестовые данные, а второй – предназначен для создания тестовых данных с заданными характеристиками. И если в случае Object Mother вы получаете объект в предустановленном состоянии, то Test Data Builder позволяет задать лишь те атрибуты, которые интересны в данном случае.

Я довольно часто совмещаю использование Test Data Builder с параметризованными юнит-тестами. Так, при разработке Verification Fakes (оболочки вокруг Microsoft Fakes для упрощение тестирования поведения), я использовал этот подход для тестирования моков. Поскольку мок должен запомнить набор действий, которые над ним выполнили корректным образом, то проверку легко параметризировать, сохранив лямбда выражения со списком действий и еще одно лямбда-выражение с ожидаемым результатом. В результате получится код, который генерирует тест-кейсы довольно декларативным образом:

[TestCaseSource("GetVerifyWithSequenceOfActionsTestCases")]
public void
Test_Verify_With_Sequence_Of_Actions(
   
List<Expression<Action<ILogWriter
>>> actions,
   
Expression<Action<ILogWriter>> verificationExpression, Times
times)
{
   
// Arrange
    var stub = new StubILogWriter
();
   
var mock = new Mock<ILogWriter
>(stub);

   
// Act
    foreach (var a in
actions)
    {
        a
.
Compile()(stub);
    }

   
// Assert
    mock.
Verify(verificationExpression, times);
}

public IEnumerable<TestCaseData
> GetVerifyWithSequenceOfActionsTestCases()
{
   
var builder = new Builder<ILogWriter
>();

   
yield return builder.Do(lw => lw.Write(42
))
       
.Do(lw => lw.Write(42
))
       
.Expects(lw => lw.Write(42), Times.Exactly(2));
}

Исходный код билдера – FluentTestCaseBuilder.cs, дополнительные примеры использования – MockActionsParametrizedTests.cs.

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

О книге "Growing Object-Oriented Software Guided by Tests"

clip_image004

Обилие цитат в этой заметке должно дать некоторое представление об этой книге и моего мнения о ней. Если этого недостаточно, то готов выразиться более четко: "Growing Object-Oriented Software Guided by Tests" – это одна из лучших книг об ООП и юнит-тестирования, которую я держал в руках. Она отлично дополняет пару других книг: "The Art of Unit Testing" Роя Ошерова и "Dependency Injection in .NET" Марка Симана, но является несколько более продуманной и четкой.

В книге есть все: начиная от связи ООП и ФП (да, авторы сочетают оба эти подхода, ООП – на верхнем уровне, ФП и неизменяемость на уровне реализации), заканчивая перечнем типов зависимостей и тестированием асинхронного и многопоточного кода.

При этом авторы действительно знают, о чем пишут. Как никак, именно они изобрели понятие Mock Object и написали jMock. Примеры их кода весьма реалистичны за счет использования сквозного примера, и очень показательны. Любовь к мокам зачастую приводит к несколько необычному дизайну (с моей точки зрения), а попытка юнит-тестирования многопоточного кода несколько пугает своей сложностью, но, в целом, полезных мыслей множество.

ЦИТАТА
Объектно-ориентированная система является графом взаимодействующих объектов. Система строится путем создания объектов и последующего их объединения таким образом, чтобы они могли обмениваться сообщениями между собой. Поведение системы является возникающим свойством композиции объектов – благодаря выбору объектов и способа их взаимодействия.

В целом же, это одна из лучших книг о дизайне и юнит-тестировании!

Оценка: must have!

З.Ы. Все примеры в книге на Java, но это нисколько не напрягает.

Дополнительные ссылки

Официальный веб-сайт книги "Growing Object-Oriented Software Guided by Tests".

Другие книги о дизайне и тестировании:

Другие посты по этой теме:

23 комментария:

  1. Этот комментарий был удален автором.

    ОтветитьУдалить
  2. Отличный обзор и комментарии толковые! Спасибо. "Именно поэтому я считаю не корректным требовать писать тесты до кода" +1, "Когда мы реализуем некоторую возможность системы, то мы начинаем с написания приемочного (acceptance) теста, который проверяет ожидаемую функциональность. Пока этот тест не проходит, мы знаем, что данная возможность системы еще не реализована; когда он проходит – функциональность готова" +10 :)

    ОтветитьУдалить
  3. А я сейчас с основами ОО на Java барахтаюсь. Это, вообще-то, мой первый язык программирования. Считаю нужным изучать и другие языки. Методом сравнений соответствий и различий между языками программирования можно добиться эффекта синергизма в понимании этих языков. А также и того, о чем пишет автор постов. Не так ли? Какую последовательность изучения выбрать?

    Статьи очень интересные. Хочется побольше накопить знаний, чтобы извлечь побольше пользы. Буду очень признателен за совет.

    ОтветитьУдалить
    Ответы
    1. @Владимир: тут сложно дать совет. Я начинал с С++, изучил его достаточно плотно, и также довольно плотно изучал ООП и переключился на другой язык (C#) лишь через 5 лет.
      Так что я бы советовал углубиться в текущий язык (Java - отличный вариант), чтобы стать его экспертом. Вот будучи экспертом переходить на другой язык будет просто, поскольку можно будет проводить не поверхностные аспекты языка или runtime-а, а глубинные основы.

      Удалить
    2. Сергей, огромное тебе спасибо за ответ и поддержку моего выбора.

      Я думаю, что процесс изучения Java займет меньше времени чем С++, поскольку этот язык требует меньшей квалификации от программиста ввиду меньшей его сложности.
      Потом можно переключиться на С#. Это на некотором этапе можно делать параллельно с изучением Java, чтобы преобразовывать коды, к примеру, при изучении паттернов.
      После этого можно будет подключить и С++. Да и твои статьи, считаю, мне в этом здорово помогут: Особенно анализ литературы по программированию.
      Относительно литературы по Java у меня вопросов нет. Если начну изучать С#, и возникнут вопросы по литературе, ты не будешь возражать, если я снова обращусь к тебе с просьбой, что нужно прочитать и освоить каждому программисту, чтобы разобраться с основами этого языка?
      Если честно, то я и сейчас не отказался бы попросить тебя о соответствующем списке литературы по С#, потому что подобрался к паттернам и скачал неплохую книжку и коды по 23-ем шаблонам на языке С#. Если у тебя нет проблем с украинским языком, ты бы cмог с ней ознакомиться в оригинале. Объем совсем небольшой, около 100 страниц. Возможно, ты уже и прочел эту книгу: "Дизайн-паттерни - просто як двері", автор Андрій Будай. Да и перевод этой книги на русский для меня особого труда не составил бы. Могу, если потребуется, предложить свои услуги. От такой книги ни один С#-программист не откажется.

      С уважением, Володимир Ващук.

      Удалить
    3. Владимир, я не совсем уверен, что процесс изучения Java займет меньше времени, ведь в Java существенно больше стандартная библиотека, и, как следствие, количество возможных инструментов. Вместо того, чтобы переключаться на другой язык вполне можно таки копнуть глубже: посмотреть многопоточность, асинхронное программирование, заняться юнит-тестированием. Все это можно изучать "с примерами некоего языка программирования", легко используя повторно эти знания в других языках.
      З.Ы. За ссылку на книгу - спасибо, посмотрю.
      З.Ы.Ы. Список литературы по C# давно готов - Классические книги по C#/.NET (http://sergeyteplyakov.blogspot.com/2011/04/cnet.html).
      З.Ы.Ы.Ы. По поводу перевода: тут дело ваше, мне сложно советовать стоит переводить предложенную книгу или нет. Есть желание - отлично, нет, тоже не смертельно, за всем не успеешь.

      Удалить
    4. Сергей, я принимаю твой совет посмотреть многопоточность, асинхронное программирование, заняться юнит-тестированием. За дополнительную подсказку, что почитать для этого, буду благодарен.
      Спасибо за указанную ссылку на список литературы по С#. По Java рекомендуют обязательно переварить "Java Полное руководство" Герберта Шилдта, 8-е издание. Он же автор книг бестселеров по С, C++ и C#. Шилдт в число авторов классических книг по С# не входит?
      Каково твое мнение относительно книги Медведев В.И. - Особенности объектно-ориентированного программирования на C++/CLI, C# и Java. 2-е издание (http://www.proklondike.com/books/codingproch/valter_Windows_8_HTML5_JavaScript_2013.html).

      Удалить
    5. Владимир, я не думаю, что книга Шилда входит в список классических (т.е. must have for everyone) книг для Java программистов;) Я думаю, там место книгам Хорстманна и Экеля.
      Кстати, у меня есть очень много ревью и других постов о книгах. Достаточно ткнуть в "Книги" - http://sergeyteplyakov.blogspot.com/2013/08/blog-post.html, или по соответствующему тегу. Ведь у меня есть и ревью книг по юнит-тестированию (кстати, мы комментируем пост с рецензией книги по юнит-тестированию).

      З.Ы. У меня нет мнения о книге Медведева. Я обычно иду другим путем: я стараюсь найти классную книгу на заданную тему, а не нахожу первую попавшуюся книгу и стараюсь выяснить, насколько она хороша.

      Удалить
    6. О Хорстмане и Экеле у мене вопросов не было. Поэтому их и не упоминал. Спасибо за подсказку, куда ткнуть.

      Удалить
  4. Сергей, если ты не против, додаю рабочую ссылку на скачивание вышеупомянутой мною книги.
    http://www.proklondike.com/books/oop/buday_design_patterni.html

    ОтветитьУдалить
    Ответы
    1. Перевод этой книжки будет. Я уже успел перепустить ее через Fine Reader. Код с изображениями оставляю без изменений. Текст пропущу через переводчик. Документ будет в формате Microsoft Office Word. После этого загоняю его вместе с кодами в Zip файл и уведомлю тебя об этом.

      Удалить
    2. Это хорошо. А автор этой книги знает об этих намерениях?

      Удалить
    3. Автор этой книги об этих намерениях не знает. Но он, я думаю, не против своей популяризации, если при этом не преследуются коммерческие цели. Что посоветуешь сделать? Я писал ему на указанный в его книге э-майл с просбой прислать мне, по возможности, коды на языке Java. Ответа, вообще, не получил.

      Удалить
    4. Я бы советовал таки попытаться получить согласие на перевод. Насколько я помню, Андрей читает меня в твиттере, возможно я смогу с ним связаться по этому поводу.
      Попробуй связаться с ним через твиттер сам: @andriybuday, если не ответит, то я попробую помочь.

      Удалить
    5. Я, к сожалению, пока пользуюсь только скайпом. Думаю, тебе будет легче связаться с Андреем по поводу. перевода его книжки. А, может, у него уже есть готовый перевод?

      Удалить
    6. Владимир, я думаю, что самое время познакомиться с чем-то еще помимо скайпа:) Я предложил решение и готов помочь, но лишь тогда, когда текущий вариант не даст результата:) И да, я не знаю, есть ли у него перевод или нет;)

      Удалить
    7. Не могу возразить против. Я согласен с тем, что самое время познакомиться с чем-то еще помимо скайпа. Начну с твиттера. Далее - за надобностью.

      Удалить
  5. Ого! Ток прочитал. Прямо говоря, охренительная статья. Проделанная работа потрясает. Спасибо.

    ОтветитьУдалить
    Ответы
    1. Вадим, рад, что статья понравилась!

      Удалить
  6. Закончил чтение книги "Growing Object-Oriented Software Guided by Tests" пару месяцев назад... Что-то по ощущениям многое осталось за кадром. Не сказал бы, что книга существенно повлияла на меня. Наверное, из-за того, что книга на английском. Местами сложновато ее было читать. И скорее всего многое упустил или не понял вовсе. Как-то нет ощущения ясности с code smells, test smells, хороший дизайн и т.п.
    Думаю к ней еще раз вернуться и попробывать повторить за ними разработку приложения Auction Sniper пошагово...

    ОтветитьУдалить