Pull to refresh

Comments 140

По поводу «альтернативного» синтаксиса запросов, представьте, что у вас есть значение, которое используется в запросе несколько раз:

xml.Elements("blabla")
   .Where(x => x.Attribute("a") != null && x.Attribute("b") != null)
   .Select(x => new Something
        {
            Name = x.Value,
            Value1 = x.Attribute("a").Value,
            Value2 = x.Attribute("b").Value
        }
   )

Для того, чтобы не дублировать код, можно объявить временную переменную с помощью ключевого слова let:

from elem in xml.Elements("blabla")
let attrA = elem.Attribute("a")
let attrB = elem.Attribute("b")
where attrA != null && attrB != null
select new Something
{
    Name = elem.Value,
    ValueA = attrA.Value,
    ValueB = attrB.Value
}

А чтобы реализовать такое без «альтернативного» синтаксиса, понадобятся… правильно, анонимные типы, которые вы также с легкой руки занесли в список ненужных нововведений:

xml.Elements("blabla")
   .Select(x => new { Elem = x, AttrA = x.Attribute("a"), AttrB = x.Attribute("b") })
   .Where(x => x.AttrA != null && x.AttrB != null)
   .Select(x => new Something
        {
            Name = x.Elem.Value,
            Value1 = x.AttrA.Value,
            Value2 = x.AttrB.Value
        }	
   )

Кроме того, анонимные типы используются в качестве проекций в EF. В этом свете очень странно читать обзор возможностей языка от человека, который до конца в них не разобрался. «Пастернака не читал, но осуждаю»?
Спасибо за развернутый пример.

Действительно, несколько раз по ходу поста я старался дать понять, что то, что здесь описано — это лишь мой частный взгляд. Конечно, у меня свои задачи (чаще, это чистая алгоритмика, а не DB или разбор XML). И мой взгляд тоже ограничен. Именно поэтому мне интересны Ваши возражения.

Но позиция — а давайте добавим в язык это, а потом это (я про комментарии к новым фичам C#7)… Она у меня вызывает вопросы. Хочется проанализировать, что было удачным, а без чего можно было бы обойтись. Кстати, сложность языка также добавляет ограниченности каждому комментатору.

С синтаксисом запросов я конечно же разбирался. Просто привел свой опыт, что в моих задачах мне он оказался излишним. Более того, показалось, что он вообще внес слишком большую сложность в язык (ну согласитесь, что это сложная в общем-то конструкция), не дав взамен больших плюсов. Вполне возможно, что как раз после обсуждения в комментариях я изменю своё мнение на противоположное. Как это произошло к моменту, когда в C# решили ввести локальные функции.

Теперь по сути.

В своем коде я предпочту именно последний вариант — функциональный синтаксис с созданием объекта. Правда… можете назвать меня параноиком, но даже тут я бы предпочел описать класс явно. Моя практика показывает, что в таком коде будет меньше проблем при поддержке, хотя чуть больше времени тратится на первоначальное написание. Кстати, тут бы очень к месту было упрощенное создание классов, описанное в конце поста:

class Person(string First, string Last);

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

Релиз C# 3.0 был, на мой взгляд, самым революционным за всю историю языка. Поменялся фундаментальный подход к написанию программ — вместо строго императивного указания, как нужно вычислить значение, нам предложили объяснить, что мы хотим получить, а всю рутинную работу спрятать в стандартные функции. Кроме того, за счет IQueryable и Expression появилась возможность метапрограммирования — одна и та же функция может обработать данные из обычного массива, XML-документа, или вообще из базы данных, транслировав свой код в SQL и выполняясь на стороне сервера (!!!).

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

Еще одна мысль — возможности языка следует рассматривать вместе с возможностями IDE. Часто ли приходится писать код на бумаге или изучать его в распечатанном виде? Я не помню навскидку, как пишется group by в альтернативном синтаксисе запросов, но Visual Studio всегда подскажет. Так же и с объявлением переменных через var: при наведении мыши на имя переменной всплывает подсказка с ее типом. Это гораздо удобнее, чем искать место объявления переменной, чтобы подглядеть тип оттуда.

даже тут я бы предпочел описать класс явно

Создавать по именованному классу для каждого места, где нужна промежуточная переменная в запросе, имхо, наоборот существенно усложнит поддержку проекта. Кроме того, при обращении к БД код с использованием анонимных классов может быть транслирован в легковесный SQL и исполнен на сервере, а вам вернется только проекция данных. С вашим же подходом придется сначала выгрузить вообще все используемые данные, а потом обрабатывать их на клиенте — это может сказаться на производительности.
Добавлю как частный случай описанного выше.

Часто встречаю использование анонимных типов, если в обработке необходимо использовать индекс элемента коллекции.

collection.Select((o,i) => new {Value = o, Index = i})
Вы видели, во что транслируются LINQ-запросы? Что лучше написать всё с выводом типов
    public void MapReduceTest()
    {
        var words = new[] {"...some text goes here..."};
        var wordOccurrences = words
            .GroupBy(w => w)
            .Select(intermediate => new
            {
                Word = intermediate.Key,
                Frequency = intermediate.Sum(w => 1)
            })
            .Where(w => w.Frequency > 10)
            .OrderBy(w => w.Frequency);
    }

Или писать руками
public void MapReduceTest()
{
    string[] words = new string[] { "...some text goes here..." };
    IOrderedEnumerable<int> wordOccurrences = Enumerable.OrderBy(Enumerable.Where(Enumerable.Select(Enumerable.GroupBy<string, string>(words, delegate (string w) {
        return w;
    }), delegate (IGrouping<string, string> intermediate) {
        return new { Word = intermediate.Key, Frequency = Enumerable.Sum<string>(intermediate, (Func<string, int>) (w => 1)) };
    }), delegate (<>f__AnonymousType0<string, int> w) {
        return w.Frequency > 10;
    }), delegate (<>f__AnonymousType0<string, int> w) {
        return w.Frequency;
    });
}
Всё-таки не понимаю, неужели не ясно, какой тип у переменной по её имени и правой части выражения? Почему так прицепились к этому var? Я вообще не помню моментов, чтобы у меня была проблема с определением типа переменной.
Этот пример надо переписать не «barvalue foo = Bar()», а, скажем, «var barResult = CreateBar()» (смотря как принято у вас по стандартам).
Даже если здесь и писать напрямую тип, то уже со следующей строчки этот тип обозначен не будет и всё равно придётся постоянно возвращаться к ней, чтобы вспомнить/уточнить, какой же всё-таки тип.
Да я и не против, просто пример. В большинстве случаев, даже если я и не понимаю, что это за тип, resharper мне подскажет.
Да, но при всем моем уважении к решарперу, читать с его помощью код — это примерно как водить пальцем по страницам книги. Первоклашкам помогает, но очень медленно.
Есть еще один важный, с моей точки зрения, момент это читаемость кода вне студии, например через веб интерфейс TFS при code review.
Да, да! Во вьюверах кода систем контроля версий решарпер не встраивается. А это очень распространенный кейс.
По этому поводу хорошо написал Эрик Липперт — blogs.msdn.microsoft.com/ericlippert/2011/04/20/uses-and-misuses-of-implicit-typing
Я полагаю, там эта тема раскрыта достаточно хорошо.
Если кратко — точный тип выражения не важен, важна его семантика.
Да, написано хорошо. И все же позволю себе добавить.

Эрик Липперт — один из идеологов C# (был ведущим разработчиком компилятора языка). Поэтому стоит рассматривать его аргументы через призму того, что он вполне может даже не замечая этого, подбирать аргументы под уже реализованную конструкцию языка.

Сравнивает он не var и короткое название типа (если бы была конструкция type/typedef), а var и монструозные Dictionary<string, List>. В этих случаях я бы тоже выбрал, пожалуй, var. Но я ведь не могу этот же var использовать в объявлении элементов, видимых извне методов, классов. Там, несмотря на то, что мне важна семантика, я вынужден бесконечно использовать именно это слоноподобное Dictionary<string, List>. А еще — переписывать все эти куски кода, как только решу использовать в качестве элемента словаря что-то другое, а не список децималов. Дайте описать короткий синоним типу и все эти проблемы решатся. И мы опять вернемся к семантике. К тому же, будет хорошо видно, куда посмотреть, если будет нужна структура типа, а не выводить её как компилятор, временами возвращаясь к предыдущим строкам. Таким образом, наберусь наглости сказать, что Липперт на самом деле дает аргументы в пользу type/typedef, просто у него есть только var.

Ну и в конце даже Эрик дает рекомендации, когда использовать, а когда не использовать var. Но дело в том, что на практике var используют практически везде. Т.е., фактически идут против некоторых из этих советов (рекомендаций Майкрософта). Или это мне только везет с таким кодом?
Используя typedef можно легко столкнуться с теми же проблемами, что и в С: что будет если два макроопределения совпадут?

В этом плане идеален Rosylin, который вы там отмели как «инфраструктурную фигню». Он как раз и позволит локально расширять язык. Условно вы сможете себе написать IMyDicrionaryPretifier, который добавит в язык ключевое слово dict, к примеру, которое будет транслироваться в монструозные Dictionary&ltTgsdgsd,Tdgsdgs>.

Это фактически и есть то, что вы предлагаете, только безопаснее и намного мощнее.
>то будет если два макроопределения совпадут?
Не совсем понимаю причем тут именно typedef? А что будет если просто имена двух разных классов совпадут? Да ничего не будет. Если они в пределах одного пространства имен, код просто не будет компилироваться.
Просто если речь идет именно о typedef как он сделан в С, то это плохая возможность.
Если как возможность сделать макроопределение для препроцессора — то эту возможность добавили в «откинутом» Rosylin.

В любом случае высказывание про то, что не хватает typedef не по адресу.
>var barResult = CreateBar()
Это уже венгерская нотация. Собственно в нестрого типизированных языках она спасает, но зачем сначала добавлять в строго типизированный язык проблемы нестрого типизированных языков, а потом пытаться решать их с помощью таких вот костылей.
К тому же тут возникает опасная проблема с рефакторингом. Допустим захотел я переименовать Bar в AwesomeClass. Среда разработки любезно переименовала за меня типы. Последствия лучше показать на примере. Допустим у нас есть несколько вариантов одного и того же кода.
/*1*/ var barResult = CreateBar();
/*2*/ var barResult = Bar();
/*3*/ var result = Bar();
/*4*/ var result = CreateSomething();
/*5*/ Bar result = Bar();

Сравним результаты рефакторинга:
/*1*/ var barResult = CreateBar(); // Bar теперь AwesomeClass, но код не поменялся совсем, название метода и переменной теперь нагло врут
/*2*/ var barResult = AwesomeClass(); // уже лучше, но название переменной по прежнему врет
/*3*/ var result = AwesomeClass(); // все корректно заменилось, код не противоречив и не сбивает с толку
/*4*/ var result = CreateSomething(); // тип result не ясен, если не знать, что возвращает CreateSomething, но код хотя бы не врет
/*5*/ AwesomeClass result = AwesomeClass(); // все корректно заменилось, код не противоречив и не сбивает с толку, тип result очевиден

Вывод: поскольку var уже появился, и никуда от него не деться, придется к нему привыкнуть, но компенсировать его недостатки, кодируя тип в названиях переменных и методов — явно не стоит. Бороться с неявностью, которую вносит var, можно, разве что, уменьшая область видимости переменных, которые с ним объявлены. Если программист может легко охватить взглядом участок кода, от объявления такой переменной, до ее смерти, то тип ему будет ясен из контекста. Тем более, больших методов вообще лучше избегать.
Я не предлагал кодировать тип в названии. Просто так совпало. Я имел ввиду семантическое название переменных, самодокументирование кода или как там его ещё назвать. Если Вам удобнее, то в качестве примера можно использовать, например «var marker = TakeObject(id)».
К тому же, следуя Вашей логике у нас получится «AwesomeClass barResult = AwesomeClass()» и везде ниже будет использоваться переменная barResult (которая на самом деле уже далеко не Bar), что тоже крайне запутает понимание. Тут проблема системная и, заменив var на конкретный тип, её решить будет нельзя. Всё равно придётся переименовывать.
Я и не предлагал вариант «AwesomeClass barResult = AwesomeClass()». Написав, что он лучше, чем вариант с «CreateBar()», я имел в виду, что тут мы видим настоящий тип, хотя бы в момент объявления, и следовательно, можем заметить косяк с именем переменной, и исправить его. Особенно если метод небольшой и весь код перед глазами. В первом же случае, у нас почти нет шансов быстро заметить ошибку.
Наиболее же правильным я считаю вариант под номером 3. Впрочем и классический вариант под номером 5 — не хуже. Они вообще практически равны, ведь тип мы по любому видим только в объявлении переменной, а там, в обоих случаях, конструктор вызван явно и тип очевиден. Пятый вариант просто длиннее, там мы тип указываем дважды. По сути, в третьем случае, тип у нас указан не слева, а справа, как в паскале.
А вот вариант номер 4 — уже не очень удобен для чтения, но он хотя бы не создает иллюзий по поводу типа. Увидев такой код, человек уточнит тип. Время потратит чуть больше, но код поймет, и сбит с толку не будет. Так что, четвертый вариант, все же лучше вариантов номер 1 и 2.
Насчет семантических названий согласен. Названия только такими и должны быть. Просто пример выше действительно выглядел, как венгерская нотация. Из-за этого я вас неправильно понял. Проблема видимо в самой попытке рассуждать о понятности, читабельности, и всем таком, в терминах «foo» и «bar». Примеры с такими именами подходят для объяснения алгоритмов или базового синтаксиса, но они категорически не годятся, когда речь идет о выражении какой-то семантики через стиль кодирования. Оно и понятно, ведь в именах «foo» и «bar» нет абсолютно никакой семантики.
Вас должен интересовать не тип но смысл полученного объекта. Ежели ваша переменная называется foo, то скорее всего её тип будет называться Something, что лишь ухудшит читабельность. var позволяет красиво выражаться на холсте, а если вас что то интересует конкретно — наведите мышкой. В противном случае, количество информации на квадратный сантиметр кода будет больше, а значит вы будете меньше думать об интерфейсах, но больше о следующей чашке кофе.
Насчет var — дело привычки. Обычно отторжение такой синтаксис вызывает только у тех кто долгое время писал на С/С++, со временем привыкают. Читаемость это нисколько не ухудшает если код написан нормально.
И вот 100% не надо typedef, вреда от него будет больше чем пользы. Если имя типа получается слишком сложным то проблема решается вовсе не синтаксическими костылями. Переименуйте тип, создайте новый итд итп. И не используйте вложенные generic-и где ни попадя, и проблемы не будет.

Anonymous types незаменимы в linq например.
UFO just landed and posted this here
У меня наблюдение, что особенно «злостно» (я имею в виду, в ущерб удобочитаемости) var используют те, кто одновременно пишет на C# и JavaScript. Ну опять-таки, оговорюсь, что это в моем окружении.
Насчет имени типа — тут как с любым идентификатором. Он должен хорошо читаться. А с быстрым вводом поможет Intellisense.
Насчет вложенных дженериков — вот, как раз за счет того, что нет конструкции type/typedef, то и получаются они длинными. Почему вы считаете, что от typedef/type будет какой-то вред?
Вложенные дженерики это за редким исключением быдлокод. Если у вас конфликтуют пространства имен — это быдлокод. Если у вас over9000 параметров в дженерике — это быдлокод.
Typedef его конечно мог бы замаскировать, но быдлокодом быть от этого он не перестанет.
А для локалных переменных имя типа читать не нужно, поскольку что за объект обычно понятно из его имени и того чем инициализируют переменную. А если непонятно (т.е. имена переменных вроде a, b, c и ниочем не говорящие названия методов), то это опять же быдлокод, т.о. var сделает неудобочитаемым только изначально неудобочитаемый код, а нормальный код становится только чище и проще.
Зачем же возводить всё в абсолют — быдлокод/не_быдлокод. Реальный проект обычно где-то по серединке, невозможно всё сделать академически правильно. И в имя переменной/метода не всегда удаётся вместить всю информацию, необходимую для понимания.
Ну зачем же так сразу навешивать ярлыки?

Вложенные дженерики это за редким исключением быдлокод.

Список словарей или очередь списков — вполне нормальный и обозримый случай. А это уже вложенные дженерики. Однако, даже просто дженерики (невложенные — например, Dictionary<string, int>) приходится повторять в нескольких местах. Даже с использованием var это приходится делать в каждом инициализаторе, в объявлении поля или свойства:

class SomeClass {
	...
	Dictionary<string, int> dictionary = new Dictionary<string, int>();
	...
	public void Insect(Dictionary<string, int> anotherDictionary){
	...
	}
}


И тут int решили поменять на uint (или сделать из него класс). Или сделать классом — идет мощное редактирование кода во всех этих местах. Если же сконцентрировать это описание в type, то изменить определение нужно будет только в одном месте. var служит той же цели, но в ограниченном количестве случаев. Кроме того, её легче использовать неправильно (заставляя человека выводить тип), чем имя типа.

Если у вас конфликтуют пространства имен — это быдлокод

Речь идет, прежде всего, об импортированных библиотеках (не моих). Типичный пример — импортирую, скажем 2 библиотеки, у которых есть класс Document (а он есть у многих библиотек). И они начинают конфликтовать. Что делать? У меня 2 пути:
1. Записывать квалификатор имени вместе с полным неймспейсом — несколько громоздко, если класс используется часто. В случае, если он по коду упоминается 1-2 раза, так и делаю.
2. Сделать синоним классу через using TheDocument = xxx.xxx.Document;
Я же пояснил, что using у меня остался, но для крайне ограниченного количества случаев, не связанных с использованием в качестве суррогата для type.
Я ярлыки не навешиваю, просто все это пройденные этапы и наступленные грабли.

Не вижу ничего обозримого и нормального в словарях словарей из списков массивов пар ключ-значение. Они могут быть оправданы только в случае каких то очень злых оптимизаций, и то сугубо внутри класса, который в этом случае должен отвечать только за управление этими коллекциями и следовательно быть небольшим. В этом случае никакого мощного редактирования или многократного повторения возникнуть не может — в классе будет 1+ полей с таким типом и все, локальные переменные используют var все просто и понятно, никаких typedef не нужно.

Если при замене int на uint или объект в одном словаре идет какое то _мощное_ редактирование то скорее всего связность кода слишком высокая и/или еще что то в этом роде. И тут опять же надо упрощать и рефакторить а не костылить синтаксический сахар.

Если в одном и том же классе один используется напрямую некий Document из разных сторонних библиотек, то с немалой вероятностью
этот класс — «божественный объект», который к тому же не покрыт нормально тестами по причине невозможности подмены этих сторонних объектов. Обычно за работу с одной библиотекой отвечает одно, за работу с другой — второе, за взаимодействие прослоек — третье.

Ну и если уж без коллизий или многократно используемых сложных имен типов совсем никуда то ограничения директивы using одним файлом скорее преимущество чем недостаток — чтобы эта зараза гарантированно автоматически не распространялась на полпроекта.

Хотел отметить, что мы немного отвлеклись от начальной темы — неудачного опыта использования using xxx=yyy, как замены typedef. Но с точки зрения решения конфликта имен она подходит хорошо.

Такие конфликты возникают редко, но возникают. Для этого не обязательно использовать Document из двух неймспейсов. Достаточно использовать только один. А второй — чтобы был. Он уже своим присутствием в пространстве другого using будет продуцировать конфликт.

Пример из недавнего.

Есть такая библиотека для работы с длинными именами файлов — AlphaFS. Удобная. Её модель — заменить классы из пространства System.IO на свою реализацию так, чтобы в большинстве случаев пришлось только заменить неймспейс System.IO на Alphaleonis.Win32.Filesystem. Но заменяется не все. Например, Alphaleonis.Win32.Filesystem.File.GetAttributes(...), возвращает System.IO.FileAttributes. Этот тип используется неоднократно по коду (не очень много, но достаточно, чтобы полное написание начало раздражать). Если добавим

using System.IO;


то пойдут конфликты между File из разных неймспейсов (хотя мы используем только один). Var не поможет, ибо там нечто вроде:

FileAttributes.ReadOnly | FileAttributes.System | FileAttributes.Hidden


Вот в этом случае

using FileAttributes=System.IO.FileAttributes;


подошло идеально.

Сценарий не частый, но using xxx=yyy уже не первый раз позволяет написать код более чистый, чем с помощью полной квалификации имен в коде.
Ну и если уж без коллизий или многократно используемых сложных имен типов совсем никуда то ограничения директивы using одним файлом скорее преимущество чем недостаток — чтобы эта зараза гарантированно автоматически не распространялась на полпроекта.


Согласен, именно для этого и подходит идеально конструкция using xxx=yyy. И с этой точки зрения ограничение в один исходный файл очень логично.
Вот только не надо про C++, не надо. Какой тип вот в таком выражении у переменных x и y:
auto lambda = [](auto x, auto y) { x + y; };
И ничего — как-то же люди это пишут и читают…
UFO just landed and posted this here
Зависит от того, куда вы эту переменную засунете. В C++14 не только типы и функции, но и переменные могут быть шаблонными. То есть типа, как такового, у них — нету.

Понятно что при передаче в нешаблонную конструкцию (printf там или что-нибудь подобное) она «материализуется» (и все переменные получают-таки определённый тип), а поскольку main у нас [пока?] не шаблонный, то это рано или поздно произойдёт — но это может быть после прохождения 100500 уровней индирекции…

Соответственно описанная выше лямбда — тоже вполне себе шаблонная (даже несмотря на отсутствие ключевого слова template) и может с лёгкостью складывать как числа, так и строки, а то и матрицы…
UFO just landed and posted this here
Для слишком длинных имён можно использовать using, например:

using IntTuple = Tuple;
Ну, тут скорее нужно правильно именовать типы сразу. А вот с using беда в том, что действует это определение только в пределах того файла с исходником, в котором оно введено. Так что после некоторых экспериментов с ним забросил эту конструкцию. Она осталась только для решения вопроса при конфликте имен в разных библиотеках (чтобы не писать полный длинный путь к классу).
Ну по идее можно сделать так:
public class ShortName: List { }

Конечно, с sealed классами не прокатит, но в большинстве случаев применимо. Хотя очень сомневаюсь, что стоит так делать.
Да, конечно. Отвлекся на непрофильное использование using, а этот вариант не упомянул. Этот вариант тоже подойдет, но со своими ограничениями. Чтобы было более понятно — расширю пример

public class Keyword{...}
public class Entrances:List<...>{...}
public class KeywordsDictionary: Dictionary<Keyword, Entrances>{}

class DocumentStatistics {
	...
	KeywordsDictionary dictionary = new KeywordsDictionary();
	...
	void AugmentStatistics(KeywordsDictionary anotherDictionary){...}
	public Entrances GetEntrances(Keyword){...}
	...
}


Ну и этих вхождений типов Keyword и Entrances может быть некоторое количество по разным классам. Кстати, обратите внимание, что var тут никак не поможет. И вот, если конструкция одного из классов изменилась — скажем, Keyword стал чем-то более сложным, с новыми полями, то достаточно его объявление поменять один раз. Да, еще нужно будет покопаться в работе с реализацией, там где это было затронуто, но высокоуровневые объявления не затрагиваются. Это удобно.

К сожалению, есть ложка дегтя. Вы о ней сказали — sealed классы (а это тот же string). Еще добавлю различные базовые типы (int, float), которые часто поначалу используются в качестве ключей, элементов. В алгоритмах с высокой вычислительной нагрузкой все переводить на классы — это существенный overhead.

Для бизнес-логики же вполне можно использовать.
Насчет var — дело привычки. Обычно отторжение такой синтаксис вызывает только у тех кто долгое время писал на С/С++, со временем привыкают. Читаемость это нисколько не ухудшает если код написан нормально.


У меня было наоборот. Стал использовать var повсеместно сразу после его появления. И так было долгие годы пока коллега не открыл мне глаза) Читаемость ухудшает, это факт. Особенно если читаете чужой код. Или свой, но старый.
Из контекста обычно понятно. На крайний случай — мышкой навести. Обычно, когда читаю чужой/старый код так или иначе ей пользуюсь.
Вот-вот, в мышке всё и дело. А код должен читаться как песня, одним лишь текстом, без мышки, без перехода внутрь методов). Должен читаться даже если его просто распечатать на бумаге. Фанаты функциональных языков называют это «предсказуемостью». Я сам долго ломался, но стОит только попробовать и разница становится очевидной.
Ну, как выше писали, код не всегда бывает идеальным. Если приходится использовать мышь для того, чтоб понять, что скрывается за var, стоит подумать о рефакторинге.
Да, код не идеален, а рефакторинг частенько дальше мыслей не уходит. И если можно сделать код чуть более читаемым, почему бы этого не сделать? Лень набирать имя класса? Хорошо, наберите var, потом Alt+Enter->Specify type explicitly и решарпер сделает всё за Вас.
Лень загромождать код ненужными символами. Мозг (пока?) работает достаточно быстро, чтоб определить тип из контекста.
Хм, имя класса — набор ненужных символов? Ну ладно. А откуда в мозгу берётся контекст? Чтобы контекст сформировался, нужно опять же читать окружающий код, разве нет?
Хм, имя класса — набор ненужных символов?
Если оно очевидно из правой части — да. Если оно — длинный генерик — да.

Чтобы контекст сформировался, нужно опять же читать окружающий код, разве нет?
эм, весь тред про чтение кода, разве нет? Код вокруг вы все равно собираетесь читать, вчитываться в название дженерика чаще всего нет необходимости. Цитируя автора
Код становится чище, а значит одобряю.


Вы поймите, я не за повсеместное использование var, я прнотив его тотальной отмены. сли из кода непонятно, зачем нужна переменная, и что она делает — код в принципе хреновый и знание типа переменной не сильно спасет.
UFO just landed and posted this here
Да, фанатеют, но никак не для того, чтобы писать такие портянки

var a = GetA();
var b = GetB(a);
var c = GetC(b);
// etc

Правильно, они в такой ситуации просто напишут что-нибудь вроде вот этого, в зависимости от точного типа функций (примеры на Haskell):


GetC $ GetB $ GetA
GetC . GetB . GetA
GetA >>= GetB >>= GetC

Иными словами, само наличие в коде выше переменных a, b и c — суть костыль из-за недостаточной выразительности языка.


Если же имя для значения и правда требуется (из-за повторного использования или просто ради самодокументации кода), то я не видел чтобы кто-нибудь и правда указывал тип в такой ситуации:


c where a = GetA
        b = GetB a
        c = GetC b
Всегда есть lisp-подобный вариант.
var c = GetC(GetB(GetA()))
Да, всё правильно говорите. Но лично для меня это не аргумент в пользу повсеместного var конкретно в C#.
пример синтетичен из-за названия методов и переменных
UFO just landed and posted this here
Без поддержки со стороны компилятора(или clr) получается довольно неуклюже, с именами item1, item2 и т.д. Хотелось бы честные кортежи с внятными именами свойств.
UFO just landed and posted this here
UFO just landed and posted this here
Еще Events в F# реализованы через Observable, вот бы такое в C#, но уже поздно.


Скажите, а чем это лучше, чем то, как это сделано сейчас в C#? И чем это принципиально отличается? По-сути, то же самое спрятано внутри делегатов и +=
Observer UML
image
UFO just landed and posted this here
Тут уже все описано, но хочется отметить — LINQ на мой взгляд очень полезный инструмент и именно с использованием SQL-like синтакиса. Хотя известно, что некоторые методы доступны только во fluent-стиле. На деле же, я всегда предпочитаю «ЧПС», иногда добавляя fluent вызовы уже по завершению «ЧПС» вызова.
Туда же отправляются анонимные типы. Выше уже было показано, что они незаменимы, когда нужно отфильтровать/объединить и вернуть некоторую комбинацию свойств элементов коллекции, а не элементы коллекции целиком. Образно говоря, может понабодится отфильтровать список ФИО по отчеству, но интерес представляют только фамилии и имена. Тогда применение анонимного класса, который будет содержать только Ф и И, вполне оправдано.
var. Почему все так не любят var? Если код плохо написан, то вам и названия типов/методов ничего не скажут. Никто не призывает писать что-то типа «var a = Request.Something.From.SomeWhere.Using.Idontknow(What)». Но var хорошо сочетается с foreach — циклами по коллециям и using-конструкциями. Детские примеры: «foreach (var word in GenerateSampleSequenceOfWords())» и «using (var str = new Stream())». Обычно этого достаточно для понимания того, какой тип на самом деле скрывается за var.

Более того, синтакс var настолько удобен, что, написав несколько небольших проектов на C++, я был несказанно рад ключевому слову auto, которое так же спасало ситуацию в случае, скажем, с for-циклами по итераторам (аналогично шарповским foreach).
Свое мнение о том, что ЧПС не нужен в C#, я составил, исходя из следующих фактов:

— Одно и то же можно написать и с помощью ЧПС и с помощью функционального синтаксиса. Те отличия, которые есть, решаются в общем-то несложно (в третьем примере из комментария как раз и приведено, как решать одну из таких особенностей).
— ЧПС существенно отличается от других конструкций языка, значит именно он лишний.

Насчет анонимных типов я подумываю, чтобы поменять своё мнение. Если будет еще несколько удачных примеров, то я готов буду это признать.
Стало возможно писать так:

public Class Point {
public int X { get; set; }
public int Y { get; set; }
}


Да простят меня все минусующие, но ПМСМ лучший синтаксис для этой конструкции — в пресловутом Object Pascal:

private
  fSomeIntenralX: integer;
public
  property X: integer read fSomeInternalX write fSomeInternalX;


или с запретом на запись:
private
  fSomeIntenralX: integer;
public
  property X: integer read fSomeInternalX;


Или с сеттером:
private
  fSomeIntenralX: integer;
  procedure SetX(X: integer);
public
  property X: integer read fSomeInternalX write SetX;
...
procedure TClassName.SetX(AX: integer);
begin
  ... do some validation
  fSomeInternalX := AX
end;


Однообразно, легко читается. Если прямое маппирование на внутреннюю переменную, она просто подставляется, как если бы была в public.

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

Это не всегда хорошо: дополнительная информация «по умолчанию», которую нужно помнить. ПМСМ explicitly written конструкции более надежны.
Первый пример: в объявлении свойства имя связанного поля приходится писать два раза. Неужели бывают случаи, когда мы читаем одно поле, а записываем другое?

Третий пример: логика одного свойства оказывается размазана по всему исходнику. Если в классе будет штук десять свойств, то приватные поля и декларации сеттеров окажутся в самом верху, объявление полей — ближе к середине, а реализации их сеттеров — внизу. При изучении такого класса придется постоянно перечитывать код вверх-вниз.

Это общая особенность языка, а не конкретно синтаксиса свойств. Напомню про обязательное разделение на секции interface и implementation в каждом модуле.

Первый пример: в объявлении свойства имя связанного поля приходится писать два раза. Неужели бывают случаи, когда мы читаем одно поле, а записываем другое?

Это нужно скорее для ограничения записи. «Записываем в другое» — как раз случай с сеттером. Это общий синтаксис — вот отсюда читаем, вот сюда пишем. Если создать конструкцию вида «читаемпишем сюда», это будет дополнительная конструкция, усложнение. Излишняя, потому что случай, который она контролирует, покрывается раздельными read write.

Третий пример: логика одного свойства оказывается размазана по всему исходнику. Если в классе будет штук десять свойств, то приватные поля и декларации сеттеров окажутся в самом верху, объявление полей — ближе к середине, а реализации их сеттеров — внизу. При изучении такого класса придется постоянно перечитывать код вверх-вниз.

Это свойство конкретно Паскаля — разделение объявления и кода. Мне лично нравится то, что исходник так структурирован. Кроме того, большую часть времени читается/правится именно тело, а не объявление класса. Напротив, лично мне претит смешение объявления и реализации. Здесь же интерфейс задан отдельно, implementation отдельно.
Если создать конструкцию вида «читаемпишем сюда», это будет дополнительная конструкция, усложнение.

Сложность устройства компилятора и сложность написания программ с его помощью — обычно величины обратно противоположные. Хорошо спроектированная система не должна заставлять пользователя писать массу boilerplate-кода для реализации базовых вещей.

Излишняя, потому что случай, который она контролирует, покрывается раздельными read write.

В вашем утверждении противоречие: а зачем вообще существует синтаксис свойства, связанного с переменной? Вот есть переменная: можно атомарно прочитать или записать. Вот есть свойство, при обращении к которому вызывается логика в виде приватных методов. Можно было бы избежать «избыточности» и сделать для всех свойств единую форму с явным геттером и сеттером:

private
  fSomeIntenralX: integer;
  function GetX: integer;
  procedure SetX(X: integer);
public
  property X: integer read GetX write SetX;
...
function TClassName.GetX: integer;
begin
  Result := fSomeInternalX
end;

procedure TClassName.SetX(AX: integer);
begin
  fSomeInternalX := AX
end;

Но согласились бы вы каждый раз такое писать?
Сложность устройства компилятора и сложность написания программ с его помощью — обычно величины обратно противоположные. Хорошо спроектированная система не должна заставлять пользователя писать массу boilerplate-кода для реализации базовых вещей…
В вашем утверждении противоречие: а зачем вообще существует синтаксис свойства, связанного с переменной? Вот есть переменная: можно атомарно прочитать или записать.

Если нам нужно просто читать и писать внутреннюю переменную, мы ее просто сделаем внешней, без всяких properties и гетеросетеров — и все дела. RW спецификаторы нужны как раз для ограничения записи и валидации/преобразовании/синтеза выдачи.
Потому что выделение публичного property, который просто отображен на внутреннюю переменную 1 к 1 ничем не отличается от нахождения этой переменной в публичной зоне видимости. Писать read X write X это просто дело вкуса (например, с расчетом на то, что в будущем будет добавлена валидация, и инкапсуляция станет «настоящей»), или изменение видимого имени по тем или иным причинам. Так-то можно было просто написать
public
  X: integer;
end;


Но согласились бы вы каждый раз такое писать?

А это и есть как раз «сишная» конструкция со сторанным {return x;} там где функция по сути не нужна.
Нет, это загромождает.
>Потому что выделение публичного property, который просто отображен на внутреннюю переменную 1 к 1 ничем не отличается от нахождения этой переменной в публичной зоне видимости.
Позволю себе с этим не согласиться. Отличия есть, и они существенны.
1. Свойство нельзя передать по ссылке.
int Val = 0;
int PropVal {get;set}
void ChangeValue(ref int val){val=5;}

ChangeValue(ref Val); //работает
ChangeValue(ref PropVal); //ошибка компиляции
Что это означает для нас? Прежде всего то, что мы не можем в будущем заменить публичное поле, на публичное read/write свойство, не сломав обратную совместимость нашего кода. Ведь тот, кто наш код использовал, вполне мог передавать это поле по ссылке, и тогда его код перестанет компилироваться. Так что, если вдруг нам понадобилось добавить немного дополнительной логики к чтению/записи данного поля, например добавить событие на изменение значения, мы уже не сможем так просто этого сделать. Сделав же изначально, свойство вместо открытого поля, мы оставляем себе пространство для маневра.
2. Интерфейсы не могут содержать полей, только методы и свойства. Так что тут у нас и выбора особого нет. Если мы хотим в интерфейсе отразить возможность полного доступа к каким-то данным, нам придется использовать свойство. А поскольку надо сохранять единообразие кода (нам не нужно, чтобы код, являющийся частью интерфейса, сильно выделялся по сравнению с остальным), в объектах тоже имеет смысл использовать свойства. К тому же, все что имеет модификатор доступа, отличный от private — является частью интерфейса нашего класса. А значит, по хорошему, должно соответствовать требованиям, предъявляемым к интерфейсу: никаких полей, только методы и свойства.
Спасибо, принято. Действительно, это еще причины:
например, с расчетом на то, что в будущем будет добавлена...»

Тем не менее, все равно считаю RW семантику более удобной.
Однообразно, легко читается. Если прямое маппирование на внутреннюю переменную, она просто подставляется, как если бы была в public.

Не знаю как вам, а мне 1 строчку читать проще, чем 10. Куча бойлерплейт-кода для совершенно тривиальных вещей — это хорошо?..
Не знаю как вам, а мне 1 строчку читать проще, чем 10

Если в 1 строчке фарш — то нет, спасибо. А пустые return и присвоения — это фарш. Субъективно, конечно же.

Куча бойлерплейт-кода для совершенно тривиальных вещей — это хорошо?

Так маппирование свойства на переменную 1 в 1 одной строчкой вместо строчных геттера/сеттера — это и есть «нет кучи бойлерплейт-кода».

Смысл в том, что если действие примитивно — просто map на внутреннюю переменную, то мы используем 1 строку. А если нам нужен сеттер с валидацией, то уже функция, как минимум 2 строки (простейший if), уже в строчку не уложишь — здесь вынесено отдельно.
Exception filters
Недавно был спор на смежную тему. WinRT и UWP крутятся вокруг COM и часто бывают случаи, когда системные функции могут выкинуть просто Exception (да, именно базовый класс), а HResult будет записан в Message. Собственно для различения таких вот общих Exception и были введены такие конструкции.

Или для TargetInvokationException / AggregateException

ну да, хотя вторых в жизни как-то не встречал. повезло, наверное.

Они возникают при возвращении из асинхронного кода в синхронный через Task.Result или Task.Wait. Так лучше не делать — но иногда приходится. В методе Main, к примеру.

var местами крайне полезна. Каждый раз выписывать какой-нибудь
ConcurrentDictionary<string, MyBusinessObject> foo = GetBusinessObjects(); 
не особо удобно. Или какой-нибудь
Func<IEnumeragle<AnuGeneric>, IEnumerable<AnyGeneric>, List<AnyGeneric>>... 
Ограничение же scope-а только методом снижает непонятки. При этом, здравый смысл в вашей позиции есть, многие фичей злоупотребляют.

Импорт статиков — есть как минимум одно исключение: Math.Sin(), Math.Cos(), etc. Еще вариант: собственный хелпер, методы которого ясно показывают что именно происходит, и при этом не пересекаются с методами того класса, где применяются. Пример:
public List<BusinessObject> GetAll(){ return AsyncHelper.RunSync( () => AnyAsyncMethod); }

AsyncHelper вполне можно статично импортировать.

Инициализация массивов\списков\словарей — крайне сложно читается при большой вложенности. Например, при инициализации Unity контейнера (я про IoC контейнер). Правда, альтернативы я не вижу.

P.S. Спасибо за статью. Четко, по пунктам, с аргументами.
var. Надо во всём знать меру, но как-то у меня вообще никогда в визуалке не возникало проблемы с определением типа. Но тут всё скорее зависит от разработчиков, чей код приходится глядеть.

Зачем так обижать Expression trees? Я к примеру использую для раскраски элементов в одном из своих проектов, очень удобно в файлике конфигурировать. Очень удобно собирать фильтры с веба, хотя тут без Linq to SQL было бы не так удобно, но с другой стороны Linq тут совсем не обязателен.

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

Тоже прям странно, прям вот так обязательно рефлексию и dynamic, как другие разработчики обходятся? Взять вот к примеру WPF, так как-то для Dependency Property обходятся без рефлексии и dynamic. Да, писанина есть, но всё без dynamic. А вот использование скриптов, тут смысл есть. Но всё же штука полезная, у себя использовал DynamicObject для автоматического оборачивания моделей в WPF.

А вот моё отношение к Null propagator спорное, с одной стороны код упрощается, с другой стороны временами можно и не заметить вопросика при просмотре кода, да и злоупотребления могут привести к не очень хорошим последствиям.

Tupple удобно, но лишь бы никто не стал переть это в открытые API, для внутренних нужд библиотек удобно, позволит уменьшить объем кода.
Тоже прям странно, прям вот так обязательно рефлексию и dynamic

Не обязательно. Просто привел как пример, когда опасность не увеличивается, поскольку и так работаем с подгружаемой динамически сборкой, так что нужно проверять каждый чих. В то же время, за счет некоторых особенностей dynamic, получается достаточно производительно без написания своего кода.

Взять вот к примеру WPF, так как-то для Dependency Property обходятся без рефлексии и dynamic.

Да, конечно все можно написать и без динамиков. Но насчет без рефлексии… Я не сильно работаю с визуальной частью, так что не могу считать, что знаю WPF. Там разве внутри под капотом не сидит рефлексия?

лишь бы никто не стал переть это в открытые API

Вот вот… Здесь те же опасения, что и с var. Если что-то позволяет чуть-чуть убыстрить написание первоначального кода, то это будут использовать и в хвост и в гриву, несмотря на то, что результирующий код получится… ну с душком, назовем это так.
Tuples очень годятся для все тех же лямбд, когда как раз вернуть структуру или объект или использовать out/ref-аргументы довольно неудобно.

Мой пример может оказаться очень спорным (поправьте, где я не прав). Давайте посчитаем количество баллов для каждого студента в рамках олимпиады (за каждое задание) и найдем трех победителей:

class TestCaseResult {
public TestCase testCase;
publit int points;
}

class TaskResult {
public Task task;
public List testCaseResults;
}

class Member {
public String name;
public List results;
}

var students = new List {… };

students.Map(s => (s.name, s.results.Map(tr => r.testCaseResults.Map(tcr => tcr.points).Sum()).Sum())).OrderBy(t => t.Item2).Take(3)

Вся эта функциональщина вернет список пар Имя_Студента => Баллы, который уже можно и в Dictionary обернуть, и просто вывести в шаблон.

Такое ощущение, что у вас парсер угловыми скобками по-обедал.

Не совсем понимаю, о чем вы — единственное что не заработало при комментировании — это форматирование кода
Вот в этой строчке ничего не пропущено?
var students = new List { … };
Если говорить о простоте, то в целом, все императивные алгоритмы сводятся к триаде Дейкстры, последовательность, ветвление, цикл.
Со структурами данных сложнее, тут Вирт предлагает всего лишь примитивные типы, массив и структуру (RECORD), ещё нужен указатель на структуру, и больше ничего.

По идее, это всё, что нужно. Может, действительно критичные вещи стоит писать на этом наборе.
Здесь я не касаюсь декларативного программирования, это отдельная песня.

Писать про указатели в контексте "простоты" может только тот, кто либо никогда не работал с ними, либо не представляет альтернатив им.


Как человек, пробовавший и указатели, и управляемые ссылки в C#, и умные указатели в C++, могу сказать: сырые указатели должны умереть!

Указателей достаточно для формирования динамических структур данных.
Если говорить про конкретную имплементацию, то могут быть нюансы. Многие вот боятся NIL-значения, например.

Для динамических структур данных — да, достаточно.


Для построения произвольных графов объектов — нет, не достаточно.

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

Это что-то новенькое.

Не могли бы вы пояснить свою мысль?
Есть математическое «необходимо и достаточно», а есть практическое, инженерное «необходимо и достаточно». Второе множество гораздо больше первого. То что все алгоритмы сводятся к последовательности, ветвлению и циклу, говорит лишь о том, что любой алгоритм принципиально возможно выразить через этот базис. Но в реальной жизни нас еще волнует скорость разработки, возможность оперативной подгонки под изменившиеся требования, поддержка кода и возможность его повторного использования. Ну это так, то что первым в голову пришло. С академической точки зрения, может быть интересно, попытаться открыть банку консервов двумя чайными ложками и тапком, но для повседневного использования я предпочту консервный нож.
Какое отношение имеет тапок к открыванию консервов? Правильно, никакого. Если инженер открывает консервные банки тапком, то такого инженера стоит только пожалеть. Если уж использовать подобные аналогии, то только идиоты открывают банки чайной ложкой с тапком, ведь именно консервный нож как раз и является академическим базисом. А в «реальной» жизни многим «инженеграм» нравится использовать электрические комбайны, с MP3-плейером. Потому что «надо».
А какое отношение указатель на ячейку памяти, и работа с регистрами процессора имеет к тому, чтобы забить в базу накладную от контрагента? Использование слишком низкоуровневых инструментов для написания какой-нибудь бизнес логики может быть не менее абсурдным, чем открывание консервов тапками. Даже если с чисто академической точки зрения это в принципе возможно. И базисы можно подбирать разные. Если кто-то докажет, что двух ложек и тапка действительно достаточно, чтобы открыть консервы, можно будет считать этот набор базисом. Не единственным, но одним из возможных. Например, все логические операции можно выразить через стрелку Пирса. И в каких-нибудь электро схемах это наверняка используется. Но в программировании мы почему-то используем базис «и, или, не», как более интуитивный. Хотя стрелки Пирса было бы необходимо и достаточно.
Вообще пример с тапком и ложками может и не самый удачный. Это просто мем, на самом деле, который частенько мне вспоминается, когда я слышу предложение, делать что-то с помощью неудобных инструментов. Более правильным примером была бы попытка построить многоэтажный дом, используя только кирпичи, цемент и мастерок.
Что же по поводу использования электрических комбайнов, то зачастую это бывает излишним, да. Но если мне надо открыть не одну консервную банку, а несколько тысяч, консервный нож мне уже не подойдет. Хотя им и можно открыть каждую из банок в отдельности, но чтобы открыть тысячу в разумные сроки, нужен комбайн. И даже если сегодня мне поставили задачу открыть именно одну банку, чутье инженера подсказывает, что как только я эту задачу закончу, далее от меня потребуют открывать миллион банок в минуту. Поэтому, пока найденное мной решение не будет хоть в какой-то степени масштабируемым, я не пойду докладывать, что задача выполнена.
Поэтому, пока найденное мной решение не будет хоть в какой-то степени масштабируемым, я не пойду докладывать, что задача выполнена.
10x. На миллион банок закладываться не стоит, это уже другая задача, но масштабирование в 5x-20x предуспомотреть бывает очень полезно.

Иногда, правда, бывают задачи, которые точно не будут расти, там можно и «пределные решения» забацать (наглядный пример: один мой знакомый реализовывал лет 10 назад AES для 4-битного процессора — и вот там запаса не было нигде и никакого, потому что было понятно, что новый стандарт шифрования будет принят не раньше, чем через 10-20 лет, а каждый байт в системах стоимостью в центы — на счету). Но это обычно очень хорошо понято из постановки задачи.
Почему вы рассматриваете применение указателей и регистров как чисто академический подход?

Кто вам сказал, что Дейкстра занимался указателями и регистрами?

Вы вообще Дейкстру читали? Если читали, то должны знать про его мысли о представлении программы как математического объекта (что само по себе максимально удалено от железа с его ячейками памяти и регистрами и приближено к ФП). А если не читали, зачем делаете такие выводы?

Я вас уверяю, что академики не дураки и если надо открыть тысячи консервных банок, они подгонят комбайн.

Но вы делаете типичную ошибку начинающего программиста. Если от вас требуется открыть банку (одну, Карл!), а вы делаете сразу комбайн — то вы сильно неправы и никакое чутьё вас не может оправдать.

Чтобы тратить время и силы на комбайн, сперва надо доказать, что он потребуется. А этим, судя по всему, вы и не думаете заниматься.

Гораздо проще создать позитивную мотивацию на сложном комбайне, увеличить сложность и тем самым поднять свой авторитет повыше )
Я вас уверяю, что академики не дураки и если надо открыть тысячи консервных банок, они подгонят комбайн.
Академики не дураки, но их целью являются не «открытые консервные банки», а «статья в журнале» («доклад на конференции», etc). Почему они, как правило, ограничиваются тем, что описывают как сделать комбайн, но сами — его не делают.

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

Наичинающий программист: с воплем «ух ты — какая интересная задачка» начинает проектировать монстра, отрывающего все банки в мире одровременно с использовать нейросетей (или какая там технология была прочитана/изучена последней), масштабирование — закачаисся, результат можно ждать годами.

И то и другое — в практическом использовании плохо. Как я уже сказал: разумный компромисс — 10x. То есть если попросили открыть одну банку — стоит готовиться к тому, что потребуется открыть десяток. Ну новый контракт заключили или ещё чего. Дело-то житейское. Но на тысячу или миллион закладываться не стоит. Если будет миллион банок, будет дополнительное время и финансирование (если не будет — бежать из такой конторы нужно не огрядываясь).
Ну с миллионом-то я перегнул конечно, это было просто утрированное преувеличение. Я собственно в конце так и написал «хоть в какой-то степени масштабируемым», что подразумевает небольшой разумный задел на будущее.
Да и говоря про комбайн, я не имел в виду бросаться пилить свою собственную вундервафлю. Речь же шла не про создание тапок, ложек, консервных ножей или комбайнов, а про их использование. Так что, исходя из контекста, под комбайном я предполагал использование какого-то готового мощного фреймворка или библиотеки, с расчетом что мелкая задача будет расти, и фреймворк перестанет быть излишним. Собственные комбайны, наоборот, обычно возникают от того, что кто-то начал делать проект на коленке, не оценив его перспектив, затем вовремя не спохватился, чтобы все переписать, и по мере роста проекта, применяемые в процессе костыли разрослись настолько, что стали «фреймворком».
Я не рассматриваю применение низкоуровневых инструментов, как чисто академический поход. Я говорил про неуместность их использования для реализации бизнес логики. Говоря про чисто академическую точку зрения я имел в виду попытки обосновать использование таких инструментов, через математическое доказательство того, что этих инструментов теоретически достаточно. Подобные доказательства пишутся не для того, чтобы люди кидались писать СУБД или браузер на ассемблере. Хотя ассемблер ведь полный по Тьюрингу. На нем что угодно можно написать. Так же и с знаменитой троицей Дейкстры. То, что он рассматривал программу, как математический объект, как раз говорит о том, что его рассуждения имеют интерес, прежде всего, с математической (академической) точки зрения. Такие изыскания очень важны и они ложатся в основу разработки всех языков программирования, но пытаться использовать их" в лоб" для работы в продакшен, это уже фанатизм какой-то. И посмотрите на пост, с которого началась эта ветка. Там человек говорит и про указатели тоже, а не только про Дейкстру. То есть он предлагает использовать низкоуровневые средства и минимальный набор алгоритмических конструкций, потому что этого «достаточно, чтобы написать любой алгоритм». Отсюда и взялись у меня указатели и регистры, Дейкстра тут действительно ни при чем.
Академики конечно же не дураки, и будь перед ними инженерная задача, они бы ее решили не хуже инженеров. Ну может на чем-нибудь и набили шишек с непривычки. И в итоге предложили бы вполне себе инженерное, а вовсе не академическое решение. Просто обычно они решают совсем другие классы задач. Задачи академические, и решения академические. Например, доказать, что решения нет, в их случае тоже результат. От инженера, такого ответа никто никогда не примет. Если начальство сказало нарисовать прямую линию в форме котёнка, инженеру придется как-нибудь изловчиться. Ведь он же эксперт.
Что касается требования открыть конкретно одну банку, подобного рода проекты обычно всегда, либо вырастают в более крупные, либо становятся составной частью более крупных проектов. По крайней мере, мой опыт именно таков. Возможно так бывает не везде.
З.Ы. Про трату времени на комбайн ответил ниже по ветке. Если кратко, то я не предлагал создавать комбайн, а лишь использовать готовый.
Пока не понял необходимости данной фичи, в каких ситуациях она будет полезнее, чем вернуть класс/структуру или же использовать out-аргументы.

Как минимум, ref/out параметры нельзя использовать для async методов(что даже выглядит логичным), а плодить классы для единственного приватного метода, который бы их вернул, не хочется.
Почти на 100% согласен с выводами автора. В LINQ никогда не использую SQL-подобную форму записи, т.к. она относительно красиво выглядит только в простых запросах, пока не появится Distinct или что-то подобное, что приносит кучу скобок и многоэтажность. Никогда не использую var: по мне без него код выглядит куда более читаемым. Как пользователя SharpDevelop меня разозлило требование использования var в новой версии IDE, хоть это и можно отключить в настройках. Кстати, в гайде по разработке самого SharpDevelop также требуют использовать var.

Меня удивляет, что все еще встречаются люди, которые считают себя умнее всей команды разработчиков компилятора C#.


И, как ни странно, каждый из них считает, что в совершенстве знает C++.

Это больше похоже на религию — «Не обсуждать, что решили патриархи. Они умнее и все продумали за нас».

Да, команда у C# сильная. Но есть и другие команды. И у них другие решения. Зная, что задачи нормально решаются и на других языках, вполне можно попытаться понять уместность той или иной конструкции. Своеобразная ретроспектива. Понятно, что она уже ничего не решит в текущей версии языка. Но, возможно, сложится мнение, которое будет влиять на дальнейшее развитие через комьюнити.

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

Также целью написания статьи была попытка выйти из мира своих задач. Поэтому с интересом читаю все примеры кода в комментариях. Кое-что уже подправил и в своей позиции. Кое в чем укрепился.

Насчет C++ вроде претензий не было.

Обсуждать можно и нужно.
Но не на основе вкусовых предпочтений (typedef вместо var), а на основе сценариев для которых та или иная фича проектировалась.


var + linq + анонимные типы придуманы были чтобы работать с базой данных из C# с типизацией.
Попробуйте спроектировать работу с базой данных типизировано, так чтобы не получилось var, анонимных типов и linq-подобного синтаксиса.


А длинные вложенные генерики — второстепенный сценарий, а не основной.


С другой стороны есть динамические языки, где вообще типы переменных не указываются и люди как-то пишут. И даже хорошо пишут.
Может вовсе необязательно засорять язык аннотациями типов, а использовать возможности компилятора по выводу типов?

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


Да, есть такое. В спойлерах попытался объяснить, что я хорошо понимаю, что есть разные люди. Кому-то не мешает отсутствие опеки со стороны компилятора. Но я — не такой.

Обратите внимание, везде, где только возможно, я пытаюсь вставить оговорку, что это мой взгляд. Я уважительно отношусь к людям, пишущим на любых языках. Просто у меня они (динамические языки) не идут — бесят и требуют массу времени на выискивание опечаток и других дурацких ошибок. Когда-то я думал, что нужно просто больше заниматься программированием и читать правильную литературу. Но, со временем, понял, что многое зависит также от личных особенностей. У меня, например, ниже среднего объем кратковременной памяти (то самое магическое число 7±2). Есть и другие объективные отличия от среднего человека. Ничего необычного — просто я такой. Но эти отличия приводят к тому, что я начинаю сталкиваться с некоторыми проблемами раньше других (с другими же наоборот — позже). И мне приходится применять разные приемы и приемчики (например, делить на более короткие методы) там, где другие люди еще просто пишут код и не испытывают ни малейших проблем с охватом уровня полностью.

По совокупности всех этих причин, у меня не сложилось со скриптовыми языками. Там моя производительность низка. Это особенно печалит из-за того, что все самые перспективные библиотеки для Deep Learning имеют в качестве фронтэнда именно скриптовые языки, прежде всего Python (Lua, R). А к этому направлению я сейчас сильно присматриваюсь. Надеюсь, что в Гугле все же допилят нормальную поддержку Go в TensorFlow (на мой поверхностный взгляд, это все же самая перспективная open-source библиотека в данном направлении на сегодняшний момент).
Тема называется
C# — есть ли что-то лишнее?


А не
Я считаю, что C# есть ли что-то лишнее


Поэтому не надо теперь про "я понимаю" и "это мой взгляд". И про свой опыт в языках вы не написали в начале темы.
То есть вы умышленно сделали вид что ваше мнение имеет большой вес, так что не надо теперь оправдываться.

ИМХО вообще не стоит писать про языки если вы не владеете на высоком уровне как минимум одним языком с динамической типизацией, одним языком со статической типизацией, одним функциональным языком и одним декларативным (типа SQL).
Я ответил на ваше замечание о том, что люди неплохо пишут программы на языках с динамической типизацией.

C# мне нравится и, в общем-то, устраивает. Сейчас это мой основной инструмент. Но скорость накапливания в нем новых фич порождает мысли о том, что он становится слишком сложным для обучения новых людей, просто для дальнейшего развития. Своеобразная скрипка Энгельбарта.

Насчет языков можете еще добавить программирование прямо в коде без ассемблера. Еще желательно на различных архитектурах. Мне именно этот опыт кажется крайне важным. Становится понятно, насколько нелогичной является архитектура x86 (по сравнению с другими). Это наслоение нескольких поколений эволюции, зачастую мешающих друг другу. Обязательно добавьте также использование NoSQL-систем. Я согласен, что перечисленные вами подходы (языки, технологии) нужно попробовать. И не на уровне прочтения спецификации языка. Это все было в разные периоды. В том числе и работа не с теми языками, которые мне нравились. Конечно, с них старался соскочить при первой возможности.

Разработка ПО — та область, которую охватить полностью уже невозможно. Да, это было возможно лет надцать лет назад — ситуация параллельна древнегреческим ученые, которые держали в голове весь объем знаний того времени, но сейчас это нереально. Крайне желательно заглядывать не только в свой мирок, но и в параллельные вселенные, но активно работать со всем — нереально. Сложность систем стала слишком велика. Остается только использовать достаточно репрезентативную выборку.

И вот именно здесь становится важна сложность инструмента. Чем меньше его сложность — тем легче его освоить. И тем больше инструментов можно опробовать и держать наготове.

Возвращаясь к языкам с динамической типизацией. Да и просто к языкам. Сейчас программированием занялись (или пытаются) очень многие люди. Понятно почему — денежное направление и все такое. Но не у всех мозг приспособлен именно для такой работы. В центрах занятости можно пройти тесты и узнать свой тип. Если он окажется не «человек — знаковая система», то освоение именно программирования скорее всего будет идти достаточно тяжело. Тут можно посоветовать какие-то смежные специальности — IT достаточно широкая область, где есть и работа с людьми и еще много чего (конечно, знание программирования хоть на каком-то уровне и тут поможет — это как развитие общего кругозора). Это я к чему — сейчас я уверен, что можно и нужно разрабатывать более тонкие психотесты, на основании которых будет понятно, какой подход у человека пойдет легче всего — императивный или же функциональный, языки со статической типизацией или же динамической. Именно убежденность в том, что многие предпочтения (и связанная с этим эффективность работы) зависят от особенностей психики разработчика и для разных людей она будет различной, заставляет меня везде делать оговорку «на мой взгляд». Это не от неуверенности, а от понимания того, что все разные и во что это выливается.

Но помимо субъективных моментов есть и объективные. Простая система проще учится, проще держится в голове. И именно она даст возможность опробовать большее количество подходов (поддержать широту взглядов). И еще — сложность не всегда объективна. Иногда сложность системы не окупается дальнейшей простотой использования. Именно попыткой найти такие моменты в C# и является пост.

Но никто не предложил своих вариантов (если не считать таковым призыва к самоограничению в комментарии https://habrahabr.ru/post/302076/#comment_9657336). Неужели есть только путь накопления фич и чем их больше, тем язык лучше?
По поводу предложения своих вариантов, есть у меня одна идея. Хотя она и кажется мне слишком фантастичной. Но последнее время все чаще о ней думаю.
Сначала о проблеме, как я ее понимаю, и откуда она на мой взгляд проистекает. Языки со временем усложняются. Это касается всех активно используемых языков. Особенно это заметно на примере языков с долгой историей. Почему так происходит? Причин, как мне кажется, четыре. Во всяком случае основных.
Во-первых, для разных классов задач подходят разные инструменты, а чтобы один инструмент подходил под многие задачи, он должен быть универсальным. Проблемы у всех универсальных инструментов примерно одинаковые: повышенная сложность и меньшая эффективность, по сравнению с узкоспециализированными инструментами. Эффективность зачастую не так критично падает, а вот сложность может расти очень сильно.
Во-вторых, люди стремятся использовать, прежде всего знакомые им инструменты. Особенно учитывая, что наиболее популярные языки довольно навороченные, учить их долго и сложно, а менее популярных — столько, что жизни не хватит, даже примерно их все оглядеть, и неясно что из них выбрать. Это толкает создателей инструментов, делать их все более универсальными. А единственный способ это сделать — наращивать функционал.
В-третьих, разные классы задач пересекаются между собой, так или иначе. Многие решаемые задачи являются составными частями большого проекта. Бывает, что одну подзадачу было бы удобней решить с помощью одного инструмента, а другую — с помощью другого. Но в программировании это означает, что мне удобно было бы часть проекта написать на Си, часть на С#, а что-то на Scala. Возникает проблема — как потом все это совместить? Гораздо удобней в таком случае, если один язык позволяет написать код в разных стилях и с использованием разных парадигм.
В-четвертых, мир быстро развивается, особенно сфера IT, поэтому языкам программирования тоже приходится стремительно развиваться, чтобы не опоздать за прогрессом. А требования обратной совместимости, не позволяют удалить или переделать старое. Можно только добавлять новое. Поэтому в старых языках проблема со сложностью стоит особенно остро. Да и новый функционал выглядит не всегда логично из-за этого. Взять тот же модификатор «private protected» в C#7. Сама идея хорошая, а синтаксис дурацкий. Если же прекращать развивать старый язык и просто делать новый, то возникнет проблема, как совместить старый код, написанный для старого языка с новым кодом. Ну не выкидывать же проверенные и хорошо отлаженные библиотеки. В свое время C++ перетащил в себя кучу архитектурных решений из C, только чтобы быть с ним максимально совместимым. И ведь взлетел, во многом, благодаря этому.

Ну а теперь к предлагаемому мной решению. Функционал естественно должен развиваться. Крупным проектам бывает необходимо совмещать разные подходы для реализации их составных частей, и интеграция должна быть максимально простой. Идти в ногу со временем тоже нужно. Так как же всего этого добиться, не усложняя языки программирования? Ответ на поверхности — нужно создавать много простых языков для разных задач. А чтобы не терять простоту совмещения разных подходов между собой, и возможность использования кода, написанного для других языков, надо сконцентрироваться на том, чтобы эти языки между собой легко стыковались. То есть, чтобы модуль, написанный на одном языке, было так же просто использовать из другого, как если бы они оба были написаны на одном языке.
Сейчас какой основной способ использования кода одного языка из другого? Скомпилировать библиотеку и ее слинковать. Лично мне кажется, что это никуда не годится. Во-первых в разных ОС формат библиотек и методы их линковки разные, во-вторых подключение таких библиотек сложно назвать простым и удобным. Прежде всего проблема в том, что языки не проектируются с расчетом на легкое использование кода друг друга. Вот это и надо исправлять. Нужен простой и современный стандарт, которому должны будут соответствовать языки, чтобы быть взаимно совместимыми. Тот же .Net и так является семейством языков. Надо только развить эту идею до должного уровня. И тогда не придется в один язык пихать абсолютно всё. И об обратной совместимости можно было бы не париться. Представьте, в том же С++, не пришлось бы придумывать использование фигурных скобочек для присваивания, чтобы избежать неявного приведения типов. Просто в модуле указываешь версию языка, и типы перестают неявно приводится при обычном присваивании с помощью знака равно. Что-то на подобие «use strict» в современных версиях JS. Можно было бы даже использовать разные версии компиляторов для старых и новых модулей, чтобы код самого компилятора не усложнялся.
В общем, мне кажется, что будущее за расширяемым семейством простых специализированных языков. По функционалу это будет как один большой суперъязык, но его подмножества будут четко разграничены, и не надо будет учить его весь, а только необходимые тебе части. Сейчас ты не можешь сказать, что знаешь С#, если не знаешь всех его составляющих. К примеру, нужно обязательно знать оба синтаксиса LINQ. Дело не столько в синтаксисе даже, а в том, что все эти синтаксические штуки, про которые написано в статье, предназначены для разных задач, и программирования в различных стилях. Мало кто решает все типы задач, и использует все возможные стили. И работодатель тоже, как правило, не может сформулировать, владелец какого из стилей им нужен. Вот и получается, что многие вещи ты не используешь, но знать их нужно. А держать в голове, то чем не пользуешься, вообще тяжело. Выучить — не проблема. Проблема — не забыть. При дроблении функционала на разные языки таких проблем не будет. Расширяемость, так же будет намного выше, чем у обычного языка. Не будет проблем обратной совместимости. Исчезнет главная проблема новых языков — малое количество готового кода и компонентов. Один и тот же класс задач смогут реализовывать разные взаимозаменяемые языки. Конкуренция языков будет высокой, ведь перейти с одного на другой, для разработчиков будет не сложно. Скорость эволюции языков будет соответствующая. По сути, два разных синтаксиса LINQ, в такой системе могли бы быть разными языками. Распространение получил бы наиболее удобный, а второй — просто исчез. Либо оба заняли бы свою нишу, не мешая друг другу.
В принципе, решаются все четыре описанные проблемы:
1) От слишком универсальных языков, и их сложности, мы уходим.
2) Языки становятся проще, учить их легче, и даже из непопулярного языка можно использовать компоненты, написанные на более популярных.
3) Пересекающиеся задачи пишем на подходящих языках, а потом все стыкуем.
4) С обратной совместимостью проблем нет — несовместимую со старой, новую версию языка, считаем новым языком, а использовать старый код из нового — не проблема, уж если разные языки совмещаем без проблем.
Сейчас ты не можешь сказать, что знаешь С#, если не знаешь всех его составляющих.
И вот именно поэтому языки и «растут».

И работодатель тоже, как правило, не может сформулировать, владелец какого из стилей им нужен.
А ему это и не нужно. Он ищет специалиста со знанием C# — а про конкретный стиль нанятый работник узнаёт на месте.

При использовании же семейств языков количество потенциальных кандидатов сужается (нужно знать два, три, пять языков — пусть простых, но разных), и, стало быть, увеличиваются требования к зарплате (чем меньше претендентов тем они переборчивие).

Так что не там вы ищите проблему, не там.

Конкуренция языков будет высокой, ведь перейти с одного на другой, для разработчиков будет не сложно.
Почему вдруг? Изучение любого языка, даже очень простого — это куда более серьёзная головная боль, чем изучение пары новых фич, добавленных в существующий язык.

Думаете зря Google создал Go? До этого у них как раз и использовались чуть не десятки «маленьких», специализированных языков.

Понятно, что развитие идёт по спирали: вначале — люди пробуют создать кучу языков, потом оказывается что это неудобно, создаётся универсальный язык, в который добавляют фичи, работать с ним становится всё сложнее, а потом… потом всё повторяется…
>А ему это и не нужно. Он ищет специалиста со знанием C# — а про конкретный стиль нанятый работник узнаёт на месте.
А будет искать специалиста, со знанием и умением применять определенные парадигмы и стили, а конкретный язык разработчик узнает на месте. Язык освоить проще, чем новую парадигму. Особенно, если язык простой.
>Изучение любого языка, даже очень простого — это куда более серьёзная головная боль, чем изучение пары новых фич, добавленных в существующий язык.
Это вы рассуждаете с точки зрения того, кто уже знает язык. В идеале начал его изучение, когда язык только появился. Такой человек растет вместе с языком. Новичок же вообще не знает за что схватиться. Это как раз приводит к дефициту кадров, и к тому, что за ветеранами языка начинается охота, как за единорогом, т.к. порог входа со временем повышается, новичков прибывает все меньше, и знания языка у них неполные, а более опытный разработчик, не только язык знает в совершенстве, но и опыта имеет много. И поскольку это происходит со всеми языками, то специалисту в одном языке трудно перейти на другой, даже чисто психологически, потому что тут ты гуру, а там будешь джуном, с соответствующим статусом и зарплатой. Именно поэтому я и говорю об упрощении отдельных языков, с превращением набора знаний в некий конструктор, который можно постепенно дополнять. И если языки по прежнему будет очень сложно изучать, значит их не достаточно упростили. А вообще, чем больше языков ты знаешь, тем проще изучать новые. Особенно, если язык реализует известную тебе парадигму. К тому же, новичкам тоже будет намного проще. Работодатель сможет взять джуна, который знает пару простых языков, используемых компанией. И этому джуну сразу можно будет давать реальные задачи. Он сразу будет понимать чужой код, написанный до него, и сам может писать так же. А остальной стек языков, будет изучать постепенно, параллельно с работой и под руководством других программистов. Причем, внимание, не обязательно сеньоров. Такой же джун, со знанием другого языка сможет его научить. Они научат друг друга. В этом и суть упрощения, ведь сложность сейчас состоит не столько в том, что надо выучить много всего, а в том, что надо выучить много всего СРАЗУ, а до этого ты бесполезен в продакшене. Если же подмножества используемых технологий имеют четкое разграничение, не нужно знать их все на старте. Можно начать с небольшого стартового набора, и развиваться постепенно. Это как декомпозиция в коде, когда мы разбиваем один большой метод, на несколько маленьких, минимизируя интерфейс между ними.
>Думаете зря Google создал Go?
Говоря о простых языках, я как раз и представлял что-то вроде Go. Точнее некую комбинацию, вот таких простых языков общего назначения, как Go, и более узких небольших языков, которые добавят глубины. Я как-то, пару месяцев назад, взялся его изучать, сделал тестовый проект. В принципе, язык реально выучить за пару дней. И это при том, что там не только синтаксис свой, но и много концептуальных вещей, новых для меня. Конечно, сейчас я успешно забыл, большую часть, потому что нет возможности на практике использовать. Но это не страшно. Появится подходящий проект, просто за день повторю все, и начну писать. Главная проблема Go, на мой взгляд, низкая расширяемость. Нету средств для обобщенного программирования, нету шаблонов. Это позволяет сохранить язык простым, так что это и преимущество тоже. Но и ограничение. Если же будет возможность совмещать его модули с другими языками, такой проблемы не будет. Не хватает универсальности одного языка, дополни его другим. Языки просто перестанут пытаться стать серебряными пулями, а будут просто занимать свои ниши.
В том же Go, кстати есть и пример того, как сопрягать разные языки. Сишные модули транслируются в конструкции, нативные для Go. Что важно, это заложено в стандарт. Сопрягаемость с Си, это то, за счет чего взлетел C++ в свое время. Тогда, ради совместимости, было решено сделать языки похожими. Go показывает другой путь. Языкам не обязательно быть похожими, чтобы быть совместимыми. Нужно лишь стандартизировать способ использования одного языка из другого.
Вообще, может вы и правы, и настолько радикальных изменений, как я описал не будет, но во всяком случае, стандартизация сопряжения языков, даст нам решение проблем обратной совместимости, позволит более быстрое внедрение новых технологий разработки, с сохранением старой кодовой базы, и даст больше шансов новым средствам разработки «взлететь». Существуют же всякие стандарты для операционных систем, тот же POSIX, есть стандартные сетевые протоколы, модель OSI, а как доходит до языков программирования, то кто во что горазд.
Это вы рассуждаете с точки зрения того, кто уже знает язык.
Это я рассуждаю с точки зрения человека, которого приглашают работать в команду со своим «мини-языком» (или, ещё хуже, со своими «мини-языками»).

И если языки по прежнему будет очень сложно изучать, значит их не достаточно упростили.
Тут есть некая проблема: если язык «хорошо заточен» под определённую область (как какой-нибудь APL), то он оперирует концепциями, которые в других языках выглядят сильно по-другому, а значит освоить его будет сложно. Даже если он очень небольшой. У нас для запуска заданий на кластерах есть свой собственный язык — и он в 100 меньше, чем полнофункциональный язык, но первый раз, когда я столкнулся я неделю доводил свой конфиг «до ума» под руководством «опытных гуру».
Особенно, если язык реализует известную тебе парадигму.
Ммм… А если язык похож на 100500 других языков вообще кому-то нужен? В том-то и дело, что «если язык реализует известную тебе парадигму», то он, скорее всего, не нужен вообще.

В этом и суть упрощения, ведь сложность сейчас состоит не столько в том, что надо выучить много всего, а в том, что надо выучить много всего СРАЗУ, а до этого ты бесполезен в продакшене.
К сожалению «маленькие языки» делают эту проблему только острее.
>приглашают работать в команду со своим «мини-языком» (или, ещё хуже, со своими «мини-языками»)
Что значит со своим? Собственной разработки? Не думаю что будет все настолько диверсифицированно. Зачем людям делать свой язык, если можно решить задачу с помощью существующих? Ведь написать язык это еще сложнее, чем свой фреймворк сделать. Такое осилят только очень крупные компании, и в условиях, когда языки легко между собой интегрировать, их языки быстро перейдут из разряда «своих», в разряд общеупотребимых. А если кто-то и будет клепать проприетарные языки, похожие на уже существующие, то разве сложно выучить язык, если он простой, и похож на уже существующий, который ты знаешь?
>если язык «хорошо заточен» под определённую область (как какой-нибудь APL), то он оперирует концепциями, которые в других языках выглядят сильно по-другому
Это уже больше относится к изучению новых концепций и парадигм. Сложность тут не от языка зависит. И если такой заточенный язык нужен, его и сегодня создадут, только о совместимости и интеграции с другими могут не подумать. Если же концепция человеку знакома, т.е. он не новичок в предметной области, то изучение такого языка наоборот будет для него естественным. Читая стандарт такого языка, строчку за строчкой, он будет лишь кивать головой и приговаривать: «Ну конечно», «Само собой», «Так и надо», «Я бы тоже так сделал», и т.д.
>если язык реализует известную тебе парадигму», то он, скорее всего, не нужен вообще
Обычно, исторически так складывается, что несколько языков развиваются параллельно, а потом разница у них оказывается в деталях. И «одинаковые» языки продолжают существовать по двум причинам:
1) Кодовую базу терять жалко, начав писать проект на одном языке, нельзя продолжить на другом. Языки хоть и похожи, но не очень совместимы между собой. Если кратко, то легаси.
2) Кому-то могут быть важны вот эти самые детали, различия между языками.
Соответственно я и предполагал, что многие различия уйдут в сопрягаемые миниязыки. И для каждой мажорной парадигмы, со временем, останется небольшое количество языков, либо вообще один язык. И легаси уже не помешает этому процессу. Скорее все же их останется несколько. Некоторые детали не слишком удобно будет выносить отдельно. Но языки будут просты и похожи. Зная один, очень быстро освоишь другой. Образование тоже сможет адаптироваться к этому. Будут специальные учебные материалы, типа: «язык B, для тех, кто знает язык A». Сейчас, я например не смог найти книжку: «Java, для тех кто знает C#». Может дело в том, что отличий все же слишком много, и подобная книга оказалась бы толще, чем мне представляется.
>К сожалению «маленькие языки» делают эту проблему только острее.
То есть вы считаете, что система, которую я описал, когда два джуна, хорошо знающих каждый свой небольшой язык, успешно справляются, каждый со своими небольшими задачами, и параллельно друг друга учат и сильнее погружаются в процесс — не сработает? Можете объяснить почему?
Мне почему-то кажется что должно сработать. Вот даже с сегодняшними реалиями, в компанию, разрабатывающую сайты, приходит человек, знающий только SQL Ведь можно ему поручить писать хранимые процедуры. А параллельно, он будет учиться, не только использовать, но и разрабатывать базы данных. Совершенно независимо от этого, его учат, например, писать регулярные выражения. И вот он уже может писать их, помогая, как в бэкенде, так и во фронтенде. Параллельно он изучает html и css. Изучив, он уже и верстать может. Следом он начинает изучать JS. С последним конечно придется повозиться. Человек постепенно обрастает знаниями, становясь full stack разработчиком. Но вход у него получается плавный, и даже со старта, он был полезен и выполнял реальные «боевые» задачи, зная только SQL. С каким-нибудь навороченным языком, типа C#, проблема в том, что если человек уже освоил основные алгоритмические конструкции, типы данных и структуры, но все еще не въехал в наследование, дженереки, лямбды, делегаты, интерфейсы и LINQ, он почти бесполезен в работе. Даже если сам он уже дорос, чтобы писать простой код, он откроет чьи-то исходники и ужаснется. У меня так было, когда в институте учился. Вроде сидишь, учишь, учишь, по предмету пятерки, курсовые сдаешь, а открываешь «реальный» код, и ничего понять не можешь. У меня вообще такое чувство, что я тот же C# раза четыре выучил, на самом деле. Вообще, любой язык сейчас, приходится учить, минимум дважды. Сначала, ты изучаешь, как на нем писать, в принципе, а потом изучаешь, как писать принято/правильно. Если язык более универсальный, то потом ты еще узнаешь что этих самых «принято/правильно» много разных, в зависимости от области применения. Вот вам и третья итерация изучения. А потом еще идет четвертая — изучение мощного, навороченного фреймворка, под конкретную задачу.
В этом плане, JS переплюнул всех, на мой взгляд. Выучил JS, а как же JQuery? А теперь изучи десять разных способов объявления объекта, и пять способов реализации наследования. Вот теперь ты готов начать изучать язык по настоящему. Angular, React… Постой, постой, зачем ты изучаешь первый ангуляр? Уже пора второй. Хотя знаешь, первый тоже пригодится. В итоге приходишь на работу устраиваться, и тебе говорят: «Ну знаете, пока вы все это учили, вышло пять новых фреймворков, каждый из которых, как отдельный язык, поскольку каждый предполагает свою философию, и написание кода в своем собственном стиле. А еще появилось три новых стандарта языка, и половина тех функций, старых фреймворков, что вы учили, больше не нужны.»
Проблема, на мой взгляд в том, что на работе, в каждый момент времени, требуется знание конкретного фреймворка, но чтобы до него добраться, надо сначала хорошо изучить основной язык, со всем его многообразием и многоликостью, а потом уже только фреймворк. Да еще и разные фреймворки, выполняющие схожие функции, имеют в качестве основы разные языки. Если бы роль фреймворка выполнял простой, специализированный язык, то нужно было бы выучить только его, и он был бы даже проще обычного языка, поскольку не такой универсальный. Особенно это бы чувствовалось, при изучении нескольких фреймворков. Например для изучения asp.net, django и rails, не требовалось бы учить C#, Python и Ruby. А может и вовсе не было бы такого многообразия фреймворков, ведь основное их отличие в том, какой язык положен в основу.
Ну а особенности предметной области учить придется в любом случае, будь то язык или фреймворк. Это мы никак не упростим.
Ведь написать язык это еще сложнее, чем свой фреймворк сделать.
Вы пробовали? Сделать «большой», «универсальный» язык — да, это непросто, но, скажем, JavaScript был написан одним человеком за две недели.

Такое осилят только очень крупные компании, и в условиях, когда языки легко между собой интегрировать, их языки быстро перейдут из разряда «своих», в разряд общеупотребимых.
Если бы. Вы про какой-нибудь REXX или что-нибудь подобное — много слышали? А с языком 1С общались? А они, в общем, довольно крупные компании.

А если кто-то и будет клепать проприетарные языки, похожие на уже существующие, то разве сложно выучить язык, если он простой, и похож на уже существующий, который ты знаешь?
Как показывает практика — сложно. Да, есть люди, которые их осваивают легко, но, вот беда, они и C++ и C# целиком «охватить» могут, так что для них в «маленьких» языках смысла нет, а «ремесленники» так и пользуются каким-нибудь PHP и в вашу нирванну с сотнями «маленьких» языков их не тянет.

То есть вы считаете, что система, которую я описал, когда два джуна, хорошо знающих каждый свой небольшой язык, успешно справляются, каждый со своими небольшими задачами, и параллельно друг друга учат и сильнее погружаются в процесс — не сработает? Можете объяснить почему?
Потому что нет никаких «двух джунов» в реальном мире. Есть сеньоры, которые «ведут» проект и есть джуны, которым приходится изучать то, что сеньоры использовали, когда его создавали. Если у вас джуниор и один из них окажется с хорошим знанием нужного мини-языка — то вам крупно повезло. А в большинстве случаев они будут знать кучу разных других языков ни один из которых им не разрешат использовать — так как они незнакомы сеньору.

Ну знаете, пока вы все это учили, вышло пять новых фреймворков, каждый из которых, как отдельный язык, поскольку каждый предполагает свою философию, и написание кода в своем собственном стиле. А еще появилось три новых стандарта языка, и половина тех функций, старых фреймворков, что вы учили, больше не нужны.
Ну вот, собственно, «маленькие» языки имеют ту же самую проблему — но ещё дополнительно усугубленную тем, что ни IDE, ни всяких Resharper'ов под них нет.

