Pull to refresh

Comments 40

«каждая строка кода была произведением искусства, достойным безграничного восхищения»
Почему-то вспомнилось, что «в каждой программе есть ошибка, тогда если программа состоит из 1ой строки — в ней точно есть ошибка». Применительно к цитате из статьи: каждая строка кода была произведением искусства, достойным безграничного восхищения, но содержала ошибку… :-)

Программисты и сейчас бородатые!
Всё верно, любой шедевр содержит хотя бы маленький изъян :)
Как-то Вы драматизируете.
Обычно (ну, в моём «обычно») расширения используются, когда объект нужно дополнить чем-то для него логичным, но по каким-то причинам отсутствующим. Удобнее сделать расширение, чем дочерний класс.
Допустим, я скачал Вашу сборку с кошками в виде dll. В моей программе нужно, чтобы кошка мурлыкала. Ваша кошка этому не обучена, я мог бы сделать класс MyCat, дополнив его всего одним методом, а мог бы объявить расширение. Вроде бы ничего страшного же?
Да, собственно говоря за тем и были сделаны extension методы.
Чтобы в этом случае писать не функцию. которая явно принимает this, а через точку упрощая понимание кода.
Пример:
Что понятнее?
public static class DogCater
{
   public static void Bite(Dog dog, Cat cat);
}

или
public static class DogExtensions
{
   public static void Bite(this Dog dog, Cat cat);
}

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

А ваш пример вполне правомерен, для того расширения и были придуманы.

З.Ы. Обновил пост.
У меня вообще сложилось впечатление что автор сетует по большей части по поводу того, что код, расширяющий некий класс, размазывается по куче файлов. И еще по поводу того, что Extension-методы плохо поддаются дрессировке с помощью IntelliSense. Меня кстати тоже бесит то, что штатными средствами VS IntelliSense невозможно быстро ответить на вопрос, какие метода расширения есть у конкретного класса.
Я не очень понял Вас. Вы против extension methods или их неправильного (неуместного) использования?
Я против их неуместного использования. Обновил пост, дабы внести окончательную ясность :).
В таком случае, я согласен с Вашей точкой зрения. У каждого инструмента своё назначение, равно как и свои «накладные расходы». Трудно писать «идеальный код». Иногда extensions помогают в этом, а иногда нет. Слепо (бездумно) использовать любые фичи языка — плохо.
Не надо забывать что функции расширения — есть свертка паттерна «функция расширения» (Фаулер «Рефакторинг») — вариант реализации простой стратегии. Это синтаксический сахар и ничего более. Обе описанные вами проблемы есть и у любой стратегий.

По связке инкапсуляция-функция расширения вообще не согласен. Функции расширения позволяют лучше оттачивать интерфейсы, оставляя в них только то что действительно необходимо. Вспомните IEnumerator(IEnumerable) — я считаю его лучшем интерфейсом в .NET. А таким самодостаточным, легким и удобным он стал только с версией 3.5. До того он был довольно ущербным.
Насколько я знаю, extensions изначально и создавались для того, чтобы расширить IEnumerable (IQueryable и иже с ними). В этом случае их можно и нужно применять. ИМХО не зря в МСДН написано, что программист будет чаще вызывать расширения, чем создавать свои. Одно дело — вызывать общеизвестные Where, Select, Skip и т. д., и совсем другое — продираться через дебри доморощенных расширений, пытаясь угадать, что это за метод: член класса, или extension.
Я бы с вами согласился, если бы не нашел такой фразы:

В то время, как при использовании статического класса у нас есть хорошо сформированное имя, которое обычно отражает логику работы этого класса, для методов расширения у нас есть только догадки.

В одном месте вы боретесь с размазыванием абстракций, с другой предлагая абсолютна эквивалентное решение. Меняете шило на мыло.

Бороться с размытием надо. Но удаление слова this — это не выход.

Я считаю нормальной практикой не хранить фикстуры в тестовом классе. Для этих целей в простых случаях я использую методы расширений. Проект один. Классы обычно находятся в одном файле. Но, задача разделения абстракции решена. От других тестовых классов функция отделена. Вектор развития системы (по необходимости) очевиден: либо выносить методы в общий статический класс, либо преобразовывать в отдельный класс (не статический). Так где здесь проблема?
Я пытаюсь бороться не столько с размытием логики, сколько с ухудшением читаемости. Основная проблема, которую я пытался осветить в статье — неоднозначность восприятия расширений при разборе неизвестного кода. В то время, как с обычными статическими классами всё понятно (вот класс, а вот его статический метод), в случае с расширениями может возникнуть путаница (не всегда понято, что перед тобой: расширение или член класса).
Потому я вами и не соглашаюсь :)
Такие мелочи, как

int32 c = str.ToInt32();

вместо

Convert.ToInt32(str);

упрощают кодинг. Мелочь, а приятно
Так и есть. Но я писал немного не про это :)
Мне это напомнило мою недванюю дискуссию с Java программистом =) Выглядела она примерно так:
— В Яве нет свойств.
— Они нафиг не нужны — NetBeans (прим. или Eclipse, уже не помню) сам умеет создавать методы-аксессоры.
— В Яве нет делегатов.
— Всё, что в шарпе решается делегатами, без проблем решается наследованием.
— Но ведь так удобней.
— Но так меньше путаницы.

В общем, резюме — как и всё, что есть в шарпе, расширяющие методы очень удобны, но в то же время, зачастую все вопросы, которые вы хотите ими решить, можно решить банальным наследованием.

Приведу пример, когда простой расширяющий метод значительно упрощает код и делает его более читабельным (на мой взгляд), но при этом наследованием такого не сделать:
Copy Source | Copy HTML
  1. public static class Extensions
  2. {
  3.     public static bool IsNullOrEmpty(this string str)
  4.     {
  5.         return string.IsNullOrEmpty(str);
  6.     }
  7. }
  8.  
  9. class Program
  10. {
  11.     public static void Main()
  12.     {
  13.         string userName = GetSomeValueFromDB("UsersTable", "UserName");
  14.         if (userName.IsNullOrEmpty())
  15.         {
  16.             Console.WriteLine("User name is empty");
  17.         }
  18.     }
  19. }
Как-то слегка выносит мозг возможность сделать

string str = null;
str.SomeMethod();

и не поймать NullReferenceException :)
Не получите вы NullReferenceException.
На этапе компиляции str.SomeMethod() станет SomeMethodStaticExtensionClass.SomeMethod(str) — а уж внутри все зависит от вас.
Кстати, мелка запара от Microsoft — если у расширяющего метода имя совпадает с именем существующего статического метода (как в примере выше), IntelliSense показывает его самым обыкновенным методом, а не расширяющим. Бага 100% есть в 2008 студии, ну а в 2010 она наверняка осталась, хотя может подправили.
Ну да, именно поэтому в рекомендациях МС ( которым данный пример не следует) есть требование — проверять this агрумент на Null и кидать nullref ( или argnull, не помню). Но даже в этом случае есть «слом паттерна» т.к. экстеншен можно вызвать как обычный статик:
string x = null
Extensions.IsNullOrEmpty(x) -выкинет то же самое что и x.IsNullOrEmpty()

Хотя по логике первый должен выкинуть argumentnull а второй — nullreference.

В общем и целом я согласен что этот синтаксический сахар создает больше проблем, чем реально решает…
А такое
bool? test = null;
var result = test.HasValue;

вас мозг значит щадит?
Щадит, поскольку test — value type. Выражение «bool? test = null;» — лишь сахарок.
Безусловно, расширения — очень полезная вещь и в некоторых случаях очень удобна. Однако когда её фанатично начинают пихать в все подходящие и неподходящие места, наступает полный мрак и ппц :)
>>Это был поистине переломный момент, после которого, как грибы после дождя, стали появляться новые концепции и методики программирования.

Вообще-то они начали появляться как способ борьбы со сложностью программ. Это так, к слову. Но к чему это вступление о золотых временах и какая связь с Extension?

>>В то время, как при использовании статического класса у нас есть хорошо сформированное имя

Я даже знаю каким оно будет. Что-то типа ProjectName.Helpers.Converters.DogConverter.ToCat()

>>Чтобы подключить нужный метод, нам нужно прописать namespace, имя которого может оказаться совсем неочевидным.

А имя этого статического класса мы впитали с молоком матери, что-ли? То-же самое, вид в профиль.
> Непонятно, где находятся различные методы расширения, предназначенные для одного и того же класса. Непонятно, как их подключать.

