Здравствуйте, ., Вы писали:
.>Здравствуйте, IT, Вы писали:
.>...поискипано .>Тут логики никакой, никаких if, тупой последовательный код, юнит-тесты не нужны, но можно интеграционные.
Ты серьезно считаешь что такой код надо писать? А если тебе понадобится добавить инфу кто и когда деактивировал юзера, придется все сверху до низу менять? Это такая шутка?
.>Можешь начинать ржать, но во время ржания пожалуйста напиши мне аналогичный код в котором только "чистая функция, принимающая всё необходимое в качестве параметра и возвращающая результат как возвращаемое значение".
ASP.NET MVC
[Authorize("Admin")]
class UserConteroller
{
MyDataContext ctx = new MyDataContex();
[Action("Deactivate")]
[HttpPost]
async ActionResult DeactivatePost(int userId)
{
var user = ctx.Users.First(u => u.Id == userId);
user.Actve = false;
await ctx.SaveChagesAsync();
return Redirect("Index");
}
}
Не нужно ничего. От слова вообще.
Права разруливает фреймворк, транзакционность — EF. Сама логика тривиальна, покрывать юнит-тестами не за чем.
Здравствуйте, vmpire, Вы писали:
V>>>Вы DI с mocks и stubs не путаете? .>>А как mocks и stubs подсовывать без DI? Через глобальные переменные, хаки байткода и прочий шлак? V>Через явную передачу интерфейсов,
А как делается явная передача интерфейса, не через DI ли?
V>через конфигурационный файл,
Это как? Очередной xml?
V>через специальные фреймворки типа moles...
Как он работает? Как переопределять статики?
V>Если вам лично это не нравится — это не значит, что это шлак.
Потому что это делает код теста особенным. А хороший тест это просто обычный код, демонстрирующий как твой класс можно использовать, а не как можно надругаться над средой исполнения.
V>>>И то и другое прекрасно реализуется без DI фреймворков. Да и не всегда нужны mocks и stubs, если тестируемая логика достаточно изолирована. .>>А вы IoC/DI с IoC-containers не путаете? Фреймворки, конечно, необязательны. .>>Достаточно изолирована, следует понимать "юнит", который тестируют. Нет зависимостей — хорошо, повезло, появятся — заинжектим. V>Не "повезло", а "разумно спроектировано" Code talks
G>Не нужно ничего. От слова вообще.
Что значит не нужно? У меня в коде были бизнес-требования.
Куда ты дел аудит, посылку сообщения, проверку на то, что юзер уже деакивирован? Куда потерялся факт, что есть две базы — для данных и аудита?
Как сформируется правильное описание ошибки, что юзер с таким id не найден?
G>Права разруливает фреймворк, транзакционность — EF.
Конечно, если у тебя всё вписывается в дефолтные юзкейсы фреймворка (погоди, а какого ешё фреймворка? Вроде без них хотели). А если не примитивные права, а кастомные проверки, например, юзер с открытым заказами не может быть деактивирован.
G>Сама логика тривиальна, покрывать юнит-тестами не за чем.
А как ты считаешь, проверить, что пять строк выполнились успешно надо юнит-тестом покрывать? Или так сойдёт?
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
G>>Не нужно ничего. От слова вообще. .>Что значит не нужно? У меня в коде были бизнес-требования.
У тебя в коде была нечитаемая мешанина, очень неусточивая к изменениям.
.>Куда ты дел аудит, посылку сообщения, проверку на то, что юзер уже деакивирован? Куда потерялся факт, что есть две базы — для данных и аудита?
У ТС не было такого, это ты уже сам придумал.
.>Как сформируется правильное описание ошибки, что юзер с таким id не найден?
Это не нужно, ибо чтобы вызвать post с id нужно этот id получить. Если же такого id не существует, то "ололопыщьпыщьвсепропало" достаточно.
G>>Права разруливает фреймворк, транзакционность — EF. .>Конечно, если у тебя всё вписывается в дефолтные юзкейсы фреймворка (погоди, а какого ешё фреймворка? Вроде без них хотели). А если не примитивные права, а кастомные проверки, например, юзер с открытым заказами не может быть деактивирован.
Тогда будет примерно так:
В остальном не изменится.
G>>Сама логика тривиальна, покрывать юнит-тестами не за чем. .>А как ты считаешь, проверить, что пять строк выполнились успешно надо юнит-тестом покрывать? Или так сойдёт?
Конечно сойдет. У меня стоимость ошибки минимальна, изменения выкатить легко, да еще 100500 раз поменяется прежде чем реально потребуется.
Здравствуйте, gandjustas, Вы писали:
G>>>Не нужно ничего. От слова вообще. .>>Что значит не нужно? У меня в коде были бизнес-требования. G>У тебя в коде была нечитаемая мешанина,
Эээ. Ты правда считаешь "ctx.Users.Where(u => !u.Orders.Any(o => o.CloseDate == null)).First(u => u.Id == userId)" более читаемая? Что тут происходит? Какое бизнес-требование?
Я тоже умею писать код в одну строчку, но вроде мы не о write-only коде.
Давай напиши окончательный вариант аналогичный моему и сравним читабельность и устойчивость к изменениям. Классы, которые у меня не реализованы (SomeSecurity, SessionService, CommunicationService, AuditSerivce, Database), будем считать писались другой командой, у тебя только есть интерфейсы которые ты волен задать сам. Что должно быть:
* конкретные ошибки (не ололо 500, всё пропало) NotFoundException, SuicideNotAllowedException,
* вызов проверки безопасности someSecurity.checkSomething
* аудит auditSerivce.reportAction
* посылка сообщения sendMessage
* Плюс каким образом будут задаваться всякие опции (коннекты к базам, stmp порт, етс).
И никакой магии, когда какой-то очередной волшебный фреймворк тебе сам всё сделает.
G>очень неусточивая к изменениям.
К каким например? Тесты есть, менять нестрашно.
.>>Куда ты дел аудит, посылку сообщения, проверку на то, что юзер уже деакивирован? Куда потерялся факт, что есть две базы — для данных и аудита? G>У ТС не было такого, это ты уже сам придумал.
Верно, для того, чтобы поменять одно поле в БД, слои не нужны, можно прямо в коде представления зафигачить апдейт базы. Как я понял вопрос топика — накой вообще все эти слои. Я дополнил требования, чтобы каждый слой имел смысл, выполняя свою роль.
.>>Как сформируется правильное описание ошибки, что юзер с таким id не найден? G>Это не нужно, ибо чтобы вызвать post с id нужно этот id получить. Если же такого id не существует, то "ололопыщьпыщьвсепропало" достаточно.
Ты никогда не писал public API? Когда тебя забрасывают вопросами "а что это такое http 500?".
G>>>Права разруливает фреймворк, транзакционность — EF. .>>Конечно, если у тебя всё вписывается в дефолтные юзкейсы фреймворка (погоди, а какого ешё фреймворка? Вроде без них хотели). А если не примитивные права, а кастомные проверки, например, юзер с открытым заказами не может быть деактивирован. G>Тогда будет примерно так: G>
G>В остальном не изменится.
И это тоже будет в asp.net-обработчике?
G>Конечно сойдет. У меня стоимость ошибки минимальна, изменения выкатить легко, да еще 100500 раз поменяется прежде чем реально потребуется.
Ну счастливый. Бывают ситуации когда час простоя даёт чистый убыток несколько сотен тыс $. Плюс труднооценимая потеря репутации.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ., Вы писали:
.>Здравствуйте, gandjustas, Вы писали:
G>>>>Не нужно ничего. От слова вообще. .>>>Что значит не нужно? У меня в коде были бизнес-требования. G>>У тебя в коде была нечитаемая мешанина, .>Эээ. Ты правда считаешь "ctx.Users.Where(u => !u.Orders.Any(o => o.CloseDate == null)).First(u => u.Id == userId)" более читаемая? Что тут происходит? Какое бизнес-требование? .>Я тоже умею писать код в одну строчку, но вроде мы не о write-only коде.
Ну разбей на несколько строк, суть не изменится.
.>Давай напиши окончательный вариант аналогичный моему и сравним читабельность и устойчивость к изменениям. Классы, которые у меня не реализованы (SomeSecurity, SessionService, CommunicationService, AuditSerivce, Database), будем считать писались другой командой, у тебя только есть интерфейсы которые ты волен задать сам. Что должно быть:
.>* конкретные ошибки (не ололо 500, всё пропало) NotFoundException, SuicideNotAllowedException,
Это неверный подход. Надо не позволять пользователю выполнить такие операции, скрывать\деактивировать кнопки на UI. А если же какойнить хакер сделает такой вызов, то "все пропало" достаточно.
.>* вызов проверки безопасности someSecurity.checkSomething
Так вообще писать нельзя. Проверки ролей безопасности должны быть в фильрах (атрибутах), а проверки данных в запросе.
.>* аудит auditSerivce.reportAction .>* посылка сообщения sendMessage
Зачем? это ты уже сам придумал.
.>* Плюс каким образом будут задаваться всякие опции (коннекты к базам, stmp порт, етс).
web.config
.>И никакой магии, когда какой-то очередной волшебный фреймворк тебе сам всё сделает.
В смысле? Не пользоваться тем, что есть и фигачить свое? Это же прямой путь к говнокоду.
G>>очень неусточивая к изменениям. .>К каким например? Тесты есть, менять нестрашно.
Добавь дополнительный параметр в метод деактивации. окажется что надо сделать изменение на каждом слое, что в свою очередь вызовет правку сервисов каждого слоя.
Нуегонафиг.
.>>>Куда ты дел аудит, посылку сообщения, проверку на то, что юзер уже деакивирован? Куда потерялся факт, что есть две базы — для данных и аудита? G>>У ТС не было такого, это ты уже сам придумал. .>Верно, для того, чтобы поменять одно поле в БД, слои не нужны, можно прямо в коде представления зафигачить апдейт базы. Как я понял вопрос топика — накой вообще все эти слои. Я дополнил требования, чтобы каждый слой имел смысл, выполняя свою роль.
Нет, ты придумал сложности там где их не было. Собственно так и происходит чаще всего в приложении. Приудмываются сложности, потом героически решаются. А надо было всего-то user.Active = false сделать.
.>>>Как сформируется правильное описание ошибки, что юзер с таким id не найден? G>>Это не нужно, ибо чтобы вызвать post с id нужно этот id получить. Если же такого id не существует, то "ололопыщьпыщьвсепропало" достаточно. .>Ты никогда не писал public API? Когда тебя забрасывают вопросами "а что это такое http 500?".
Для web api будет так:
G>>>>Права разруливает фреймворк, транзакционность — EF. .>>>Конечно, если у тебя всё вписывается в дефолтные юзкейсы фреймворка (погоди, а какого ешё фреймворка? Вроде без них хотели). А если не примитивные права, а кастомные проверки, например, юзер с открытым заказами не может быть деактивирован. G>>Тогда будет примерно так: G>>
Получается еще и DSL, что прекрасно.
G>>Конечно сойдет. У меня стоимость ошибки минимальна, изменения выкатить легко, да еще 100500 раз поменяется прежде чем реально потребуется. .>Ну счастливый. Бывают ситуации когда час простоя даёт чистый убыток несколько сотен тыс $. Плюс труднооценимая потеря репутации.
Это не тот случай изначально.
.>>Эээ. Ты правда считаешь "ctx.Users.Where(u => !u.Orders.Any(o => o.CloseDate == null)).First(u => u.Id == userId)" более читаемая? Что тут происходит? Какое бизнес-требование? .>>Я тоже умею писать код в одну строчку, но вроде мы не о write-only коде. G>Ну разбей на несколько строк, суть не изменится.
Каким образом разбить? Как распределить по коду? Как назвать куски? Это и есть вопрос слоёв и архитектуры.
Можно написать
int f(int a, b, c)
{
return a + b - c;
}
а можно написать
int calculateAmountToCharge(int orderPrice, deliveryPrice, discount)
{
int totalPrice = orderPrice + deliveryPrice;
return totalPrice - discount;
}
.>>Давай напиши окончательный вариант аналогичный моему и сравним читабельность и устойчивость к изменениям. Классы, которые у меня не реализованы (SomeSecurity, SessionService, CommunicationService, AuditSerivce, Database), будем считать писались другой командой, у тебя только есть интерфейсы которые ты волен задать сам. Что должно быть:
.>>* конкретные ошибки (не ололо 500, всё пропало) NotFoundException, SuicideNotAllowedException, G>Это неверный подход. Надо не позволять пользователю выполнить такие операции, скрывать\деактивировать кнопки на UI. А если же какойнить хакер сделает такой вызов, то "все пропало" достаточно.
Я говорю о UI разработчиках, пользователях API.
.>>* вызов проверки безопасности someSecurity.checkSomething G>Так вообще писать нельзя. Проверки ролей безопасности должны быть в фильрах (атрибутах), а проверки данных в запросе.
А фильтры|атрибуты как работают? Магически опять? Или нужно кучу кода?
Да и вообще, фильтры|атрибуты или просто вызов метода — не принципиально, детали реализации. Суть архитектуры остаётся той же.
.>>* аудит auditSerivce.reportAction .>>* посылка сообщения sendMessage G>Зачем? это ты уже сам придумал.
Да, придумал. Допустим надо. Сложно это тебе добавить в код? Ведь он так гибок к изменениям. Главное на тестах съэкономить.
.>>* Плюс каким образом будут задаваться всякие опции (коннекты к базам, stmp порт, етс). G>web.config
Допустим. А как оно в код попадёт? Заинжекится фреймворком?
.>>И никакой магии, когда какой-то очередной волшебный фреймворк тебе сам всё сделает. G>В смысле? Не пользоваться тем, что есть и фигачить свое? Это же прямой путь к говнокоду.
Я не предлагаю не пользоваться фреймворками, а как планировать архитектуру. Понятно с .net, там потратили несколько человеколет, выдали фреймворк, где достаточно написать одну функцию, и всё работает. Суслика видишь?
G>>>очень неусточивая к изменениям. .>>К каким например? Тесты есть, менять нестрашно. G>Добавь дополнительный параметр в метод деактивации. окажется что надо сделать изменение на каждом слое, что в свою очередь вызовет правку сервисов каждого слоя. G>Нуегонафиг.
Опять... "дополнительный параметр", а бизнес-требования то какие?
Да и в конце-концов... невелика беда. Добавление нового параметра в 10 функций займёт одну минуту.
.>>>>Куда ты дел аудит, посылку сообщения, проверку на то, что юзер уже деакивирован? Куда потерялся факт, что есть две базы — для данных и аудита? G>>>У ТС не было такого, это ты уже сам придумал. .>>Верно, для того, чтобы поменять одно поле в БД, слои не нужны, можно прямо в коде представления зафигачить апдейт базы. Как я понял вопрос топика — накой вообще все эти слои. Я дополнил требования, чтобы каждый слой имел смысл, выполняя свою роль. G>Нет, ты придумал сложности там где их не было. Собственно так и происходит чаще всего в приложении. Приудмываются сложности, потом героически решаются. А надо было всего-то user.Active = false сделать.
Ок.
void deactivateUser(long userId)
{
jdbcOperations.update("update user set active=false where id=?", userId);
}
одна строчка. asp.net фтопку.
.>>>>Как сформируется правильное описание ошибки, что юзер с таким id не найден? G>>>Это не нужно, ибо чтобы вызвать post с id нужно этот id получить. Если же такого id не существует, то "ололопыщьпыщьвсепропало" достаточно. .>>Ты никогда не писал public API? Когда тебя забрасывают вопросами "а что это такое http 500?". G>Для web api будет так: G>
Уже лучше, осталось остальное реализовать и понять, что кода у тебя будет не меньше для той же функциональности.
.>>И это тоже будет в asp.net-обработчике? G>Если используется в нескольоких местах, то делается extension-метод: G>
G>Получается еще и DSL, что прекрасно.
Согласен. Можно и так. Осталось это объединить в одном namespace и внезапно получится UserDao.
.>>Ну счастливый. Бывают ситуации когда час простоя даёт чистый убыток несколько сотен тыс $. Плюс труднооценимая потеря репутации. G>Это не тот случай изначально.
Раз в этом формуе, то хочется верить в лучшее, что человек хочет что-то новое изучить, чтобы мог пойти работать в такую компанию. А так пусть пишет на PHP, и никаких забот.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ., Вы писали:
.>>>Эээ. Ты правда считаешь "ctx.Users.Where(u => !u.Orders.Any(o => o.CloseDate == null)).First(u => u.Id == userId)" более читаемая? Что тут происходит? Какое бизнес-требование? .>>>Я тоже умею писать код в одну строчку, но вроде мы не о write-only коде. G>>Ну разбей на несколько строк, суть не изменится. .>Каким образом разбить? Как распределить по коду? Как назвать куски? Это и есть вопрос слоёв и архитектуры.
Детерминированные функции и extension-методы. Если у тебя набор функций, то нет особой надобности делить это на много слоев с кучей уровней косвенности. Решение о создании классов лучше принимать на основе необходимости, а не на основе "паттернов".
.>Можно написать .>
.>int f(int a, b, c)
.>{
.> return a + b - c;
.>}
.>
Ну не пиши так. Кто-то заставляет?
.>>>Давай напиши окончательный вариант аналогичный моему и сравним читабельность и устойчивость к изменениям. Классы, которые у меня не реализованы (SomeSecurity, SessionService, CommunicationService, AuditSerivce, Database), будем считать писались другой командой, у тебя только есть интерфейсы которые ты волен задать сам. Что должно быть:
.>>>* конкретные ошибки (не ололо 500, всё пропало) NotFoundException, SuicideNotAllowedException, G>>Это неверный подход. Надо не позволять пользователю выполнить такие операции, скрывать\деактивировать кнопки на UI. А если же какойнить хакер сделает такой вызов, то "все пропало" достаточно. .>Я говорю о UI разработчиках, пользователях API.
А UI не пользователь API?
.>>>* вызов проверки безопасности someSecurity.checkSomething G>>Так вообще писать нельзя. Проверки ролей безопасности должны быть в фильрах (атрибутах), а проверки данных в запросе. .>А фильтры|атрибуты как работают? Магически опять? Или нужно кучу кода? .>Да и вообще, фильтры|атрибуты или просто вызов метода — не принципиально, детали реализации. Суть архитектуры остаётся той же.
Суть да, форма — нет. Если у тебя A вызывает Б, Б вызывает В, а В делает запрос к базе и больше ничего, то нет смыса иметь такую косвеннсть. Запрос можно сделать прямо в А или, на крайняк в Б, если это приведет у уменьшению кода.
Твой подход оправдывает только то, что у тебя много чего делается еще на каждом уровне. Но на практике эти cross-cutting concerns надо выносить отдельно, иначе у тебя появится много копипасты.
Представь, что у тебя в someSecurity.checkSomething добавится еще один параметр, тебе придется поменять все методы, где он вызывается и все юнит-тесты для этих методов. Если же у тебя этот вызов спрятан внутри атрибута-фильра, то надо будет в одном месте поменять.
.>>>* аудит auditSerivce.reportAction .>>>* посылка сообщения sendMessage G>>Зачем? это ты уже сам придумал. .>Да, придумал. Допустим надо. Сложно это тебе добавить в код? Ведь он так гибок к изменениям. Главное на тестах съэкономить.
Вот так я бы делал http://gandjustas.blogspot.com/2010/02/entity-framework.html
Причем оно никак бы не отразилось на моем коде, который я писал выше.
.>>>* Плюс каким образом будут задаваться всякие опции (коннекты к базам, stmp порт, етс). G>>web.config .>Допустим. А как оно в код попадёт? Заинжекится фреймворком?
Да, данные из конфига попадают в соотвествующие классы без участия программиста.
.>>>И никакой магии, когда какой-то очередной волшебный фреймворк тебе сам всё сделает. G>>В смысле? Не пользоваться тем, что есть и фигачить свое? Это же прямой путь к говнокоду. .>Я не предлагаю не пользоваться фреймворками, а как планировать архитектуру. Понятно с .net, там потратили несколько человеколет, выдали фреймворк, где достаточно написать одну функцию, и всё работает. Суслика видишь?
Не вижу, нет его. Зачем писать больше, если можно писать меньше и делать код надежнее?
В этом и есть основной поинт ТС. Куча готовых средств делают ненужным 99% архитектурных паттернов, а программисты продолжают лепить репозитории, слои, спецификации и кучу хрени, а оправдывают это тестируемостью.
Если бы не было ASP.NET MVC и EF, то программы очевидно выглядели бы по-другому. Но эти средства есть и крайне глупо их не использовать.
G>>>>очень неусточивая к изменениям. .>>>К каким например? Тесты есть, менять нестрашно. G>>Добавь дополнительный параметр в метод деактивации. окажется что надо сделать изменение на каждом слое, что в свою очередь вызовет правку сервисов каждого слоя. G>>Нуегонафиг. .>Опять... "дополнительный параметр", а бизнес-требования то какие?
А какая разница? Ты можешь гарантировать, что параметр не добавится? Нет, никто не может.
Добавление полей — самая частая доработка,
.>Да и в конце-концов... невелика беда. Добавление нового параметра в 10 функций займёт одну минуту.
Агащаз. Я недавно добавлял одно поле в базу в приложении с такой архитектурой как ты пишешь. Поле использовалось в одном методе, а вот чтобы вывести его в таблицы и формы UI потребовалось поправить 2 метода контроллера, 2 ViewModel, 4 метода сервисов, три хранимки. Также это поломало 4 теста, причем логика, для которой нужно было это поле, даже не тестировалась. Заняло почти день работы.
Ты же не забывай, что у тебя не один метод, как ты написал, а несколько десятков. И в каждом возможны изменения. По факту такое расслоение, которое ты предлагаешь, по факту увеличивает площадь изменений, а в теории должно уменьшать, ведь именно для этого и делается расслоение.
.>>>>>Куда ты дел аудит, посылку сообщения, проверку на то, что юзер уже деакивирован? Куда потерялся факт, что есть две базы — для данных и аудита? G>>>>У ТС не было такого, это ты уже сам придумал. .>>>Верно, для того, чтобы поменять одно поле в БД, слои не нужны, можно прямо в коде представления зафигачить апдейт базы. Как я понял вопрос топика — накой вообще все эти слои. Я дополнил требования, чтобы каждый слой имел смысл, выполняя свою роль. G>>Нет, ты придумал сложности там где их не было. Собственно так и происходит чаще всего в приложении. Приудмываются сложности, потом героически решаются. А надо было всего-то user.Active = false сделать. .>Ок. .>
.> void deactivateUser(long userId)
.> {
.> jdbcOperations.update("update user set active=false where id=?", userId);
.> }
.>
.>одна строчка. asp.net фтопку.
В ASP.NET можно сделать тоже самое. Но тогда ты не застрахован от переименований и банальных ошибок в тексте запроса.
.>>>>>Как сформируется правильное описание ошибки, что юзер с таким id не найден? G>>>>Это не нужно, ибо чтобы вызвать post с id нужно этот id получить. Если же такого id не существует, то "ололопыщьпыщьвсепропало" достаточно. .>>>Ты никогда не писал public API? Когда тебя забрасывают вопросами "а что это такое http 500?". G>>Для web api будет так: G>>
.>Уже лучше, осталось остальное реализовать и понять, что кода у тебя будет не меньше для той же функциональности.
А почему у меня не будет такой функцинальности? Кто-то отменяет ASP.NET MVC? Как раз наоборот, развивают.
В том и дело что уже есть такая функциональность. Не надо велосипеды изобретать.
.>>>И это тоже будет в asp.net-обработчике? G>>Если используется в нескольоких местах, то делается extension-метод: G>>
G>>Получается еще и DSL, что прекрасно. .>Согласен. Можно и так. Осталось это объединить в одном namespace и внезапно получится UserDao.
А почему Dao? Это вполне себе бизнес-логика, то есть логика, нужная бизнесу. Да и как не называй, это не слои приложения, ибо нету косвенности.
.>>>Ну счастливый. Бывают ситуации когда час простоя даёт чистый убыток несколько сотен тыс $. Плюс труднооценимая потеря репутации. G>>Это не тот случай изначально. .>Раз в этом формуе, то хочется верить в лучшее, что человек хочет что-то новое изучить, чтобы мог пойти работать в такую компанию. А так пусть пишет на PHP, и никаких забот.
А чем в принципе PHP отличается от Java или ASP.NET? Языки разные, а приложения в общем те же — прослойка между базой и UI.
Здравствуйте, AndrewJD, Вы писали:
IT>>Сложные или тормознутые? AJD>И сложные и/или тормознутые.
Сложные и тормознутые в контексте тестирования попадают в совершенно разные категории. Сложность компонента/метода на использование его в тестах не отражается никоим образом. А вот тормознутость может привести к принципипльно иным решениям в тестировании приложения. Даже тот же DI в этом случае может оказаться полезным, хотя всегда можно обойтись без него.
AJD>Или например есть компонт который расчитывает сложную аналитику, которому нужны куча данных но он возвращает простой ответ: "Да" или "Нет". AJD>Вместо того чтобы пытаться восоздать сложное окружения для этого компонента в юнит тесте, не проще его замокать?
Если этот компонент часть приложения, в котором тестированию уделяется серьёзное внимание, то скорее всего этот компонент тоже обложен тестами вдоль и поперёк. Т.е сложное окружение для его тестирования уже имеется в наличии. В этом случае никто не запрещает использование техник повторного использования кода для воспроизведения сложного окружения. Это один вариант. Второй — использование в тестируемом методе уже готовых результатов этого компонента, а не его самого. Обычно в правильно спроектированном коде отчуждение таких мест от основного workflow не представляет особых проблем.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, ., Вы писали:
IT>>Следует читать "расширяемое приложение, которое понятия не имеет что в нём будет выполняться". .>Эээ. По тестам видно — что.
Т.е. у нас проблемы с пониманием что такое расширяемое приложение?
IT>>Следует читать как "зависит от других частей с недетерминированным поведением" .>"недетерминированным во времени" — запросто, проект-то меняется.
Это как раз самое простое. Сложнее когда тест зависит от внешних компонент, которые, например, могут быть временно недоступны.
IT>>Следует читать "охота повыпендриваться и казаться умным перед девчонками". Девчонок можно заменить по вкусу. .>А что такого выпедрючного в DI?
Ну хотя бы в том, что под элементарную передачу параметра в конструктор или присвоение свойству значения подвели целую псевдо теорию, которую обозвали DI. Есть мнение, что от DI нереально прутся прежде всего те, кому довелось осознать "магию" косвенных вызовов, но так и застрявших на этом уровне. Другими словами, чтобы понять что такое DI нужно стать эльфом 48-го уровня, а чтобы начать осознавать вред наносимый этой хренью нужно достигнуть 60-го уровня.
IT>>Альтернатива прямо противоположная — чистая функция, принимающая всё необходимое в качестве параметра и возвращающая результат как возвращаемое значение, не создавая и не используя при этом никаких артефактов вроде статиков и вызовов с высоким уровнем косвенности. .>Сколько будет аргументов у функции "деактивировать пользователя"?
Сколько?
.>Можешь начинать ржать, но во время ржания пожалуйста напиши мне аналогичный код в котором только "чистая функция, принимающая всё необходимое в качестве параметра и возвращающая результат как возвращаемое значение".
Можно в исполнении LinqToDB?
class UserManager
{
void DeactivateUser(long userId)
{
using (var db = new MyDB())
{
db.Users
.Where(t => t.UserID == userID)
.Set (t => t.IsActive, false)
.Update();
}
}
}
Тест:
class UserManagerTest
{
[Test]
void DeactivateUserTest(long userId)
{
using (var db = new MyDB())
{
db.Users
.Where(t => t.UserID == 1)
.Set (t => t.IsActive, true)
.Update();
new UserManagment().DeactivateUser(1);
Assert.That(db.Users.First(t => t.UserID == 1), Is.False);
}
}
}
Заметь, из выпендрёжного этот код превратился в примитивный.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, gandjustas, Вы писали:
.>>Каким образом разбить? Как распределить по коду? Как назвать куски? Это и есть вопрос слоёв и архитектуры. G>Детерминированные функции и extension-методы. Если у тебя набор функций, то нет особой надобности делить это на много слоев с кучей уровней косвенности. Решение о создании классов лучше принимать на основе необходимости, а не на основе "паттернов".
Я так и не увидел функции, которая делает всё, что делает мой слоистый код. Приведённый тобой код аналогичен одному методу моего кода.
У меня все классы stateless, что в общем-то превращает их методы в чистые функции, у которых несколько аргументов перенесены в конструктор.
G>Ну не пиши так. Кто-то заставляет?
Ты однако демонстируешь код в одну сторчку как образец. Зачем косвенность в виде локальной переменной? Можно ведь в одну строчку написать.
.>>>>* конкретные ошибки (не ололо 500, всё пропало) NotFoundException, SuicideNotAllowedException, G>>>Это неверный подход. Надо не позволять пользователю выполнить такие операции, скрывать\деактивировать кнопки на UI. А если же какойнить хакер сделает такой вызов, то "все пропало" достаточно. .>>Я говорю о UI разработчиках, пользователях API. G>А UI не пользователь API?
Слава КПСС вообще не человек. Нормальные ошибки нужны только человекам. Я говорю о UI разработчиках! Именно для них мы делаем вменяемый публичный API, которым они смогут нормально пользоваться, не пиная нас каждый раз, когда видят невменяемый ответ от сервера.
.>>А фильтры|атрибуты как работают? Магически опять? Или нужно кучу кода? .>>Да и вообще, фильтры|атрибуты или просто вызов метода — не принципиально, детали реализации. Суть архитектуры остаётся той же. G>Суть да, форма — нет. Если у тебя A вызывает Б, Б вызывает В, а В делает запрос к базе и больше ничего, то нет смыса иметь такую косвеннсть. Запрос можно сделать прямо в А или, на крайняк в Б, если это приведет у уменьшению кода.
Косвенность там не просто так, а для разделения обязанностей.
G>Твой подход оправдывает только то, что у тебя много чего делается еще на каждом уровне. Но на практике эти cross-cutting concerns надо выносить отдельно, иначе у тебя появится много копипасты.
Правильно. Я пытаюсь ответить на вопрос, зачем эти уровни нужны и что там обычно делается.
G>Представь, что у тебя в someSecurity.checkSomething добавится еще один параметр, тебе придется поменять все методы, где он вызывается и все юнит-тесты для этих методов.
Если параметр и правда нужен — то это означает, что эти все методы и вправду логически меняются, а значит их надо переписать и протестировать и т.п.
А если не нужен, то есть куча других способов — параметр можно сделать с дефолтным значением, создать новый метод или параметр протащить через DI.
G>Если же у тебя этот вызов спрятан внутри атрибута-фильра, то надо будет в одном месте поменять.
Ужас, а атрибуту-фильтру передают параметры телепатией, через ноосферу.
G>>>Зачем? это ты уже сам придумал. .>>Да, придумал. Допустим надо. Сложно это тебе добавить в код? Ведь он так гибок к изменениям. Главное на тестах съэкономить. G>Вот так я бы делал http://gandjustas.blogspot.com/2010/02/entity-framework.html G>Причем оно никак бы не отразилось на моем коде, который я писал выше.
Это не совсем аудит, а логгирование, или в лучшем случае сохранение истории. Записи аудита обычно диктуются бизнесом и связаны с действиями пользователя, а не полями в БД.
.>>>>* Плюс каким образом будут задаваться всякие опции (коннекты к базам, stmp порт, етс). G>>>web.config .>>Допустим. А как оно в код попадёт? Заинжекится фреймворком? G>Да, данные из конфига попадают в соотвествующие классы без участия программиста.
Не понял. Ведь мы без IoC контейнеров и DI забанили. А вдруг выясняется что зависимость инжектится IoC фреймоврком. Уличная магия какая-то.
.>>Я не предлагаю не пользоваться фреймворками, а как планировать архитектуру. Понятно с .net, там потратили несколько человеколет, выдали фреймворк, где достаточно написать одну функцию, и всё работает. Суслика видишь? G>Не вижу, нет его. Зачем писать больше, если можно писать меньше и делать код надежнее? G>В этом и есть основной поинт ТС. Куча готовых средств делают ненужным 99% архитектурных паттернов, а программисты продолжают лепить репозитории, слои, спецификации и кучу хрени, а оправдывают это тестируемостью. G>Если бы не было ASP.NET MVC и EF, то программы очевидно выглядели бы по-другому. Но эти средства есть и крайне глупо их не использовать.
Что значит ненужным? Просто MVC и EF и есть те самые архитектурные паттерны. Т.е. ты хочешь сказать, что программисты не разобравшись с тем, что уже есть начинают лепить своё? Тогда да, согласен — жуть. С таким я не спорю.
Хотя не понимаю, тесты то тут причём? Понятное дело, что тесты для MVC и EF писать не надо, ибо внешние компоненты, уже, надеюсь протестированные майкрософтом.
.>>Опять... "дополнительный параметр", а бизнес-требования то какие? G>А какая разница? Ты можешь гарантировать, что параметр не добавится? Нет, никто не может.
Большая. Т.к. решение может быть разное — от тупо добавления одного параметра к одному методу до развёртывания нового сервиса в кластере.
G>Добавление полей — самая частая доработка,
Да и в общем-то самая простая модификация кода. Не понимаю что ты хочешь этим доказать.
.>>Да и в конце-концов... невелика беда. Добавление нового параметра в 10 функций займёт одну минуту. G>Агащаз. Я недавно добавлял одно поле в базу в приложении с такой архитектурой как ты пишешь. Поле использовалось в одном методе, а вот чтобы вывести его в таблицы и формы UI потребовалось поправить 2 метода контроллера, 2 ViewModel, 4 метода сервисов, три хранимки. Также это поломало 4 теста, причем логика, для которой нужно было это поле, даже не тестировалась. Заняло почти день работы.
Не знаю, трудно судить о суслике которого я не видел. Вот я привёл код, давай его и рассматривать.
G>Ты же не забывай, что у тебя не один метод, как ты написал, а несколько десятков. И в каждом возможны изменения. По факту такое расслоение, которое ты предлагаешь, по факту увеличивает площадь изменений, а в теории должно уменьшать, ведь именно для этого и делается расслоение.
Это способ декомпозиции сложной функциональности на несколько частей, чтобы не было кучи всего в одном месте. SRP, OCP и всё такое.
.>>одна строчка. asp.net фтопку. G>В ASP.NET можно сделать тоже самое. Но тогда ты не застрахован от переименований и банальных ошибок в тексте запроса.
Застрахован. Тесты есть, которые можно ещё и выполнить в разных окружениях и быть уверенным, что везде работает. Не говоря уж о подсветке, автодополнении, авторефакторингах и прочем.
.>>Уже лучше, осталось остальное реализовать и понять, что кода у тебя будет не меньше для той же функциональности. G>А почему у меня не будет такой функцинальности? Кто-то отменяет ASP.NET MVC? Как раз наоборот, развивают. G>В том и дело что уже есть такая функциональность. Не надо велосипеды изобретать.
Я имею в виду ту функциональность, которая в моём коде.
И это хорошо, что тут веб-приложение, поэтому asp подходит, а если что другое, оффлайн клиент какой-нибудь, то уже упс.
.>>Согласен. Можно и так. Осталось это объединить в одном namespace и внезапно получится UserDao. G>А почему Dao? Это вполне себе бизнес-логика, то есть логика, нужная бизнесу. Да и как не называй, это не слои приложения, ибо нету косвенности.
Потому что бизнес не говорит таким языком, не "дай мне предикат поиска юзеров, у которых нет открытых заказов". Бизнес скажет что-то типа "деактивировать можно только таких юзеров, у которых нет открытых заказов. При ошибке отобразить текст XYZ".
.>>>>Ну счастливый. Бывают ситуации когда час простоя даёт чистый убыток несколько сотен тыс $. Плюс труднооценимая потеря репутации. G>>>Это не тот случай изначально. .>>Раз в этом формуе, то хочется верить в лучшее, что человек хочет что-то новое изучить, чтобы мог пойти работать в такую компанию. А так пусть пишет на PHP, и никаких забот. G>А чем в принципе PHP отличается от Java или ASP.NET? Языки разные, а приложения в общем те же — прослойка между базой и UI.
Что очень маловероятно, PHP будет использоваться в системе, простой которой может нанести серьёзный ущерб.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, IT, Вы писали:
IT>>>Следует читать "расширяемое приложение, которое понятия не имеет что в нём будет выполняться". .>>Эээ. По тестам видно — что. IT>Т.е. у нас проблемы с пониманием что такое расширяемое приложение?
Конечно, под этим понимать можно всё что угодно. Типичный баззворд.
IT>>>Следует читать как "зависит от других частей с недетерминированным поведением" .>>"недетерминированным во времени" — запросто, проект-то меняется. IT>Это как раз самое простое. Сложнее когда тест зависит от внешних компонент, которые, например, могут быть временно недоступны.
Это будет интеграционный тест. Что вообще параллельно юнит-тесту.
IT>>>Следует читать "охота повыпендриваться и казаться умным перед девчонками". Девчонок можно заменить по вкусу. .>>А что такого выпедрючного в DI? IT>Ну хотя бы в том, что под элементарную передачу параметра в конструктор или присвоение свойству значения подвели целую псевдо теорию, которую обозвали DI. Есть мнение, что от DI нереально прутся прежде всего те, кому довелось осознать "магию" косвенных вызовов, но так и застрявших на этом уровне. Другими словами, чтобы понять что такое DI нужно стать эльфом 48-го уровня, а чтобы начать осознавать вред наносимый этой хренью нужно достигнуть 60-го уровня.
Как я понял из вышенаписанного — достичь 60-го уровня можно только если есть какой-нибудь фреймворк типа ASP.NET, и заявлять что DI и фреймворки хрень, игнорируя факт, что этот самый фреймворк делает этот самый DI.
IT>>>Альтернатива прямо противоположная — чистая функция, принимающая всё необходимое в качестве параметра и возвращающая результат как возвращаемое значение, не создавая и не используя при этом никаких артефактов вроде статиков и вызовов с высоким уровнем косвенности. .>>Сколько будет аргументов у функции "деактивировать пользователя"? IT>Сколько?
В моей реализации получилось один — user. Интересно сколько будет в твоей.
.>>Можешь начинать ржать, но во время ржания пожалуйста напиши мне аналогичный код в котором только "чистая функция, принимающая всё необходимое в качестве параметра и возвращающая результат как возвращаемое значение".
IT>Можно в исполнении LinqToDB? IT>Заметь, из выпендрёжного этот код превратился в примитивный.
Аналог твоего кода ещё примитивнее:
void deactivateUser(long userId)
{
jdbcOperations.update("update user set active=false where id=?", userId);
}
Тест будет:
void testDeactivateUser()
{
jdbcOperations.update("update user set active=true where id=1");
new UserDao(jdbcOperations).deactivateUser(1);
assertThat(jdbcOperations.queryForObject("select active from user where id=1"), is(false));
}
Даже без всяких фреймворков. И что? Я прошу код аналогичный моему по функциональности, с посылкой сообщений, аудитом, различными проверками, обработкой ошибок и т.п.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ., Вы писали:
.>Здравствуйте, gandjustas, Вы писали:
.>>>Каким образом разбить? Как распределить по коду? Как назвать куски? Это и есть вопрос слоёв и архитектуры. G>>Детерминированные функции и extension-методы. Если у тебя набор функций, то нет особой надобности делить это на много слоев с кучей уровней косвенности. Решение о создании классов лучше принимать на основе необходимости, а не на основе "паттернов". .>Я так и не увидел функции, которая делает всё, что делает мой слоистый код. Приведённый тобой код аналогичен одному методу моего кода.
Они там и не нужны, ты еще не понял этого? Даже если понадобится функционал, то он будет не в теле методов написан.
.>У меня все классы stateless, что в общем-то превращает их методы в чистые функции, у которых несколько аргументов перенесены в конструктор.
Это неправда. Ты получаешь классы извне, какое там состояние ты не знаешь. Даже ссылка на другой класс — тоже состояние. Я вот один раз сломал несколько тестов в коде подобном твоем, просто потому что часть инициализации из метода перенес в конструктор, естественно поведение при этом не поменялось. Так что не надо желаемое выдавать за действительное.
Ну и основная проблема остается — много уровней косвенности.
.>>>>>* конкретные ошибки (не ололо 500, всё пропало) NotFoundException, SuicideNotAllowedException, G>>>>Это неверный подход. Надо не позволять пользователю выполнить такие операции, скрывать\деактивировать кнопки на UI. А если же какойнить хакер сделает такой вызов, то "все пропало" достаточно. .>>>Я говорю о UI разработчиках, пользователях API. G>>А UI не пользователь API? .>Слава КПСС вообще не человек. Нормальные ошибки нужны только человекам. Я говорю о UI разработчиках! Именно для них мы делаем вменяемый публичный API, которым они смогут нормально пользоваться, не пиная нас каждый раз, когда видят невменяемый ответ от сервера.
А ты сам не делаешь UI? Ты еще не понял, что лучше человеку не дать нажать кнопку, чем показать потом ошибку? Ты не на том внимание концентируешь.
.>>>А фильтры|атрибуты как работают? Магически опять? Или нужно кучу кода? .>>>Да и вообще, фильтры|атрибуты или просто вызов метода — не принципиально, детали реализации. Суть архитектуры остаётся той же. G>>Суть да, форма — нет. Если у тебя A вызывает Б, Б вызывает В, а В делает запрос к базе и больше ничего, то нет смыса иметь такую косвеннсть. Запрос можно сделать прямо в А или, на крайняк в Б, если это приведет у уменьшению кода. .>Косвенность там не просто так, а для разделения обязанностей.
А разделение обязанностей зачем? Его делают для уменьшения изменений и "наведенных" ошибок. Но лишние уровни косвенности увеличивают площадь изменений и количество наведенных ошибок. Так что шило на мыло.
По сути нет необходимости заниматься таким разделением обязанностей. создавать функции и классы надо для: убирания копипасты, увеличения читаемости, сохранения инвариантов. В твоем случае только читаемость подходит, но создание класса для этого — оверкилл.
G>>Представь, что у тебя в someSecurity.checkSomething добавится еще один параметр, тебе придется поменять все методы, где он вызывается и все юнит-тесты для этих методов. .>Если параметр и правда нужен — то это означает, что эти все методы и вправду логически меняются, а значит их надо переписать и протестировать и т.п.
Вовсе не означает. Иначе странная логика — любое изменение код, даже очень масштабное является неизбежной необходимостью, потому что "и вправду логически меняются".
Стремиться уменьшать площадь изменений нужно независимо от мнения.
.>А если не нужен, то есть куча других способов — параметр можно сделать с дефолтным значением, создать новый метод или параметр протащить через DI.
Ага, а в нормальных фреймворках это уже встроено, поэтому нет необходимости велосипедить.
G>>Если же у тебя этот вызов спрятан внутри атрибута-фильра, то надо будет в одном месте поменять. .>Ужас, а атрибуту-фильтру передают параметры телепатией, через ноосферу.
Нет, через контекст.
G>>>>Зачем? это ты уже сам придумал. .>>>Да, придумал. Допустим надо. Сложно это тебе добавить в код? Ведь он так гибок к изменениям. Главное на тестах съэкономить. G>>Вот так я бы делал http://gandjustas.blogspot.com/2010/02/entity-framework.html G>>Причем оно никак бы не отразилось на моем коде, который я писал выше. .>Это не совсем аудит, а логгирование, или в лучшем случае сохранение истории. Записи аудита обычно диктуются бизнесом и связаны с действиями пользователя, а не полями в БД.
Тогда еще проще — делается атрибутом на Action контроллера.
.>>>>>* Плюс каким образом будут задаваться всякие опции (коннекты к базам, stmp порт, етс). G>>>>web.config .>>>Допустим. А как оно в код попадёт? Заинжекится фреймворком? G>>Да, данные из конфига попадают в соотвествующие классы без участия программиста. .>Не понял. Ведь мы без IoC контейнеров и DI забанили. А вдруг выясняется что зависимость инжектится IoC фреймоврком. Уличная магия какая-то.
Там все проще — тебе просто передается контекст, их которого ты сам забираешь что нужно. Но можно и IoC прикрутить.
.>>>Я не предлагаю не пользоваться фреймворками, а как планировать архитектуру. Понятно с .net, там потратили несколько человеколет, выдали фреймворк, где достаточно написать одну функцию, и всё работает. Суслика видишь? G>>Не вижу, нет его. Зачем писать больше, если можно писать меньше и делать код надежнее? G>>В этом и есть основной поинт ТС. Куча готовых средств делают ненужным 99% архитектурных паттернов, а программисты продолжают лепить репозитории, слои, спецификации и кучу хрени, а оправдывают это тестируемостью. G>>Если бы не было ASP.NET MVC и EF, то программы очевидно выглядели бы по-другому. Но эти средства есть и крайне глупо их не использовать. .>Что значит ненужным? Просто MVC и EF и есть те самые архитектурные паттерны. Т.е. ты хочешь сказать, что программисты не разобравшись с тем, что уже есть начинают лепить своё? Тогда да, согласен — жуть. С таким я не спорю.
Начинают лепить обертки, например репозитарии для EF очень популярны. Они по факту ничего полезного не дают, но их все равно лепят практически все.
.>Хотя не понимаю, тесты то тут причём? Понятное дело, что тесты для MVC и EF писать не надо, ибо внешние компоненты, уже, надеюсь протестированные майкрософтом.
Если у тебя есть мощный фреймворк, то тебе становится сложно писать тесты на свой код. Даже если технически можно сделать моки на методы фреймворка, то повторить поведение уже становится крайне сложно.
.>>>Опять... "дополнительный параметр", а бизнес-требования то какие? G>>А какая разница? Ты можешь гарантировать, что параметр не добавится? Нет, никто не может. .>Большая. Т.к. решение может быть разное — от тупо добавления одного параметра к одному методу до развёртывания нового сервиса в кластере.
Ты уже начинешь демагогией заниматься. Этот прием называется "обобщение до стирания любых различий". Давай сосредоточимся на маленькой проблеме добавления параметра в вызов метода.
G>>Добавление полей — самая частая доработка, .>Да и в общем-то самая простая модификация кода. Не понимаю что ты хочешь этим доказать.
В теории самая простая, на практике отнимает неприличное количество времени. Сильно больше чем стоило бы. И причина этому — архитектура, как ты предлагаешь.
G>>Ты же не забывай, что у тебя не один метод, как ты написал, а несколько десятков. И в каждом возможны изменения. По факту такое расслоение, которое ты предлагаешь, по факту увеличивает площадь изменений, а в теории должно уменьшать, ведь именно для этого и делается расслоение. .>Это способ декомпозиции сложной функциональности на несколько частей, чтобы не было кучи всего в одном месте. SRP, OCP и всё такое.
И зачем оно нужно?
.>>>одна строчка. asp.net фтопку. G>>В ASP.NET можно сделать тоже самое. Но тогда ты не застрахован от переименований и банальных ошибок в тексте запроса. .>Застрахован. Тесты есть, которые можно ещё и выполнить в разных окружениях и быть уверенным, что везде работает. Не говоря уж о подсветке, автодополнении, авторефакторингах и прочем.
Тесты говорят что все работает в том окружении, в котором запущено. А когда код попадает в другое окружение — внезапно может перестать. Мало того, что большинство не тестирует код совместно с базой, так еще и раздельный деплой базы и кода часто приводит к несогласованности.
.>>>Уже лучше, осталось остальное реализовать и понять, что кода у тебя будет не меньше для той же функциональности. G>>А почему у меня не будет такой функцинальности? Кто-то отменяет ASP.NET MVC? Как раз наоборот, развивают. G>>В том и дело что уже есть такая функциональность. Не надо велосипеды изобретать. .>Я имею в виду ту функциональность, которая в моём коде. .>И это хорошо, что тут веб-приложение, поэтому asp подходит, а если что другое, оффлайн клиент какой-нибудь, то уже упс.
В десктопном клиенте такая архитектура вообще не живая.
.>>>Согласен. Можно и так. Осталось это объединить в одном namespace и внезапно получится UserDao. G>>А почему Dao? Это вполне себе бизнес-логика, то есть логика, нужная бизнесу. Да и как не называй, это не слои приложения, ибо нету косвенности. .>Потому что бизнес не говорит таким языком, не "дай мне предикат поиска юзеров, у которых нет открытых заказов". Бизнес скажет что-то типа "деактивировать можно только таких юзеров, у которых нет открытых заказов. При ошибке отобразить текст XYZ".
А при чем тут как говорит бизнес? Он вообще так не говорит, даже близко. Но какое это имеет отношение к коду?
.>>>>>Ну счастливый. Бывают ситуации когда час простоя даёт чистый убыток несколько сотен тыс $. Плюс труднооценимая потеря репутации. G>>>>Это не тот случай изначально. .>>>Раз в этом формуе, то хочется верить в лучшее, что человек хочет что-то новое изучить, чтобы мог пойти работать в такую компанию. А так пусть пишет на PHP, и никаких забот. G>>А чем в принципе PHP отличается от Java или ASP.NET? Языки разные, а приложения в общем те же — прослойка между базой и UI. .>Что очень маловероятно, PHP будет использоваться в системе, простой которой может нанести серьёзный ущерб.
Ага, поэтому внезапно на PHP сделаны крупнейшие соцсети, простой которых приводит к непоказу рекламы на несколько миллионов в минуту.
По большому счету нет разницы.
Здравствуйте, vmpire, Вы писали:
V>Через явную передачу интерфейсов, через конфигурационный файл, через специальные фреймворки типа moles... V>Если вам лично это не нравится — это не значит, что это шлак.
Не очень знаю, что такое moles, но в целом — если только за конфигурацию зависимостей отвечает не зависящий класс (dependant), а кто-то другой (кто считывает конфигурационный файл, например), то это уже IoC.
Здравствуйте, SharpDeveloper, Вы писали:
SD>Чуствую себя обманутым. SD>DAL<->BL<->Presentation
SD>Конечно нам всем нужен слой доступа к данным, конечно нам нужен бизнес слой и не дай Боже слою представления вызвать метод репозитория! Конечно же (Entity Framework) нам DataContext не годится, нам надо нагородить Generic Repository и в бизнес слое только через интерфейсы IoC'чить репозитории, а посему IQueryable'ы не торчат наружу! Как же! Разве можно привязыватся к Entity Framework? Нам необходим универсальный фасад!
У меня сложилось впечатление, что DDD — это как MVC, всё о нём говорят, все его используют, но никто в точности не может сказать, что же это такое, и ни у кого нет согласия, чем же считать DDD, у всех оно реализовано по-разному.
Лично я понимаю DDD в максимально широком смысле — это когда разработку приложения начинают с бизнес-логики, а потом уже сбоку прикручивают инфраструктуру (в частности, persistence) и presentation, без вмешательства в саму модель. А всё остальное из этого вытекает:
Репозитории служат для того, чтобы методы модели, которым нужно получить доступ к коллекции объектов, не зависели напрямую от системы хранения. В юнит-тестах модели репозитории достаточно легко мокаются, а persistent реализации репозиториев покрываются интеграционными тестами.
При наличии ORM не нужен выделенный слой DAL, т.к. ORM сама по себе выступает таким вот DAL. Репозитории поверх ORM реализуются достаточно тривиально.
Почему представление не может обращаться к репозиторию? Не вижу никаких проблем с этим. Разумеется, под "представлением" в случае веб-интерфейса я понимаю исключительно RPC-сервисы, которые дёргаются скриптами из браузера.
Сами скрипты из браузера напрямую из репозиториев не тащат ничего. И этому есть причины. Дело даже не в языковом барьере (у меня Java + GWT, так что барьера нет), а в том, что когда приложение разделено на несколько процессов (клиент и сервер), то между процессами должно выстраиваться взаимодействие, причём всегда есть определённые накладные расходы, связанные с этим взаимодействием. Например, нельзя просто так организовать ленивую загрузку свойств entity, иначе начнёт ухудшаться отзывчивость из-за latency (да и transaction demarcation идёт нафиг вместе с consistency). Разумеется, разделяем клиент и сервер с помощью remote facade.
Я не знаю, как в .NET, а в Java EE никаких "контекстов" создавать не надо, я просто пишу
@PersistenceContext
private EntityManager em;
и в путь.
Транзакции в сервисах/репозиториях определяются только декларативно и только типа MANDATORY (т.е. вызывающий метод должен предоставить транзакцию), а сами транзакции управляются из remote facades, тоже декларативно.
Такая схема получается достаточно удобной, boilerplate-кода практически нет, риски получить inconsistency практически нулевые, а для пользователя система достаточно отзывчива.
Здравствуйте, ., Вы писали:
.>>>А как mocks и stubs подсовывать без DI? Через глобальные переменные, хаки байткода и прочий шлак? V>>Через явную передачу интерфейсов, .>А как делается явная передача интерфейса, не через DI ли?
Например, параметром конструктора класса.
V>>через конфигурационный файл, .>Это как? Очередной xml?
Да, но он уже есть штатно.
V>>через специальные фреймворки типа moles... .>Как он работает? Как переопределять статики? http://research.microsoft.com/en-us/projects/moles/
V>>Если вам лично это не нравится — это не значит, что это шлак. .>Потому что это делает код теста особенным. А хороший тест это просто обычный код, демонстрирующий как твой класс можно использовать, а не как можно надругаться над средой исполнения.
Не делает, если в коде всё делать так же
V>>Не "повезло", а "разумно спроектировано" .>Code talks
, покажи как спроектировать разумно.
Вы хотите, чтобы я, не зная вашего проекта по отдельным разрозненным кускам кода догадался до решаемой задачи в целом и предложил оптимальное решение?
Из того фрагмента, что я вижу мне непонятно, чем плох просто ADO.NET завёрнутый, если нужно в REST или SOAP web service.
Здравствуйте, LeonidV, Вы писали:
LV>Здравствуйте, vmpire, Вы писали:
V>>Через явную передачу интерфейсов, через конфигурационный файл, через специальные фреймворки типа moles... V>>Если вам лично это не нравится — это не значит, что это шлак. LV>Не очень знаю, что такое moles, но в целом — если только за конфигурацию зависимостей отвечает не зависящий класс (dependant), а кто-то другой (кто считывает конфигурационный файл, например), то это уже IoC.
Там за это отвечает просто пользовательский код. Можно его вызвать из клиента, можно откуда-то ещё... Это уж как программу написать
Здравствуйте, vmpire, Вы писали:
V>Здравствуйте, ., Вы писали:
.>>>>А как mocks и stubs подсовывать без DI? Через глобальные переменные, хаки байткода и прочий шлак? V>>>Через явную передачу интерфейсов, .>>А как делается явная передача интерфейса, не через DI ли? V>Например, параметром конструктора класса.
Ты вообще знаешь что такое DI? Каким местом это не DI?
V>>>через конфигурационный файл, .>>Это как? Очередной xml? V>Да, но он уже есть штатно.
Т.е. если нам микрософт дала фреймворк да с IoC контейнером, то это значит можно громко заявлять, что IoC, DI и прочие фреймворки не нужны, т.к. они отстой и баззворд. Интересно получается, ASP.NET поклонничество какое-то.
V>>>через специальные фреймворки типа moles... .>>Как он работает? Как переопределять статики? V>http://research.microsoft.com/en-us/projects/moles/
Можно привести более конкретную ссылку? У меня вполне точные вопросы.
Мне интересно как он работает внутре. Например, вот был приведён код выше: IT>
Как с помощью moles замокать ситуацию, когда MyDB кидает исключение, например timeout какой-нибудь или Update() ловит constraint violation от базы?
V>>>Если вам лично это не нравится — это не значит, что это шлак. .>>Потому что это делает код теста особенным. А хороший тест это просто обычный код, демонстрирующий как твой класс можно использовать, а не как можно надругаться над средой исполнения. V>Не делает, если в коде всё делать так же
Вот там был ещё код: G>>
Смоделировать, что WithoutOpenOrders всегда возвращает true или false, т.к. это значительно упростит тесты — не понадобится во время тестирования конкретного юнита заводить список заказов удовлетворяющий предикату.
V>>>Не "повезло", а "разумно спроектировано" .>>Code talks
, покажи как спроектировать разумно. V>Вы хотите, чтобы я, не зная вашего проекта по отдельным разрозненным кускам кода догадался до решаемой задачи в целом и предложил оптимальное решение?
Требования вполне читаются из кода. Я выше
их уже описывал словесно. Есть конкретные вопросы — спрашивай.
V>Из того фрагмента, что я вижу мне непонятно, чем плох просто ADO.NET завёрнутый, если нужно в REST или SOAP web service.
Не понял что значит завёрнутый. Суть в том, что помимо собственно работы с базой, там делаются всякие проверки, записывается аудит и шлётся сообщение.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, gandjustas, Вы писали:
G>Здравствуйте, ., Вы писали:
.>>Здравствуйте, IT, Вы писали:
.>>...поискипано .>>Тут логики никакой, никаких if, тупой последовательный код, юнит-тесты не нужны, но можно интеграционные. G>Ты серьезно считаешь что такой код надо писать? А если тебе понадобится добавить инфу кто и когда деактивировал юзера, придется все сверху до низу менять? Это такая шутка?
.>>Можешь начинать ржать, но во время ржания пожалуйста напиши мне аналогичный код в котором только "чистая функция, принимающая всё необходимое в качестве параметра и возвращающая результат как возвращаемое значение".
G>ASP.NET MVC G>
G>Не нужно ничего. От слова вообще. G>Права разруливает фреймворк, транзакционность — EF. Сама логика тривиальна, покрывать юнит-тестами не за чем.
Для гомогенного шуточного проекта пойдет.
Для системы все может быть сильно сложнее
приведенный код напоминает нуб-дельфи-стайл
OnButtonClick { /* поехали писать в базу, пусть даже и в другом потоке*/ }
Код точки нравится больше. Если мне надо лезть в работу с пользователями, для этого есть UserManager, который знает или делегирует знания кому надо.
Упустил реплику когда отвечал.
.>>...поискипано .>>Тут логики никакой, никаких if, тупой последовательный код, юнит-тесты не нужны, но можно интеграционные. G>Ты серьезно считаешь что такой код надо писать? А если тебе понадобится добавить инфу кто и когда деактивировал юзера, придется все сверху до низу менять? Это такая шутка?
Конечно нет, поменяется только имплементация AuditService.reportAction (что в общем-то естественно и очевидно). Будет что-то типа
class AuditService
{
final SessionService sessionService;
final AuditDao auditDao;
final TimeService timeService;
void reportAction(AuditOperation op, long id)
{
Principal changeAuthor = sessionService.getPrincipal();
DateTime whenDateTime = timeService.getNow();
auditDao.save(new AuditRecord(op, id, changeAuthor, whenDateTime));
}
}
TimeService — конечно полушутка, обычно пишется DateTime.now() или просто в БД на поле current timestamp вешается, просто демонстрация как любые сервисы легко инжектить.
Потом, можно иметь несколько имплементаций AuditSerivce, один как тут — для веба, другой, скажем, для периодической задачи, запускаемой где-то совсем в другом месте, запускаемой по таймеру, там понятие "кто" может быть другим, auditDao может делать что-то другое (просто в файл писать, а не в бд).
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай