Может ошибаюсь, плохо знаю jQuery. Но там вроде оно возвращает само себя.
А как вы будете потом менять именно цепочки через find | xargs sed? И как вы будете, что нашли именно все цепочки, если вы не контроллируете свой код.
Это не обязательно могут быть просто геттеры.
Хороший пример с именем главы департамента:
account.getDepartment().getHead().geAssistant().getName()
Я бы скорее всего также здесь и поступил. Но давайте подумаем, а можно ли иначе и нужно ли?
В данном случае просто нельзя будет менять:
Deparment::getHead()
Head::getAssistant()
Assistant::getName()
Согласны?
Но здесь это не страшно, хотя можно и так сделать:
account.getDepartment().getHeadAssistantName()
Дело в том, что эта цепочка выражает какое-то явное понятие, в данном случае, имя ассистента главы департамента и зачем-то оно где-то нужно.
Если бы это использовалось в коде один раз, скорее всего я бы так и оставил. Если бы это нужно было бы в нескольких местах?
То скажите честно, вы бы копировали эту цепочку?
Но я хотел сказать не о простых геттерах, а о каких-то более сложных примерах.
Например, есть заказ, у него есть список товаров, и у них есть цены, Нужно посчитать сумму заказа.
Можно где-то в контроллере или еще где, сделать так:
order.products.sum(p => p.price)
В данном случае неявно выражено понятие суммы заказа. А что если нужно добавить скидку для пользователя? Вы скажете не проблема:
order.products.sum(p => p.price) * 1.1
Окей, а что если для разных категорий, разные скидки, вы поняли? Да?
Хорошо, если получение суммы заказа инкапсулировано где-то в репозитории, тогда такая цепочка не страшна, так как она только в одном месте. Но как вам такой вариант?
order.totalSum
Олег, так в том и то дело, что не буду я писать тест, чтобы получить вашу реализацию. Я постараюсь избежать такой реализации. Но если не получиться, то через моки. Давайте на PHP?
Здесь тоже вы правы, но опять — это Fluent Interface, здесь QueryBuilder возвращает сам себя и здесь все очевидно и ничего плохого в этом нет. Плохо, когда там будет иерарахия объектов. Например,
this.getSecurity().getContext().getToken().getUser()
Там не совсем о тех цепочках идет речь о которым мы говорим.
В jQuery объект возвращает сам себя и там все ОК, так как мы модифицируем один и тот же объект. Это называется Fluent Interface и это нормальный подход, тут я ничего не имею против.
Да, понятно, что отрефакторить можно, что угодно. Тогда вообще можно писать как угодно и сказать, что я потом просто заюзаю find | xargs sed.
Вот про фасады я и говорил, но без оглядки на то какой проект. В любом проекте, нормальным подходом будет прятать все эти цепочки.
Когда лень думать и хочется прямо здесь и сейчас получить то, что нужно. Не думая о последствиях. Согласен, удобны.
Возможно ошибся, но кажется, что у вас какая-то не приязнь, может из-за PHP в профиле. Могу сменить на Scala.
Не маскируйте свое незнание съездом с простейшего вопроса
Это считаю переходом. Так как вы не знаете, что я знаю, а что нет. Я привел тест для вашей задачи, вы его не осилили. Я объяснил почему ваш код плох — вы это не осилили. Я пытаюсь сейчас с нуля написать для реальной задачи, но что сделаете вы? Нет, вы не будете пытаться разбираться в вопросе и думать. Вы просто...
Скажете, что вы ах..енный практик, приведете кусок г..внокода и скажете, что х..й я его протестирую. И, бл..ть, я его реально не протестирую и окажусь гребанным теоретиком. Но я не протестирую не потому, что не могу. Я могу, я уже 14 месяцев активно занимаюсь TDD. Я его не протестирую, потому что не хочу.
Вашу проблему я решал и прошел еще в самом начале, это типичный вопрос новичка в TDD, а как тестировать цепочки вызовов? Хотел вам мягко объяснить, что никак и нужно иначе.
Короче, давайте дальше общаться аргументированно и без всяких там великих слов о джуна, практиках и теоретиках.
Думаю, что делегировать получение D в A. То есть в вызывающем коде скрыть всю сложность и разгрузить мозг читающего.
Могу ошибаться.
Эх, вы! Ленивая задница! =))))
Не смотря на ваши провокации, я покрыл заведомо пахнущий код. Если бы я писал его изначально, то не допустил бы такого, что код покрыть нельзя dou.ua/...=comment#612272
Не привирайте и не переходите на личности.
Давайте еще так, скажите где вам это понадобилось, не хватает контекста.
Хорошо, чуток меня не поняли. Скорее всего во мне проблема, не смог объяснить. Под реальным примером я подразумевал реальную практическую задачу на которую мы сначала напишем тест, а потом код.
Вы же даете код, который просите протестировать. Я используя тесты не приду к такому коду, потому как вы правы здесь:
сколько вам надо тестов? Надо подымать тестовый контекст, потому что бин something инжектится и внутри него могут быть сложные засечивания? юзать моки, замокивая все вызовы «паровозика»? проверять что getTitle реально вернет тайтл?
Используя тесты в начале. Я поставлю перед собой вопрос: «Почему я имея только foo, должен лезть в какие дебри и получить из него title baz-а?» Тут я почувствую запашок.
И попробую, для начала, написать тест, в котором something.getFirstFooBarTitle() должен будет возвращать то, что вы описали в примере. И реализую этот методом, например, вашим путем.
Почему хочу не надуманный пример? Потому, что в реальной жизни всегда можно избежать цепочки вызовов.
Отличный комментарий, спасибо!
Хотел бы добавить, что для спецификаций есть xSpec фреймворки.
Аналогично, считываю, что покрывать все не нужно, а только критически важные компоненты системы и собственно бизнес-правила. Но так сложилось, что раз пишу тест сначала, а потом код, то автоматом покрытие становится > 80%.
Больше всего интересно, где TDD может навредить.
Понял вас. Смотрите, как я это понимаю и как делаю.
1. Про цепочку. Когда есть такой вызов методов A->B->C->D, то в этой цепочке мы не можем изменить уже B и C, так как от этого пострадает цепочка где-то в нашем коде. Более того это нам намекает, что если мы так глубоко лезем за D, а у нас есть только A, то зачем нам вообще нужны эти B и C. Нам нужно либо сразу получать D, либо скрыть за A получение D.
2. SCA — для меня классная вещь, но есть семантика кода и то как он будет выполняться. Тесты позволяют мне описывать бизнес-правила в явном виде и проверять, что код работает корректно в соответствии с моими ожиданиями, а SCA для меня вещь, которая в принципе говорит, что код работает и у него нет проблем мест. Ведь, SCA не знает же, что «на сумму заказа должна начисляться скидка» или что «при получении элемента из стека, оный должен оттуда удаляться». SCA классный инструмент, который может дополнить тесты и сказать, что с технической точки зрения все реализовано ОК.
3. Да, я пишу большинство тестов на штатную отработку. Я скорее их пишу не с целью убедиться, что мой код работает корректно. А с целью решения конкретной задачи, в большинстве случаев таких тестов хватает, если обнаруживаются баги, то я дописываю тесты для отлавливания багов. То есть тесты помогают мне проектировать и концентрироваться на целях, которые решает код.
Не считаю, что обязательно делаю правильно. Но спустя 14 месяцев, как начал применять TDD/BDD-подходы, я начал получать радость от разработки, я понял, что такое SOLID, я начал использовать паттерны проектирования, мне даже иногда кажется, что я пишу не плохой код. Но самое главное, что у меня получается быстро и на приемлемом уровне решать поставленные передо мной задачи.
В промежуточных звеньях.
Вы подрываете инкапсуляцию, когда A -> B -> C -> D.
Это значит вы не можете менять теперь B и C, так как сломаете кучу таких цепочек в своем коде.
Ясное дело, что такие есть и которым все это не нужно.
Вопрос в том, как научиться писать такие интерфейсы на постоянной основе, а в случаях, когда не получается, то с легкостью их рефакторить.
Или вы хотите сказать, что даже те у кого хватает мозговых клеток для написания юзабельных интерфейсов не допускают ошибок?
Все очень просто. Это уже реализации. Давайте начнем с задачи, которая стояла перед разработчиком. Я верно понимаю, что это получение идентификаторов транзакций по определенным критериям?
Если так, то все просто.
Условно определяем для себя, что у нас будет метод TransactionRepository.findGroceryTransactionIds().
Условно на псевдо-языке пишем (не знаю Java):
void setup(TransationSourceInterface source) {
this.beConstructedWith(source) // в конструктор передаем мок source
}
void itShouldFindGroceryTransactionIdsOrderedByTransactionValue(TransactionSourceInterface source) {
source.getStream().willReturn(new List { new Transaction(2, Type.Grocery, 30) } ); // создаем список транзакций и говорим моку, что он вернет
// утверждаем, что вернет наш метод
this.findGroceryTransactionIds().shouldReturn(2, 3, 4);
}
Я извиняюсь за псевдоязык, могу привести пример на php работающего кода.
Про конкретный пример я говорил, не про надуманные и уже реализованные строчки кода, которые сложно протестировать. А пример задачи, где она поставлена так, что придется писать говнокод, даже с тестами.
Вы же сами себя обманываете, вы не до конца скопировали мою цитату и показали в том свете, в котором вам выгодно.
Честно, мне пофиг. У меня ощущение, что я с вами не общаюсь, а пытаюсь оправдываться. Я отлично понимаю почему «типичный» аутсорсер не будет писать тесты и будет всячески им сопротивляться.