Из-за таких проблем мы договорились, что все extension-методы для одного типа реализовываются в одном статическом классе вида <TypeName>Extensions. А чтобы не думать, как подключать, эти классы находятся в глобальном пространстве имён проекта. Не думаю, что это сильное «загрязнение» этого пространства имён.
Молодцы, что заранее договорились :) К сожалению, не всем посчастливилось работать с хорошо структурированным кодом.
Да, только вот это, оказывается, не лучший вариант. Ниже Guderian предложил вариант правильнее с корректным неймспейсом.
У вас есть хоть какая-то структура. В некоторых случаях нет и этого :))
Прекрасно Вас понимаю. До того как мы «придумали» такую «структуру», я сам боялся extension'ов как огня именно из-за проблем с поиском. Бывало, помнишь, что кто-то уже писал нужный метод для класса, но в какой сборке определён extension-класс, как он и его метод называются?.. В случае же, если extension где-то используется, найти его не представляет труда полнотекстовым поиском.
В принципе, любая техника проектирования будет отстойной, если взять ситуацию, где ее применять не следует. Зачем выдумывать свой пример, если есть существующие, например, тот же Linq. Как бы вы его перепели статическими методами и во что бы превратились цепочки вызовов list.Where(…).Where(…).Take(…).Sort(…)…? И почему их отрыв от коллекции является обоснованным?

Нарушается один из основополагающих принципов ООП — инкапсуляция.

Инкапсуляция никак не регламентирует местоположение функционала.

Непонятно, где находятся различные методы расширения, предназначенные для одного и того же класса. Непонятно, как их подключать.

Если у вас нет соответствующего стандарта, то да, это проблема. У нас, например, расширение IsDbNull для Object находится в файле +System\Data\ObjectExtensions.cs и пространстве имен System.Data. Очень удобно и прозрачно. Хочу расширения объекта, связанные с СУБД, подключаю System.Data.

можно придумать ещё тысячу доводов

Сделайте одолжение.
Зачем выдумывать свой пример, если есть существующие, например, тот же Linq. Как бы вы его перепели статическими методами и во что бы превратились цепочки вызовов list.Where(…).Where(…).Take(…).Sort(…)…?

Расширения изначально создавались для реализации LINQ, и я обеими руками за их использование в подобных сценариях. Если вы прочитаете апдейт к посту, то поймёте, что я имел в виду совершенно конкретный сценарий применения расширений.

У нас, например, расширение IsDbNull для Object находится в файле +System\Data\ObjectExtensions.cs и пространстве имен System.Data. Очень удобно и прозрачно.

Вы тоже молодцы, что изначально всё регламентировали :) К сожалению, есть проекты, развивашиеся спонтанно и не имеющие чётко определённой структуры. В подобный проектах фанатичное использование расширений, рандомным образом раскиданных по файлам, только ухудшает и без того затруднённое понимание кода.
эта вы, наверно, ещё про dynamic не слышали. вот там то можно такой ужас накуролесить…

но extension-метода даны вам для того чтобы использовать их только там где они уместны.

ваш пример с котопсом неудачен. даже самое название метода DogToCat, который будет вызываться как dog.DogToCat, уже нелепо смотрится.

но попробуйте без них обойтись в linq и asp.net mvc… тёмные времена наступят… тёмные.
Отчего же, слышал я про dynamic, и даже успешно применяю в одном специфическом сценарии. Очень удобно и безопасно, если писать аккуратно :)

Мой пример с котопсом, к сожалению, взят из реальной жизни. За исключением имён классов и методов, конечно :)

Как я уже и сказал, я только «за» использование расширений там, где это оправдано.
Согласен с вами. Есть множество превосходного «синтаксического сахара» в C#, но, почему-то, одни боятся его как огня (типа «Изыди лукавый»), а другие пользуются им не понимая сути этих синтаксических конструкций. Я даже не знаю кто хуже…

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

Статья порадовала. Особенно «когда компьютеры были большими, программисты — бородатыми, а код — процедурным» :)
ну вот и я об этом. крутые быдлокодеры и на асме смогут так намудрить…
так что… чем больше фич у языка тем лучше. просто надо их правильно использовать )
У extensions methods есть еще один плюс — они поддерживаются решарпером как обычные методы.
Представьте себе, что открыли какой-то код, и хотите слегка дописать его, как раз-таки введением хелпера либо же сделав метод расширения для нужного класса. Есть ненулевая вероятность, что такой хелпер либо метод расширения для этого класса уже был кем-то написан. В таком случае, поставив после имени переменной точку и нажав Ctrl+Alt+Space — вы получите список доступных экстеншн методов из зареференсеных сборок, и сможете поискать среди них нужный, а хелпер же нужно будет искать брождением по неймспейсам в object browser-е, ну либо же смотря список usages для этого класса, что не совсем подходит для этого.
Sign up to leave a comment.

Articles