P.S. Стек веб-технологий — это, на самом деле, довольно забавный зверёк. Вот там описанная вами картина как раз действительно реализована (исторически так сложилось: Netscape выиграла со своим JavaScript'ом на клиенте, но проиграла на сервере, а потом ещё и Microsoft подсуетился и вместо простого и логичного JSSS буквально «продавил» через W3C «огнедышащий» CSS) — но все изо всех сил пыжатся сделать так, чтобы всё «снизу доверху» было на одном языке. Как с одной стороны (все эти Node.JS), так и с другой (вещи типа GWT)… Пока не выходит — но сремление очевидно. Именно потому что «стек технологий» облегчает жизнь джуниору, но усложняет жизнь работодателю, а кто платит — тот заказывает музыку.
>А в большинстве случаев они будут знать кучу разных других языков
Им всего-то надо знать хотя бы один нужный язык, чтобы принести пользу со старта. В крайнем случае, джун сможет быстро выучить нужный язык, и придти устраиваться через неделю.
>Ну вот, собственно, «маленькие» языки имеют ту же самую проблему
Не совсем так. В случае с JS фреймворками, они основаны на JS, который сложнее, каждого из этих фреймворков, и его тоже нужно выучить.
>Вы про какой-нибудь REXX или что-нибудь подобное — много слышали? А с языком 1С общались?
А что не так с REXX? Я так и писал: «в условиях, когда языки легко между собой интегрировать». Когда он поддерживался операционной системой и другими языками, читай, его легко было интегрировать, он был популярен. Сейчас он особо не поддерживается, во всяком случае «из коробки», поэтому о нем забыли. Логика как раз соблюдается. Современной аналогией можно считать регулярные выражения. Несмотря на брэйнфаковый синтаксис, они очень популярны, потому что поддерживаются во многих языках, т.е. легко интегрируемы.
Ну а 1С, он не интегрируется с другими языками, на сколько мне известно. И очень простым его не назовешь.
>Именно потому что «стек технологий» облегчает жизнь джуниору, но усложняет жизнь работодателю, а кто платит — тот заказывает музыку.
А чем именно он осложняет жизнь работодателю? Я тогда вообще ничего не понимаю. Одни работодатели плачутся, что не могут найти себе rockstar c++, другим не нравится система, упрощающая вход для джуниоров. А что же им тогда нужно? Чтобы проект уровня Facebook, могла сопровождать уборщица с минимальным окладом?
Им всего-то надо знать хотя бы один нужный язык, чтобы принести пользу со старта.
И каков шанс, что они будут знать один из тысяч языков? А ведь если мы говорим про маленькие, «специализированные» языки, то их, скорее всего, будут тысячи…
Не совсем так. В случае с JS фреймворками, они основаны на JS, который сложнее, каждого из этих фреймворков, и его тоже нужно выучить.
Да — но JS описан в сотнях книг, обучается на куче курсов, тренинги и прочее. А «маленький» язык придётся учить по, скорее всего, посредственному описанию разработчиков с путанными примерами.
А что же им тогда нужно? Чтобы проект уровня Facebook, могла сопровождать уборщица с минимальным окладом?
Ну это было бы совсем идеально, но в реальном мире приходится довольствоваться выбором технологий, которыми владеют сотни тысяч потенциальных кандидатов, а не нишевые решения.
Современной аналогией можно считать регулярные выражения. Несмотря на брэйнфаковый синтаксис, они очень популярны, потому что поддерживаются во многих языках, т.е. легко интегрируемы.
Регулярные выражения это всё-таки не язык программирования. Так мы и мы всякие вещи типа lex/yacc, make и sed в «языки программирования» запишем. Если вас такой подход устраивает — то да, подобные вещи используются регулярно. Именно потому что они гораздо меньше, чем полноценные языки, ближе к фреймворкам.

В любом случае мой посыл прост: несколько полноценных языков стараются не использовать не потому, что их сложно интегрировать, а потому что специалистов сложно найти. Сколько народу используют какой-нибудь SciPy? И сколько из них при этом пишут на Фортране?
>И каков шанс, что они будут знать один из тысяч языков? А ведь если мы говорим про маленькие, «специализированные» языки, то их, скорее всего, будут тысячи…
Вот оно, теперь мне стало ясно, в чем мы друг друга не поняли. Я не предполагал такое количество языков, и что они будут создаваться вообще на каждый чих. Я предполагал, что в виде отдельных языков будут отражены основные парадигмы программирования, и что эти языки можно будет каким-то стандартным образом совмещать. Языки эти будут более-менее чистыми, без излишеств. А так же будут более специализированные языки. Под специализированными языками, я понимал, например, языки для написания бэкенда, для работы с базами данных (на стороне самой БД уже есть SQL, но хотелось бы иметь стандартизированный слой для работы с БД, на стороне клиента, то что сейчас каждый язык делает по своему), для работы с текстом (те же самые регулярки), и т.д. Причем суммарно, разнообразие не увеличится, а уменьшится. То, что существуют регулярки для обработки текста приводит к упрощению, а не к усложнению. Ну и что, что надо выучить еще один «язык»? Зато потом можно применять его везде, а не изучать кучу методов для такой обработки в каждом языке. То же и с SQL. Было бы фигово, если бы каждый язык предоставлял свой синтаксис для запросов к БД. Какие еще вещи выделятся в отдельные языки, я не знаю. Может работа с сетью, а может обработка файлов. В итоге жизнь сама рассудит. Это еще один важный момент, я не призывал к созданию кучи языков, как к самоцели. Я лишь предположил, что это вероятно станет следствием создания единого стандарта, для взаимодействия различных языков программирования друг с другом. И вот это именно то, что я предложил — создать такой стандарт. На мой взгляд, это очевидно приведет к декомпозиции в сфере языков программирования, а вот в какой именно форме это выразится, я не уверен. Возможно будет как я предположил — сильная декомпозиция, а может появится несколько более простых диалектов C# (и других языков), для разных целей. Даже по этой теме мы видим, что все споры в основном сводятся к тому, что один человек не понимает, зачем нужна та или иная особенность языка, а другой ему отвечает, что это применяется там, здесь и еще вот тут. Так может и стоит разделить все эти вещи по разным языкам, раз уж это разные сферы применения? Сейчас этого не делается, чтобы была возможность совместить разный функционал при необходимости. Вот такую возможность и предоставит стандарт взаимодействия языков. А может и этого не будет, а просто будут использовать это для того, чтобы решить проблему обратной совместимости и не тянуть огромное легаси старых версий языка. Да даже если только одна эта проблема будет решена, я буду на седьмом небе от счастья. Чтобы не было больше таких вещей, как модификатор «private protected» в C#, или фигурные скобки вместо присваивания в C++.
1. Человеко-понятный синтаксис LINQ. Достаточно было бы остановиться на fluent-стиле.
var ajustedInstants = from instant in instants
              join daylightTransition in daylightTransitions on TimeZoneId equals daylightTransition.TimeZoneId into nr
              from scheduleTransitions in nr.DefaultIfEmpty()
              where scheduleTransitions == null || (instant >= scheduleTransitions.StartDate && instant <= scheduleTransitions.EndDate)
              select scheduleTransitions != null ? instant.Add(scheduleTransitions.Delta) : instant;

var ajustedInstants = 
instants
.GroupJoin(daylightTransitions, instant => TimeZoneId, daylightTransition => daylightTransition.TimeZoneId, (instant, nr) => new {instant, nr})
     .SelectMany(t => t.nr.DefaultIfEmpty(), (t, scheduleTransitions) => new {t, scheduleTransitions})
     .Where(t => t.scheduleTransitions == null || (t.t.instant >= t.scheduleTransitions.StartDate && t.t.instant <= t.scheduleTransitions.EndDate))
     .Select(t => t.scheduleTransitions != null ? t.t.instant.Add(t.scheduleTransitions.Delta) : t.t.instant);

ИМХО первое читабельнее в разы. Так что не согласен.

Анонимные типы — это мегафича, когда не надо лепить новый тип ради единичного использования. Это невероятный плюс.

var никак не мешает читабельности нормального кода даже без решарпера. Бывает что без var хрен прочитаешь. Если код с var не потятен — это плохой код.

Импорт статиков — очень удобная фича в определенных местах.

Остальные выводы — это даже не смешно, это грустно, что автор не знает плюсов работы с кортежами, паттерн матчингом.
Для практики рекомендую написать например LDAP клиента или имплементировать еще какой протокол, и вот тогда и кортежи, и паттерн матчинг, и деревья выражений заиграют новыми красками.

Язык не наживает лишние фичи — он эволюционирует. И это как раз выгодно отличает его от Java и позволяет писать лаконичный и выразительный код. Языки, которые не эволюционируют — трупы.

dynamic кстати прекрасно используется в Dapper как и анонимные типы. Даже очень активно. Никто не умер, а код лаконичный и читабельный.

UFO just landed and posted this here
Ну как бе для List есть scores.ForEach(score => { /* foreach body */ });
А экстеншен свой дорисовать к любой коллекции не сложно.
UFO just landed and posted this here

То есть нормально, что работает вывод типов для score, а для scores надо явно его выписывать?
Если определение scores находится на другом экране?

UFO just landed and posted this here
Я просто то же смотрел на Kotlin, но меня не покидало чувство дежавю… ;)
К слову про foreach есть хорошая статейка (небольшая, но качественная). Так что не все так однозначно.
Так что либо в котлине вообще нет обычных циклов (и все пишут через лямбды), либо они наступили в описаные в статье грабли.
UFO just landed and posted this here
Эрик Липтер сравнивал dynamic с раковой опухолью языка. Думал, вы больше по нему пройдетесь.
Да, очень опасная (за счет потери плюсов от статической типизации) возможность. но она вроде бы не столь часто встречалась мне в коде.

Опять-таки, ограниченность личного опыта. Но после того, как (ткнули выше) увидел, что в Dapper использование dynamic это вполне себе обычный подход… Даже не знаю, что сказать.

Dynamic вводился для поддержки взаимодействия со скриптовыми языками. При правильном использовании (т.е., отсутсвие его использования в чисто C#-коде) он не должен доставлять особых хлопот. Но похоже, что все, что если что-то можно использовать не так как задумано и это сократит хоть на немного время написания (именно написания, а не отладки и поддержки) программы, будет использовано именно так.
Lazy парсинг недетерменированных данных Json и Xml. Очень удобно. Для всего есть свои применения. И говорить что это только для скриптов — совершенно некорректно. Надо просто понимать что и когда используется и некоторые фичи допускать для использования только опытными разработчиками, которые могут из них выжать по максимуму.

Хм, ну и как в этой задаче поможет dynamic? Я пока что не вижу никаких преимуществ перед XLinq...

Вот в этой строчке вся ленивость парсинга теряется:


element = XElement.Load(filename);
Конкретно это не пример ленивой загрузки. Это пример обхода xml через динамические аксессоры.
dynamic parser = new DynamicXmlParser(@”.\order.xml”);
Console.WriteLine(parser.customer.name);

Сделать ленивый парсинг и тд — лишь дело техники, используя TryGetMember.
Для тех кто не понимает, посмотрите например на Ruby. Ленивых парсеров навалом и подцепить их в удобный апи дело техники.
А потом еще и Safe Systems Programming.
Через аксессоры можно сделать эффективный парсер и даже близко не прикасаться к LINQ при этом.
К вышесказанным аргументам по поводу «SQL подобной» формы LINQ, хочу добавить пример Sprache ( habrahabr.ru/post/127642 ), позволяющий писать парсеры в подобном стиле:

public static Parser Question =
from at in AnswerTypeIndicator.Or(Parse.Return(AnswerType.Text))
from id in Identifier
from prompt in QuotedText
select new Question(id, prompt, at);

Писать парсеры в таком виде — исключительно удобно и понятно
Выглядит в принципе вполне читабельно (чисто), но:

1. from и select по смыслу отличаются от их обычного их применения для последовательностей. Соответственно, необходимо будет хоть немного, но привыкать и переводить их к новой семантике использования.

2. Никто не мешал реализовать конструктор парсера, который также относительно чисто будет работать во fluent-стиле (я сейчас набрасываю от балды, просто как идею):

public static Parser<Question> Question =
  ParcerBuilder
    .Clause(AnswerTypeIndicator.Or(Parse.Return(AnswerType.Text)))
    .Clause(Identifier)
    .Clause(QuotedText)
  .Build();


Для меня такой синтаксис также вполне понятен, а семантика соответствует тому, что ожидается от подобных конструкций. Чуть-чуть напрягает, что удобочитаемость таких конструкций зависит от размера и удачного форматирования кода — но это же относится и ЧПС LINQ и многому другому.

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

Мне интересно, а как по вашему люди пишут на нескольких языках сразу? Место в голове не заканчивается?:)

Шасн того что ваш синтаксис станет неудобочитаемым горахдо выше чем у vola. И форматирование вам не поможет.


Похоже, тут дело вкуса (привычки). Достаточно большие fluent-выражения, если они правильно отформатированы, обычно читаются неплохо. Но обычно я не довожу до многоэкранного монстра, а разбиваю на подвыражения. Я приведу один пример — не сочтите за издевательство.

В свое время (еще в школе) я понял, что мне достаточно легко читать текст вверх ногами. Было занятно развить это качество до такой степени, что читал в таком положении примерно с такой скоростью, как и в правильной позиции. Дальше развлекался тем, что ирретировал окружающих тем, что демонстративно читал, держа книгу вверх ногами. Ребячество конечно. Лучше бы занялся чем-то другим, но в школе мы выбрасывали тонны времени и не на такое.

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

Мне интересно, а как по вашему люди пишут на нескольких языках сразу? Место в голове не заканчивается?:)


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

Разработка — это борьба со сложностью. Именно на это направлены различные приемы и приемчики, методы, Code Agreements, ограничения.
Не только вкуса. Я вам приводил пример, где форматирование не поможет. Сокращение имен переменных сразу ухуджиш понимание процессов вычисления дат.
В зависимости от ситуации надо использовать тот или иной синтаксис. Сложные выборки/модификации данных Fluent синтаксис выразит хуже в плане читабельности. Есть простые случаи, где он будет эффективнее. Есть еще варианты миксов готовых предикатов и динамических — тогда Fluent будет удобнее

Есть фундаментальные принципы. Если вы работали с кортежами на Питоне, то использовать кортежи в другом ЯП будет не проблема. И как раз благодаря эволюции языка мы можем использовать приемы, которые были успешно опробованы и в других языках программирования.
Предикаты вижу, но где здесь собственно создание результата, и как это можно сделать? А с LINQ все очевидно
1) Человеко-понятный синтаксис LINQ. Достаточно было бы остановиться на fluent-стиле.
А мне вот недостаточно. О других тоже надо думать. Человеко-понятный синтаксис читабельней для человека. Что тут еще можно добавить? Вы мыслите как машина? Подумайте о других.

2) Анонимные типы.
Их область применения довольно узка. И возможностей для их злоупотребления немного. Но там где ини используются (тот же LINQ и какие то вещи, где хочется избежать отдельных деклораций классов), они сильно упрощают жизнь. Просто нужно понимать, что бывают такие типы, которые не имеют смысла за пределами контекста. И создавать для них классы избыточно. Это не «синтаксический сахар», а решение конкретной проблемы. Которая в вашей парадигме разработки по-видимому просто отсутствует. Но есть люди, кому это надо.

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

Избегание var в данном случае — самый настоящий антипаттерн. Которые приводит к плохому, с точки зрения самодокументирования, коду.

4) Импорт статиков — ухудшает читаемость кода.
Я так и не понял почему ухудшает. Я бы сказал, он может его ухудшать в каких то случаях. Но так как его еще написать надо, этот импорт. То чаще всего люди, которые это делают очень понимают для чего они это делают.
Sign up to leave a comment.

Articles