Как стать автором
Обновить

Комментарии 18

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

Можно обернуть проверку, что словарь не создан, в lock(something) {… }, но будем выполнять лишний lock при каждом вызове GetStringStatus.
Оптимально генерацию словаря засунуть в static constructor. Компилятор сам позаботится о потокобезопасности и об оптимизации блокировок.
да, вы правы конечно.
но, мне кажется, конвертеры должны вызываться из потока UI, а он в сильверлайте один (в нашем случае).
но это конечно не причина оставлять дырки в базовых классах.
хотел показать прежде всего подход, не обратил внимания на недочёты реализации.
На самом деле для таких вещей можно просто использовать Lazy, или же если вручную то в этом случае стоит лишь немного изменить код и он станет уже потокобезопасным (т.к. тут нам неважно ни количество инициализаций, ни уникальность публикуемого словаря — никакие блокировки не нужны):

private static void CreateStatusDictionary()
{
    var sd = new Dictionary<int, string>(); // создали локальный словарь, его видим только мы
    FieldInfo[] fields = typeof(T).GetFields();
    foreach (FieldInfo field in fields)
    {
        string TextStatus = ((StringStatusAttribute)field.GetCustomAttributes(typeof(StringStatusAttribute), false)[0]). TextStatus;
        int statusKey = (int)field.GetValue(null);
        sd.Add(statusKey, TextStatus); // заполнили
    }
    statusesDictionary = sd; // публикация
}

Оптимально использовать примитив 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. Т.е. довольно крупное АПИ заменялось, а не просто подмена одного класса. Если у вас действительно такие масштабы, то надо как-то обеспечивать обратную совместимость и т.п.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации