Pull to refresh

Comments 18

GetStringStatus не thread-safe в этом месте:
if (statusesDictionary == null)
        {
            CreateStatusDictionary();
        }

Можно обернуть проверку, что словарь не создан, в lock(something) {… }, но будем выполнять лишний lock при каждом вызове GetStringStatus.
Оптимально генерацию словаря засунуть в static constructor. Компилятор сам позаботится о потокобезопасности и об оптимизации блокировок.
да, вы правы конечно.
но, мне кажется, конвертеры должны вызываться из потока UI, а он в сильверлайте один (в нашем случае).
но это конечно не причина оставлять дырки в базовых классах.
хотел показать прежде всего подход, не обратил внимания на недочёты реализации.
UFO just landed and posted this here
Оптимально использовать примитив Lazy для этой цели.
Можно же сделать double-check lock, тогда lock будет вызван только при первых нескольких вызовах (в идеале — только при первом вызове)

if (statusesDictionary == null)
{
lock(syncobject)
if (statusesDictionary == null)
CreateStatusDictionary();
}

Почему-то форматирование текста не работает :(
Почему множество, а не сэт, с помощью, а не с хэлпом, включения, а не инклюда? etc.
«Пиплы! Коцайте тикеты! Некоцаный тикет за отмазу не канает!»… За что человека минусуете? Вполне адекватная претензия на переизбыток жаргонизмов в тексте.
Ещё пара вопросов:

1. Если вы наследуете ExPerson от Person, то как создать объект-наследник, имея на руках объекта-предка? (просто копировать одноимённые поля как-то скучно, особенно когда их много).

2. Entity-классы у вас передаются по WCF?
1. да, уже используем редко наследники, но приходится переприсваивать. думаю что должны быть какие-то более элегантные методы, но глубоко не копался.

2. чаще блтулкит чем EF, пишем руками датаконтракты. передаются конечно, как иначе?
2. lazy-загрузка → проблемы при сериализации.
Например, Document связан с Person, у Person ещё вагон документов. Так можно всю базу вытянуть при сериализации объекта, если ссылки хорошо прописаны. Кроме того, закрыл контекст (транзакцию) и lazy-поля, которые не прогружены, начинают кидать исключения при доступе.

В-общем, много граблей и мы отказались от выдачи entity-объектов за пределы кода, который выполняется в транзакции.
Для передачи по WCF отдельные классы (они же, по совместительству view models, хотя это тоже от лени).
оо, я вспомнил этот гемор с EF — у нас были еще и сущности которые смотрели друг на друга =)
это кстати одна из причин почему блтулкит — указал явно какие поля тебе нужны, и вытянул.
в EF большой соблазн написать всю логику на клиенте и сделать SubmitChanges — тогда сервер превращается в какой-то транслятор данных =)
Есть ещё причина не передавать entity с сервера наружу.
Каждому клиенту (особенно если предоставляешь WCF/SOAP API другой команде) желательно давать отдельный интерфейс с отдельными dto-объектами. Чтобы в случае, когда для кого-то надо поменять пару полей/методов, не порушить у других.
1.1. Создать конструктор (или статичный фабричный метод) у PersonEx с параметром Person. Там, конечно, придется копировать скучно, но может рефлексия вас развеселит :)

1.2. PersonEx и Person не наследник-родитель, а реализуют один интерфейс IPerson, но Person напрямую, а PersonEx как прокси к Person, который является приватным полем PersonEx (устанавливается аналогично 1.1, сеттер лучше не использовать — можно забыть установить)

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

То есть практически никогда. По-моему, нужно не только вовремя сливать две фунции в одну, но и вовремя разливать (возможно оставляя общую фунциональность в общей функции, то есть ни одна, ни две, а три функции будет). А чтобы легко определить это вовремя нужно писать тесты и не забывать их прогонять.
Тоже возник вопрос по этому пункту.
Если есть полноценное документирование, с отражением того, что такой-то класс стал зависим от такого-то, да еще и всё это прикрыто более-менее нормальным тестированием, то я абсолютно не вижу проблемы с такой зависимостью. У программиста ответственного за наследуемый класс (мы ведь его наследовали, правда?) возникла задача существенно его переработать, что может повредить зависимым классам. Он открыл описание класса и увидел там зависимый класс. Напомнил архитектору, что тут есть такая зависимость. В ТЗ внеслось изменение, родительский класс разделился на два — абстрактный, с общим функционалом, и конкретная реализация, которую уже можно править. Ну и в зависимом классе из соседней ветки изменили от кого наследуем. Ну или сами просто отнаследовались от изначального класса, и внесли в дочку изменения, чтобы не затрагивать соседей. (Мы ведь соблюдали LSP, правда?). Но тут уже от реализации зависит что будет проще.

ИМХО чрезмерная борьба с дублированием кода чревата не проблемами с зависимостями — это рабочий процесс, а перегруженностью пространства имен. Т.е. когда у тебя столько классов, что они есть на все случаи жизни, но когда будет нужно ты и не вспомнишь, и в документации не найдешь, что у тебя уже есть класс для этого, и 80% твоего времени уйдет на листание документации проекта.
немножко не так.
вы говорите о зависимых друг от друга классах, я же — о методах сервиса, вызывающихся из разных мест клиента. в моем случае невозможно сразу в один клик определить, где этот метод еще используется. и еще более невозможно определить, повлияет ли моё изменение на все другие его вхождения (или оно может быть там нужно, тоже?)

Он открыл описание класса и увидел там зависимый класс.

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

Ну и в зависимом классе из соседней ветки изменили от кого наследуем.

в моем случае такие изменения будут занимать 2-3 дня :) есть же классы, общие для всех проектов — в других солюшенах, есть классы, общие для модулей внутри одного проекта, и т.д.
Администрирование зависимостей делается через теги @ uses & @ used-by или их аналоги в вашей среде разработки.

Что касается реального кода, то тут я тоже не вижу проблем.
Понадобился нам функционал, который уже реализован в FooView->render(), который разработан и поддерживается другим разработчиком.
За полминуты прикинул, что в принципе можно его оттуда заюзать. Прописал соответствующие теги комментирования в своем классе и используемом классе. В идеале только одну ссылку сделал, а вторая появилась автоматически.
На всякий случай о своем решении уведомил тикетом архитектора/ПМа или кто в проекте блюдет общую структуру.
Спустя надцать итераций рефакторинга у нас появился фичреквест требующий изменения поведения класса FooView в том месте где он изначально появился. Тут все просто — проводится стандартный рефакторинг — создаем класс BigFooView, пока пустой, наследник от FooView. Запускаем поиск-и-замена в своем любимом IDE с запросом использования класса FooView ограничив поиск в сфере собственной ответственности, и бегло просмотрев полученный список заменяем все это на BigFooView. Аналогично создаем класс SmallFooView на который заменяем использования нашего класса за пределами нашей сферы ответственности. Отмечаем FooView абстрактным (попутно переименовав его в соответствии с вашим стандартом кодирования для абстрактных классов). Пускаем тесты. Убеждаемся что все осталось целеньким. «Распиливаем» исходный класс вынося код который должен отличаться в дочерние классы (изначально естественно одинаковым, по возможности сохранив побольше общего кода в родительском классе используя или защищенные методы или вызовы родительских методов — согласно логике работы класса). Прогоняем тесты, убеждаемся что все ок, подправляем все док-комменты, вносим нужные изменения в BigFooView.

Если подобный рефакторинг занял больше чем 5-7 минут, то либо вы еще плохо дружите с IDE или не освоились в среде, либо изначальное решение об использовании чужого класса было очевидно ошибочным (что сомнительно), либо идея о внесении изменений в поведение столь активно используемого класса было явно ошибочным. Ваша оценка в 2-3 дня меня как-то уж очень смущает. Я за 3 дня в небольшом CRUD-проекте на пару сотен модулей (с таким же количеством моделей и т.п., с разными статистическими и join запросами и т.п.) общим весом чуть больше мегабайта кода — менял ORM. Т.е. довольно крупное АПИ заменялось, а не просто подмена одного класса. Если у вас действительно такие масштабы, то надо как-то обеспечивать обратную совместимость и т.п.
Sign up to leave a comment.

Articles