среда, 20 января 2010 г.

Получаем PropertyDescriptor: использование выражений вместо строк с именами свойств

Есть у нас несколько тестов, в которых создаются PropertyDescriptor для свойств объекта. Выглядело это так:

   1: [Test]
   2: public void SomeTest() {
   3:     FooBar foo = new FooBar() { SimpleString };
   4:     PropertyDescriptor property = TypeDescriptor.GetProperties(foo)["SimpleString"];
   5:     // code continues ...
   6: }

Кодировать имена свойств в строковые константы - как-то не айс. Вдохновленный использованием expressions в moq, asp.net mvc и других фреймворках, я добавил вспомогательный метод FooBar.GetProperty:

   1: class FooBar {
   2:     public static PropertyDescriptor GetProperty<T>(Expression<Func<T>> propertyExpression) {
   3:             MemberExpression body = (MemberExpression)propertyExpression.Body;
   4:             PropertyDescriptor property = TypeDescriptor.GetProperties(body.Member.DeclaringType)[body.Member.Name];
   5:             Debug.Assert(property != null);
   6:             return property;
   7:     }
   8: }

Теперь в коде тестов можно избавиться от строковых констант с именами свойств, заменив их на выражения. Новый код показан ниже:

   1: [Test]
   2: public void SomeTest() {
   3:     FooBar foo = new FooBar() { SimpleString };
   4:     PropertyDescriptor property = FooBar.GetProperty(() => foo.SimpleString);
   5:     // code continues ...
   6: }

В плюс: подсказки Intellisense при наборе, более тесная интеграция с инструментами рефакторинга (так, Find All References теперь выдает не пустой результат для свойств FooBar).

8 комментариев:

  1. Все равно какой-то гемор - делать лямбду чтобы получить дескриптор!

    ОтветитьУдалить
  2. К вопросу о магии и излишней обфускации. Когда-то строковые константы с именами членов были неизбежным злом при использовании reflection. Сегодня в нашем распоряжении есть expression trees и они активно используются в современных фреймворках. Посмотри, например, на Html.LabelFor helper из ASP.NET MVC 2. Никакой магии - современный C# в действии. На счет обфускации - по-моему выражения типа x => x.Prop уже стали идиоматическими, поэтому читаются легко.

    "Делать лямбду" - звучит страшнее кода этой самой лямбды :) Я быстро печатаю, но мне больше нравится начать набирать foo => foo.Tr и добить вводом появившуюся подсказку Intellisense, чем набирать полностью название свойства.

    Другое возможное решение было ввести в FooBar свойства, возвращающие PropertyDescriptor для соответствующих свойств, например:

    class FooBar {
    public string Name { get; set; }
    public PropertyDescriptor NamePropertyDescriptor {
    get {
    return TypeDescriptor.GetProperties(this)["Name"];
    }
    }
    }

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

    ОтветитьУдалить
  3. "Syntactic sugar causes cancer of the semicolon". Лямбды хорошая, замечательная штука. По сути лямбды - анонимные делегаты, по матанистому - функции высшего порядка или функционалы. Это хороший, отличный инструмент, но как и любой инструмент он имеет свои рамки применения. Выход за которые может обескуражить и ввести в ступор коллег (если оные имеются конечно, но у нас то serious business =)). Рыть траншеи динамитом - странная практика. Понятное дело, что в данном случае возникает иллюзию fluent style'а, Фаулер вроде как должен быть доволен. Если быть объективным - общее качество продукта в данном случае никоим образом не улучшается(в архитектурном плане в том числе, т.к. сахар на сахаре и сахаром погоняет). Допустим, что над проектом работает не один человек, а 120. Теперь допустим, что 50 из них не настолько проницательны как автор и потратили 15 минут рабочего времени на то, что бы разобраться что тут вообще делают лямбды и как оно работает (т.к. видели их только в контексте LINQ). Еще 30, слава богу, видели Moq и его анальные извращения (я тут размышлял и пришел к выводу, что реальные пацаны, которые слышали про IoC впринципе могут никогда в глаза и не видеть подобные фреймворки, надеюсь что мы когда-нить такими станем =)), но и им понадобилось минуты 3 на оценку затеи. Итого 50*15+3*30 = 840 минут просранного рабочего времени. Просранных почему? Конечное качество продукта улучшилось? Нет. Архитектура улучшилась нет? Читаемость и понимаемость кода? Нет. Стало проще рефакторить? Опять нет. Просто получился красивый хак, что бы было не скучно читать код и востаргаться задумкой. Это все здорово и круто, но в продакшене ведет к проблемам, порою ощутимым. Мы хелперы с экспрешенами появились еще в бете MVC, в первом релизе, насколько я знаю, их выкинули нафиг из продакшен сборки и засунули в futures, я думаю небезосновательно. Не знаю как там сейчас во 2ой версии. Я не против всего нового и классного и сахарного, как может показаться. Наоборот, я фанат. Но всему свое место и время, лямбды - отличный инструмент ниша которому таки LINQ и подобные юз кейсы (ну и еще пара специфичных матинистых случая). Google (прости господи) в своих стайлгайдах максимально минимизирует использование синтаксического сахара. Результат на лицо - от сырцов, например, V8 испытываешь программисткий экстаз. Такого качественного и читаемого кода на плюсах еще нужно поискать. При этом он прост, как трусы за рубль двадцать. И это реальный критерий качества как по мне. Когда желторотый студент легко читает код большого, сложного и качественного продукта, а не втыкает в модные хаки старших товарищей.

    ОтветитьУдалить
  4. Лично я за. Если что-то можно внести под compile-time check - это НУЖНО внести.

    А насчёт "не настолько проницательных" товарищей - для них есть комменты. А если комменты не помогают - для них есть ссаный веник. Постоянно равняться на индусов не вижу нужным.

    ОтветитьУдалить
  5. Приведенное решение вообще никак не связано с fluent interface, причем тут он?

    840 минут? А кто все эти люди, дружной толпой бросившиеся изучать непонятный код? Им больше заняться нечем? (с) :) В реальности если подход нов, он просачивается в код постепенно и сталкиваются с ним только люди, имеющие отношение к данному участку кода. Их никак не 50 человек и им не составит труда распросить автора, если автор сам не успел им рассказать.

    К слову - когда переходили на .NET 2.0 было много людей, нежелавших "разбираться" с generics. Время все расставило по местам :)

    Реальные пацаны, применяющие IoC, но не слышавшие про mocking frameworks? Это какой-то другой мир. В том, который я знаю, IoC и mocking идут рука об руку, упрощая тестирование.

    ОтветитьУдалить
  6. Количество людей гипотетическое же, бывают же большие продукты. Если активно использовать IoC (как, наример, это делают многие java девелоперы) то в mocking фреймворках попросту отпадает необходимость - достаточно реализовать тестовые интерфейсы. 2 rainman-rocks в том то весь и цимез, что в приведенном коде никакого compile-time check'а не производится, там же все упирается в рефлекшен. К тому же я так понимаю у вас там сплошняком гуру программирования работают, начальство на зарплаты не разоряется? Про комменты - в 21 веке код должен быть преимущественно самодокументируемым. Да и зачем это? Если можно просто написать ясный и приятный код. Индусы тут не при чем вообще.

    ОтветитьУдалить
  7. Так наши продукты - не маленькие. Однако код разделен по зонам ответственности (в код чужой команды без спросу никто не полезет), а новшества вводятся постепенно. Проблема обучения imho не стоит так остро, как ты склонен ее видеть.

    Как ты будешь реализовывать тестовые интерфейсы без mocking frameworks? Они как раз избавляют от необходимости вручную реализовывать тонны заглушек.

    Попробуй хотя бы собрать приведенный пример :) Пусть не все, но проверки во время компиляции выполняются. Так, ты не сможешь сослаться на несуществующее свойство - компиляция не пройдет.

    ОтветитьУдалить
  8. 2 ifaaan: про compile-time check Гарри ответил. Не раз и не два я сталкивался с обидными очепятками, из-за которых всё падало в неожиданный момент. Это главный бич динамических ЯП. Да и навигация по коду для явно прописанного свойства упростится, о чём тоже было сказано в самом начале.

    "Самодокументируемый код" - это такой слон для слепых, под ним каждый понимает своё. Давайте не молиться на такие расплывчатые определения, а решать реально возникающие сложности.

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

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

Random thoughts, ideas and questions on .NET development

Постоянные читатели