И не только википедия. Я Макконела читал и вполне уважаю его взгляды. Но Макконала шарпистам надо, по моему мнению, читать критически. Некоторые его советы для нас просто вредны. Защитное программирование, например. Совет хороший для плюсов, для шарпа вреден.
Кроме Макконела есть и другие книги. Например, Харрисон «Функциональное программирование».
Потом, личный опыт. Потому как я два года работал профессионально (не просто увлекался), на одном из диалектов APL. Так что имею представления об этих вещах.
Хотя, конечно, тема обширная, информации много, а времени мало всё изучить.
«В частности, вы полностью куда-то теряете функциональное программирование»
Где я его потерял? Декларативное программирование — более общее понятие. Обычно включает в себя две парадигмы — функциональное программирование и логическое программирование. Деление достаточно условно. Потому как парадигмы напрямую не относятся к языкам, а только предрасполагают писать в том или другом стиле.
Функциональное программирование — довольно широкое понятие. В узком смысле — это языки, работающие со списками рекурсивно. Лисп, Хаскель, F# и т.д. В шикором смысле и APL и SQL — функциональные языки. Что не отменяет того, что они же и декларативные языки.
«Да ну? Слово «мама» и связанный с ним объект у вас вербально закрепляются? Не смешите меня.»
Да ну? А слово «mother»? У меня и слово «мама» не прошито ни в ДНК, ни в BIOS.
На счет реализации требований, так просто не стоит тут писать тонны кода. Но все слова в требованиях по сути являются либо существительными (классами), либо глаголами и либо другими частями речи. Естественный язык нечто моделирует. Эти все вещи можно моделировать и языком программирования. Язык программирования не беднее. Логика и способы моделирование с помощью естественного языка вообще-то намного беднее, чем языки математики. Языки программирования — это тоже языки математики. Не знаю, как доказывать эти очевидные вещи.
Императивное и декларативное программирование — термины. Не я придумал. И легко погуглить и убедиться, что это такое. Шарп является императивным языком. Условно. Его можно использовать и декларативно. Но он предрасполагает к императивному программированию, потому что сколько не инкапсулируй код в классы — основная работа проходит в методах, где последовательности, циклы и ветвления.
SQL — декларативный язык. На нем вы пишете «что» вы хотите, а не «как». В этом и мощь SQL. Задачи с помощью него решаются в десятки раз быстрее, ошибок при умении в десятки раз меньше, кода в десятки раз меньше, чем если бы вы писали в шарпе циклами и ветвлениями.
Я нигде фигню не написал. Аналогии привожу для того, чтобы вы поняли мысль. Не надо аналогии дословно воспринимать и искать в них частные несоответствия. Натуральный язык и язык программирования имеют количественные различия, но не качественные. Так что аналогии уместны. Мне, конечно, тяжело только с помощью натуральных языков объяснить, насколько они разные. Грамматики, логика, похожи. Но они не обязаны быть такими.
«Что» хочу и «Как» хочу — по смыслу разные вещи. Да, я встречал заказчиков, которые говорили: «хочу, чтобы в таблице была такая колонка, это мое требование». В результате были посланы. Я с такими людьми работать не хочу.
Также и «Проблема в том, что в моем примере код содержит информацию о том, _как_ решать задачу, и это и есть то, чего я хочу» — если это так, то это действительно проблема.
«Элементарные понятия не описываются, если что. Они элементарные, они просто закрепляются в понятийном аппарате. „
Корабли не плавают, они ходют (аналогия). Закрепление на понятийном уровне не происходит телепатически и невербально. Вот это закрепление и есть описанием.
Если код содержит алгоритм и решает задачу — то он императивный. Если код решает задачу, но в нем не сказано как решать задачу, а только что хотите — он декларативный.
Прямое или непрямое отражение — это субъективные вещи, связанные с культурой. Англичане могут удивляться, что вы прямое слово dog говорите непрямым словом «собака». А когда вы увидете свои требования на китайском, вообще удивитесь.
SQL — это отражение, что хочет пишущий от сервера. Но конечно, используя грамматику не русского языка, а грамматику SQL. Источники данных, связи и преобразованя — это декларативный способ описания «чего» вы хотите.
Естественный язык в этом плане далеко не лучше SQL. Если бы у вас осталась только грамматика языка, но язык не имел бы предопределенных понятий, то вам любое ваше требование пришлось бы долго «кодить». Сначала описать элементарные понятия.
«Описание задачи не может быть ее решением, вот в чем проблема.»
Один из примеров декларативных языков SQL. Вы в коде запроса описываете, что вы хотите. Вы не пишете последовательность шагов и не уговариваете сервер выполнять какие-то последовательности или условия. Поэтому запрос на SQL является описанием задачи и также и ее решением.
«Я еще раз спрашиваю — зачем нам знать, какие методы оно использует? Нам надо знать, _что_ оно такое. Оно — рента. Рента — это частный случай платежа.»
Ну да. Нам надо знать _что_оно_такое. Я ж о чем.
var renta = GetRent(...);
никак не говорит _что_оно_такое. Оно говорит: получаем методом ВзятьРенту нечто, чему дадим название «рента». В данном случае — рента — это не понятие пока, это слово. И мало ли, может программист был пьян, когда употребил это слово. Кодом ну никак не определяется. Имя переменной rent как и сама переменная, только здесь впервые появляется. Еще не было «пусть рентой будет Оплата, которая...». Еще этого не было. Все совпадения в фильме с реальными фактами — считать случайными.
«Весь ваш пример построен на том предположении, что вы знаете, что такое Payment»
Payment уже описывалось. Здесь Payment не впервые встречается. Здесь оно уже имеет суть. Я говорю о линейном восприятии. Если оно уже встречалось, значит скорее всего я уже знаю, что это такое. Если не знаю, то перейду на описание этого класса. И это сделать проще. И потом, если снова перейду назад, я не забуду, что рента, это какая-то оплата. Payment в данном случае — это гиперссылка. Понятие уже введено, но если кто не знает, перейти может.
«Если коротко, то потому, что код — это выражение решения задачи, а не терминов, в которых задача выражена.»
Это плохо. Восприятие кода как решения задачи — (имхо) влияет плохо и на процесс кодирования и на архитектуру и всё остальное. Если воспринимать код, как описание задачи — то код будет приближаться к описанию и будет более декларативным. Что и значит — он будет более прямо выражать суть задачи, будет более коротким, более надежным и т.д. и т.п.
«Потому что код не способен выразить «аренда — это процесс использования чужого имущества» и «ИНН — это уникальный номер, присваиваемый налогоплательщику таким-то органом».»
Почему это не способен? Если он не способен выразить мысль, то он не способен и предоставить решение. Разве если есть такие утверждения, то вы вообще не способны закодить такие требования? Очевидно, что способны.
Код условно можно разбивать на две части — декларативную и императивную. Декларативная часть описывает «что», императивная «как». Есть языки, которые только «Что» описывают и это прекрасно.
На вскидку решение задачи двух ваших утверждений. В реляционной БД можно задать таблицы и связи — декларативный способ. Что такое эти связи — это по сути ограничения. Можно взять и создать сто таблиц со ста колонками каждая, тип которых любой (varbinary) и просто пронумеровать, не ставить связи. Очень гибкий вариант, очень большое N-мерное пространство и все точки в нем возможно выразить такими таблицами. Наша задача, удовлетворять требования, т.е. ограничить точки и придумать способ их интерпретации. Способ интерпретации — даем таблицам осмысленные имена, ограничиваем типы колонок нужными типами атрибутов, даем имена. Простраство вариантов сократилось и уже более лучше отвечает задачи. Добавляем связи между сущностями — и еще сократилось. Вот уже довольно неплохо у нас описана предметная область. Имущество, процесс и т.д. (я бд для примера привел, можно и в шарпе такое же сделать). Далее приступаем к недекларативной части и кодируем возможные переходы между состояниями (точками). И если не удалось ограничить типами, проверяем местами на неверные точки и выбрасываем эксепшины.
И вот у нас код выражает требования «аренда — это процесс использования чужого имущества». И т.д. Императивная часть кодирования — похожа на доработку напильником. Иногда из паровоза получаем вертолет.
Такое представление о кодировании, кстати, показывает, что лучше не кодом, а структурами данных выражать предметную область. Т.е. подбирать подходящие типы, которые не позволяют программе быть в запрещенных точках. А также каждая переменная по возможности должна быть независимой от других (ортогональность осей).
Язык программирования — это прежде всего язык. Также, как например, неверно воспринимать математику, как набор формул для решения задач — это в первую очередь язык описания более сложных и точных мыслей. Шарп — это язык более точного описания мыслей, чем естественный язык. И многие говорят, что программы пишут в первую очередь для того, чтобы их читали.
Языки стремятся сделать декларативными. Нас силой заставляют, потому как не захотели мы с рождения на лиспах писать. Декларативные языки не описывают алгоритмы, а описывают задачу «Что делать», а не «как делать». Конечно, это пока красиво в теории, но на практике даже функциональные языки далеки от естественных в простоте описаний.
Но ничего, наработки накапливаются. Всё таки надеюсь, что мы еще застанем такие языки программирования. А сейчас, пока их нет, у нас большой диапазон способов, как использовать шарп, например. Его можно хоть циклами весь залепить, хоть лямбдами, хоть классами. Я предлагаю (и не только я), рассматривать его как язык и отталкиваться в написании кода от критерия ясности кода. как если бы мы просто были переводчиками и напрямую переводили требования.
Пока вы правы — просто и прямо использовать классы как словарь терминов и язык описания требований — не всегда легко. Но стоит только подумать в эту сторону, наверное, сразу придумаете много способов это сделать.
Извините за многословность. Но тема такая. Религиозная. Чтобы объяснить идеи, приходится использовать очень далекие аналогии.
Чем отличается 1) собака от 2) собака. Первая состоит из 6 букв, вторая из четырех лап, хвоста и рычалки. Слово и объект, с которым оно связано — разные по сути вещи. И линейное восприятие информации — это последовательное присвоение имен (терминов) объектам или уже созданным терминам.
var rent = GetRent(...);
Говорит: нечто мы возьмем с помощью метода GetRent и назовем рентой. Здесь rent — это слово (привет кэпу). И здесь тоже:
Payment rent = GetRent(...);
Но Payment — уже не просто слово. Словом оно было здесь:
class Payment
{
}
Что читается, как «пусть Оплатой мы будем считать...». Мы в этом месте определили суть слова Payment. Присвоили имени суть.
В этом же месте впервые появляется новое слово:
Payment rent = GetRent(...);
И читается так:
мы получили оплату методом ВзятьРенту, которую назовем рентой в данном скоупе.
Этот код говорит:
var rent = GetRent(...);
Мы получили нечто методом ВзятьРенту и пока пусть будет нечто, позже станет понятно, какие методы оно пользует. Если непонятно, то посмотри в словарь. Если непонятно дальше, посмотри на интелисенс и перейди, посмотри класс.
Самым главным документом является код. Почему словарь (обязательный реквизит для чтения кода) написан в ворде? Что такое ворд? Оно компилится? нет. Оно отражает то, что делает приложение? нет. Чем вообще люди занимались, когда его писали???
Я утрирую. Но в общем, язык программирования — это язык описаний. Он иногда неуклюж, но языки движутся в эту сторону. Код — единственный наш друг, который не соврет. Любая документация (ЮМЛ, требования) — являются вторичными и необязательными. Они могут быть и могут дополнять. Но только код является первичным и обязательным. Требования можно писать тестами. Тесты — это утверждения. Чем не язык?
В общем в будущем думаю так и будет. Описали задачу, и она уже работает. Два раза не описывают. А пока, думаю, к этому надо стремиться. Хотя бы с помощью шарпа.
«Если вы не знаете, что такое рента, то вы не знаете business domain.»
Так вот код и есть та книжка, которую можно читать при некотором подходе к программированию. Языки стремятся в декларативную сторону, отвязаться от реализации. Поэтому код и есть документом, а не реализацией. (то, что вы защищаете). Поэтому я его хочу читать как книгу и даже если у меня есть пробелы в предметной области, хочу по максимуму понимать написанное в коде, а не в дополнительных документациях к коду. Требования к задаче и сама решение по возможности должны быть равны — и то и другое описание.
«А зачем вам это знание в дальнейшем коде?»
Для понимания. Я никак не могу вам объяснить, что типы — не только бывают с реализацией связаны, но и с описаниями. Мой мозг например, плохо работает, когда я читаю книгу, допустим по математике или физике, а там пишут формулой или термин, еще не встречавшийся и говорят: «а что это, мы узнаем в следующей главе». И оперируют на всю формулой или термином. Мозг отключается и абсолютно отказывается читать дальше.
Также и здесь. Я последовательно воспринимаю информацию. Очень плохо воспринимаю просто слово, а потом через куски кода можно понять, что это такое. Описание типа перед именем осознается, как привязка слова к некоторому объекту. Слово не висит в воздухе, ожидая, что я потом его свяжу с объектом.
«SomeInterface obj =
…
Это совсем другое. Это ничем не отличается от указания типа (собственно, интерфейс и есть тип).
»
Тут и я поспорю. Информационной ценности что там что там. Кстати, с выводами статьи Липпера вполне согласен. Я же сразу писал, что иногда использую var, когда действительно тип не имеет значения. Когда в запросе линькю меня никак не волнует, какой там список — кверибил или просто IEnumerator, потому что дальше я просто перебираю элементы или делаю поиск элемента. Кишки действительно не интересны и не нужны.
Но возвращаемое значение метода для меня не очень понятно по типу. Вот здесь тип уместен. Если это кастомный тип и отражает важную часть предметной области. Интелисенс — не выход. Я хочу читать код, а не клацать мышкой, чтобы найти что-то скрытое. Не считаю, что эта инфа лишняя, она элемент семантики. Иногда типы — это части реализации. Особенно, где нет кастомных. В С, например. int, float, double — какое мне дело, сколько он байт выделяет? Тут не надо. Но когда я должен знать, платеж это, или должник, или клиентБанка, то имен экземпляров не достаточно лично для меня.
Я интуитивно всегда за то, чтобы язык использовали, как лингвистическую вещь, которая описывает задачу, а не решение задачи. И вот, что получится, например:
var yesterdayRent = GetRent(DateTime.Now.AddDays(-1));
Рента — это тип Paymant. Откуда я из кода выше это знаю? Если делать перевод на наш язык, звучит примерно так:
Вчерашняя рента, которая является платежом. (тут меня осеняет, что это платеж, а значит даже не зная этого класса, я догадываюсь, что в нем есть атрибуты — кто платил, когда платил и т.д.).
В первом случае, да, допустим, я тоже понял слово рента. Но я не знаю, что это точно. Может быть это класс, содержащий распределение цен на жилье/офисы географически. А может быть и по времени средние цены. Я знаю только слово, которое меня приблизило к смыслу, но не дало мне понимания, что за тип и что с ним можно делать.
«Значит вы плохо себе представляете, что у вас происходит в этом куске кода.»
Конечно. У меня цель — прочитать чужой код. Я его плохо понимаю изначально и хочу понять, что он делает и зачем. О том и речь. Мы спорим о вкусах. По вашему получается, что заменив тип на слово var, код становится читабельнее. Вы думаете, что уменьшив информацию, вы убрали ненужную информацию — шум, которая отвлекает от сути.
А я считаю, что это в большинстве случаев не шум. Опыт у меня такой. Мне тяжелее читать чужой код, где кругом пользуются var. У нас есть любители. И даже мой код правят, меняя типы на var.
Цель написания кода — чтобы его читали. Вот, я один из народа, у которого есть личный негатив и трудности со чтением реального кода. Можно, конечно, поспорить, что имена плохие давали, что причина не в var, а в названиях экземпляров. Может быть. Но глаза мои не сломались бы, если бы перед хорошими именами экземляров были прописаны хорошие имена классов.
«И что, если вы напишете var вместо Платеж, то сможете сделать операцию, не принадлежащую платежу?»
Не смогу. Я и не спорю — статическая типизация не нарушается. Если я напишу var то я буду плохо понимать, что это. И мне придется больше времени въезжать, какую операцию я могу с именем сделать, а какую нет.
Хорошо, я почитаю статью, а то нехорошо как-то спорить без этого
«И пользователи тоже функции передают? И вы мне хотите сказать, что 6 и 7 — это не данные?»
Нет )) Не данные. Пользователи передают функции-константы 6 и 7. Это формальность, правда? Функциональные языки обобщают понятие функции, в результате чего можно данные рассматривать как функции. В результате обобщения появляются замечательные возможности.
«Передача делегатов в методы — это всего лишь передача делегатов в методы, тот или иной сценарий, тот или иной паттерн, если угодно»
Смотря что вы называете языком. Создавая делегаты, которые берут на вход делегаты, вы создаете возможность потом создавать код, но отвечающий спецификациям делегатов, что вам позволит ограничить действия тех, кто будет использовать ваш код — т.е. создать язык, которым можно будет что-то выражать в какой-то предметной области. Яркий пример — методы для коллекций, берущие на вход лямбды. Вы коллекциями вращать можете как хотите и у вас почти SQL. Это уже язык, в каком-то смысле. Добавив еще парсер и абстрактное дерево — получили и новый синтаксис — linq
Если тип int (т.е. тип, относящийся к реализации, а не предметной области), то не нужен. Всегда достаточно знать _роль_ объекта. Вопрос только в том, откуда вы это знаете. Кастомный тип — это и есть его роль (т.е. возможная работа над ним).
Я совершенно согласен, что нужно обращать внимание на семантику, а не реализацию. Но типы могут быть элементами семантики. Если я пишу программу для банка, то Платеж — это элемент предметной области. И тип, следовательно, тоже. И я согласен, что сам тип имеет меньшее значение, чем его конкретный экземпляр (которому дают имя). Поэтому я и против венгерской нотации. Но все же даже если у меня есть конкретный экземпляр Платежа, мне не достаточно знать, что это какая-то var рента. Это уточняет — рента — это конкретный Платеж. Но я не уверен, что это вообще он. А именно тип определяет, что я с этим объектом могу делать.
Тип — это тоже объект, грубо говоря — экземпляр метакласса. И если это не int или decimal, которые никак не относятся к предметной области, то эта информация является семантической. Это не «реализации». К семантике, как я понимаю, может относиться также и сам код, если его писать правильно (как переводчики, а не реализаторы бизнес-логики. Она не реализуется, а описывается в идеале).
Как и в естественных языках, для передачи семантики важны не только слова, которым вы дали свои имена, а также и местоимения, запятые и структура предложения.
Входные параметры тоже функции. В конце я специально вызвал последнюю функцию, чтобы увидеть результат (шарп такой). Но вот пример, как данные можно представлять функциями.
«Там есть и скрытое поведение, и безумные затраты на изменение поведения программы»
Это смотря кто как пишет. Обычно шарп программисты очень плохо к SQL относятся, потому что слабые в нем специалисты. А если еще плохо транзакт использовать и тригеры, то можно вообще написать что-то жуткое и страшное. То же и к шарпу относится. Если человек плохо на шарпе пишет, может написать очень плохой код. В реляционных БД есть свои и немалые плюсы, вот я описал, на что обращать внимание. Та же логика относится, например, к linq. Передача делегатов в методы позволяет создавать свои языки.
«А главное — в вашем примере отчетливо понятно, что Вася — это Человек»
Для меня не отчетливо. Я же написал ниже: имена дают и свиньям. И собакам. А может быть кот Вася. И это разные типы объектов, не обязательно имеющие общего предка. Конечно, если Вася — тип человек, а человек имеет метод «Платить», то любой его потомок — Мужчина, Женщина, ПлатящийЧеловек — имеют метод «платить».
А если нет общего предка, то ничего сказать нельзя. Выражать семантику именами — хорошо и правильно. Я только о том, что не только именами нужно выражать семантику.
«var Вася = новый Человек();
Человек Вася = новый Человек();»
Я изначально говорил, что в таком примере я не имею ничего против var. Тут явно вызов конструктора. А если ссылку на Васю получают вызовом метода, то в этом случае остается только надеяться, что Вася — правильное и понятное имя, выражающее тип (я уже говорил, могут быть и свиньи), а также имя метода — правильное и понятное имя, подразумевающее точно, что у нас возвращается ровно тот тип, который, как мы подозреваем из имени, есть у Васи.
Две вещи подряд, которые не проверяются компилятором и условны. Произведение вероятностей — вероятность правильного ответа падает. Вероятность бага — растет.
Сомневаюсь, что это беда. Важность оптимизации очень сильно переоценена.
Еще не разу не сталкивался с критическими тормозами, потому что Dictionary использовал. Оптимизация — это процесс, который проводится после написания кода, который проходит в определенном порядке. Вначале замеры производительности, выбор узких мест, поиск оптимальных решений для узких мест.
Узких мест почти всегда два-три. Где-то цикл, в нем цикл, а в нем самый внутренний цикл, где в сумме программа проводит почти всё время. Вот там и оптимизируют. И если код уже написан, а там замеры показали — тормозит Dictionarу, то нет проблем из него сделать switch.
А так, просто, на вскидку, ни разу не помню, чтобы Dictionary был в чем-то виноват.
ru.wikipedia.org/wiki/%D0%94%D0%B5%D0%BA%D0%BB%D0%B0%D1%80%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5
И не только википедия. Я Макконела читал и вполне уважаю его взгляды. Но Макконала шарпистам надо, по моему мнению, читать критически. Некоторые его советы для нас просто вредны. Защитное программирование, например. Совет хороший для плюсов, для шарпа вреден.
Кроме Макконела есть и другие книги. Например, Харрисон «Функциональное программирование».
Потом, личный опыт. Потому как я два года работал профессионально (не просто увлекался), на одном из диалектов APL. Так что имею представления об этих вещах.
Хотя, конечно, тема обширная, информации много, а времени мало всё изучить.
Описание — частный случай моделирования. Нет?
Где я его потерял? Декларативное программирование — более общее понятие. Обычно включает в себя две парадигмы — функциональное программирование и логическое программирование. Деление достаточно условно. Потому как парадигмы напрямую не относятся к языкам, а только предрасполагают писать в том или другом стиле.
Функциональное программирование — довольно широкое понятие. В узком смысле — это языки, работающие со списками рекурсивно. Лисп, Хаскель, F# и т.д. В шикором смысле и APL и SQL — функциональные языки. Что не отменяет того, что они же и декларативные языки.
«Да ну? Слово «мама» и связанный с ним объект у вас вербально закрепляются? Не смешите меня.»
Да ну? А слово «mother»? У меня и слово «мама» не прошито ни в ДНК, ни в BIOS.
На счет реализации требований, так просто не стоит тут писать тонны кода. Но все слова в требованиях по сути являются либо существительными (классами), либо глаголами и либо другими частями речи. Естественный язык нечто моделирует. Эти все вещи можно моделировать и языком программирования. Язык программирования не беднее. Логика и способы моделирование с помощью естественного языка вообще-то намного беднее, чем языки математики. Языки программирования — это тоже языки математики. Не знаю, как доказывать эти очевидные вещи.
SQL — декларативный язык. На нем вы пишете «что» вы хотите, а не «как». В этом и мощь SQL. Задачи с помощью него решаются в десятки раз быстрее, ошибок при умении в десятки раз меньше, кода в десятки раз меньше, чем если бы вы писали в шарпе циклами и ветвлениями.
Я нигде фигню не написал. Аналогии привожу для того, чтобы вы поняли мысль. Не надо аналогии дословно воспринимать и искать в них частные несоответствия. Натуральный язык и язык программирования имеют количественные различия, но не качественные. Так что аналогии уместны. Мне, конечно, тяжело только с помощью натуральных языков объяснить, насколько они разные. Грамматики, логика, похожи. Но они не обязаны быть такими.
«Что» хочу и «Как» хочу — по смыслу разные вещи. Да, я встречал заказчиков, которые говорили: «хочу, чтобы в таблице была такая колонка, это мое требование». В результате были посланы. Я с такими людьми работать не хочу.
Также и «Проблема в том, что в моем примере код содержит информацию о том, _как_ решать задачу, и это и есть то, чего я хочу» — если это так, то это действительно проблема.
«Элементарные понятия не описываются, если что. Они элементарные, они просто закрепляются в понятийном аппарате. „
Корабли не плавают, они ходют (аналогия). Закрепление на понятийном уровне не происходит телепатически и невербально. Вот это закрепление и есть описанием.
Прямое или непрямое отражение — это субъективные вещи, связанные с культурой. Англичане могут удивляться, что вы прямое слово dog говорите непрямым словом «собака». А когда вы увидете свои требования на китайском, вообще удивитесь.
SQL — это отражение, что хочет пишущий от сервера. Но конечно, используя грамматику не русского языка, а грамматику SQL. Источники данных, связи и преобразованя — это декларативный способ описания «чего» вы хотите.
Естественный язык в этом плане далеко не лучше SQL. Если бы у вас осталась только грамматика языка, но язык не имел бы предопределенных понятий, то вам любое ваше требование пришлось бы долго «кодить». Сначала описать элементарные понятия.
Один из примеров декларативных языков SQL. Вы в коде запроса описываете, что вы хотите. Вы не пишете последовательность шагов и не уговариваете сервер выполнять какие-то последовательности или условия. Поэтому запрос на SQL является описанием задачи и также и ее решением.
Ну да. Нам надо знать _что_оно_такое. Я ж о чем.
var renta = GetRent(...);
никак не говорит _что_оно_такое. Оно говорит: получаем методом ВзятьРенту нечто, чему дадим название «рента». В данном случае — рента — это не понятие пока, это слово. И мало ли, может программист был пьян, когда употребил это слово. Кодом ну никак не определяется. Имя переменной rent как и сама переменная, только здесь впервые появляется. Еще не было «пусть рентой будет Оплата, которая...». Еще этого не было. Все совпадения в фильме с реальными фактами — считать случайными.
«Весь ваш пример построен на том предположении, что вы знаете, что такое Payment»
Payment уже описывалось. Здесь Payment не впервые встречается. Здесь оно уже имеет суть. Я говорю о линейном восприятии. Если оно уже встречалось, значит скорее всего я уже знаю, что это такое. Если не знаю, то перейду на описание этого класса. И это сделать проще. И потом, если снова перейду назад, я не забуду, что рента, это какая-то оплата. Payment в данном случае — это гиперссылка. Понятие уже введено, но если кто не знает, перейти может.
«Если коротко, то потому, что код — это выражение решения задачи, а не терминов, в которых задача выражена.»
Это плохо. Восприятие кода как решения задачи — (имхо) влияет плохо и на процесс кодирования и на архитектуру и всё остальное. Если воспринимать код, как описание задачи — то код будет приближаться к описанию и будет более декларативным. Что и значит — он будет более прямо выражать суть задачи, будет более коротким, более надежным и т.д. и т.п.
«Потому что код не способен выразить «аренда — это процесс использования чужого имущества» и «ИНН — это уникальный номер, присваиваемый налогоплательщику таким-то органом».»
Почему это не способен? Если он не способен выразить мысль, то он не способен и предоставить решение. Разве если есть такие утверждения, то вы вообще не способны закодить такие требования? Очевидно, что способны.
Код условно можно разбивать на две части — декларативную и императивную. Декларативная часть описывает «что», императивная «как». Есть языки, которые только «Что» описывают и это прекрасно.
На вскидку решение задачи двух ваших утверждений. В реляционной БД можно задать таблицы и связи — декларативный способ. Что такое эти связи — это по сути ограничения. Можно взять и создать сто таблиц со ста колонками каждая, тип которых любой (varbinary) и просто пронумеровать, не ставить связи. Очень гибкий вариант, очень большое N-мерное пространство и все точки в нем возможно выразить такими таблицами. Наша задача, удовлетворять требования, т.е. ограничить точки и придумать способ их интерпретации. Способ интерпретации — даем таблицам осмысленные имена, ограничиваем типы колонок нужными типами атрибутов, даем имена. Простраство вариантов сократилось и уже более лучше отвечает задачи. Добавляем связи между сущностями — и еще сократилось. Вот уже довольно неплохо у нас описана предметная область. Имущество, процесс и т.д. (я бд для примера привел, можно и в шарпе такое же сделать). Далее приступаем к недекларативной части и кодируем возможные переходы между состояниями (точками). И если не удалось ограничить типами, проверяем местами на неверные точки и выбрасываем эксепшины.
И вот у нас код выражает требования «аренда — это процесс использования чужого имущества». И т.д. Императивная часть кодирования — похожа на доработку напильником. Иногда из паровоза получаем вертолет.
Такое представление о кодировании, кстати, показывает, что лучше не кодом, а структурами данных выражать предметную область. Т.е. подбирать подходящие типы, которые не позволяют программе быть в запрещенных точках. А также каждая переменная по возможности должна быть независимой от других (ортогональность осей).
Язык программирования — это прежде всего язык. Также, как например, неверно воспринимать математику, как набор формул для решения задач — это в первую очередь язык описания более сложных и точных мыслей. Шарп — это язык более точного описания мыслей, чем естественный язык. И многие говорят, что программы пишут в первую очередь для того, чтобы их читали.
Языки стремятся сделать декларативными. Нас силой заставляют, потому как не захотели мы с рождения на лиспах писать. Декларативные языки не описывают алгоритмы, а описывают задачу «Что делать», а не «как делать». Конечно, это пока красиво в теории, но на практике даже функциональные языки далеки от естественных в простоте описаний.
Но ничего, наработки накапливаются. Всё таки надеюсь, что мы еще застанем такие языки программирования. А сейчас, пока их нет, у нас большой диапазон способов, как использовать шарп, например. Его можно хоть циклами весь залепить, хоть лямбдами, хоть классами. Я предлагаю (и не только я), рассматривать его как язык и отталкиваться в написании кода от критерия ясности кода. как если бы мы просто были переводчиками и напрямую переводили требования.
Пока вы правы — просто и прямо использовать классы как словарь терминов и язык описания требований — не всегда легко. Но стоит только подумать в эту сторону, наверное, сразу придумаете много способов это сделать.
Извините за многословность. Но тема такая. Религиозная. Чтобы объяснить идеи, приходится использовать очень далекие аналогии.
Чем отличается 1) собака от 2) собака. Первая состоит из 6 букв, вторая из четырех лап, хвоста и рычалки. Слово и объект, с которым оно связано — разные по сути вещи. И линейное восприятие информации — это последовательное присвоение имен (терминов) объектам или уже созданным терминам.
var rent = GetRent(...);
Говорит: нечто мы возьмем с помощью метода GetRent и назовем рентой. Здесь rent — это слово (привет кэпу). И здесь тоже:
Payment rent = GetRent(...);
Но Payment — уже не просто слово. Словом оно было здесь:
class Payment
{
}
Что читается, как «пусть Оплатой мы будем считать...». Мы в этом месте определили суть слова Payment. Присвоили имени суть.
В этом же месте впервые появляется новое слово:
Payment rent = GetRent(...);
И читается так:
мы получили оплату методом ВзятьРенту, которую назовем рентой в данном скоупе.
Этот код говорит:
var rent = GetRent(...);
Мы получили нечто методом ВзятьРенту и пока пусть будет нечто, позже станет понятно, какие методы оно пользует. Если непонятно, то посмотри в словарь. Если непонятно дальше, посмотри на интелисенс и перейди, посмотри класс.
Самым главным документом является код. Почему словарь (обязательный реквизит для чтения кода) написан в ворде? Что такое ворд? Оно компилится? нет. Оно отражает то, что делает приложение? нет. Чем вообще люди занимались, когда его писали???
Я утрирую. Но в общем, язык программирования — это язык описаний. Он иногда неуклюж, но языки движутся в эту сторону. Код — единственный наш друг, который не соврет. Любая документация (ЮМЛ, требования) — являются вторичными и необязательными. Они могут быть и могут дополнять. Но только код является первичным и обязательным. Требования можно писать тестами. Тесты — это утверждения. Чем не язык?
В общем в будущем думаю так и будет. Описали задачу, и она уже работает. Два раза не описывают. А пока, думаю, к этому надо стремиться. Хотя бы с помощью шарпа.
Так вот код и есть та книжка, которую можно читать при некотором подходе к программированию. Языки стремятся в декларативную сторону, отвязаться от реализации. Поэтому код и есть документом, а не реализацией. (то, что вы защищаете). Поэтому я его хочу читать как книгу и даже если у меня есть пробелы в предметной области, хочу по максимуму понимать написанное в коде, а не в дополнительных документациях к коду. Требования к задаче и сама решение по возможности должны быть равны — и то и другое описание.
«А зачем вам это знание в дальнейшем коде?»
Для понимания. Я никак не могу вам объяснить, что типы — не только бывают с реализацией связаны, но и с описаниями. Мой мозг например, плохо работает, когда я читаю книгу, допустим по математике или физике, а там пишут формулой или термин, еще не встречавшийся и говорят: «а что это, мы узнаем в следующей главе». И оперируют на всю формулой или термином. Мозг отключается и абсолютно отказывается читать дальше.
Также и здесь. Я последовательно воспринимаю информацию. Очень плохо воспринимаю просто слово, а потом через куски кода можно понять, что это такое. Описание типа перед именем осознается, как привязка слова к некоторому объекту. Слово не висит в воздухе, ожидая, что я потом его свяжу с объектом.
…
Это совсем другое. Это ничем не отличается от указания типа (собственно, интерфейс и есть тип).
»
Тут и я поспорю. Информационной ценности что там что там. Кстати, с выводами статьи Липпера вполне согласен. Я же сразу писал, что иногда использую var, когда действительно тип не имеет значения. Когда в запросе линькю меня никак не волнует, какой там список — кверибил или просто IEnumerator, потому что дальше я просто перебираю элементы или делаю поиск элемента. Кишки действительно не интересны и не нужны.
Но возвращаемое значение метода для меня не очень понятно по типу. Вот здесь тип уместен. Если это кастомный тип и отражает важную часть предметной области. Интелисенс — не выход. Я хочу читать код, а не клацать мышкой, чтобы найти что-то скрытое. Не считаю, что эта инфа лишняя, она элемент семантики. Иногда типы — это части реализации. Особенно, где нет кастомных. В С, например. int, float, double — какое мне дело, сколько он байт выделяет? Тут не надо. Но когда я должен знать, платеж это, или должник, или клиентБанка, то имен экземпляров не достаточно лично для меня.
Я интуитивно всегда за то, чтобы язык использовали, как лингвистическую вещь, которая описывает задачу, а не решение задачи. И вот, что получится, например:
var yesterdayRent = GetRent(DateTime.Now.AddDays(-1));
Рента — это тип Paymant. Откуда я из кода выше это знаю? Если делать перевод на наш язык, звучит примерно так:
Некая/нечто рента, взятая за вчера.
А так:
Payment yesterdayRent = GetRent(DateTime.Now.AddDays(-1));
Вчерашняя рента, которая является платежом. (тут меня осеняет, что это платеж, а значит даже не зная этого класса, я догадываюсь, что в нем есть атрибуты — кто платил, когда платил и т.д.).
В первом случае, да, допустим, я тоже понял слово рента. Но я не знаю, что это точно. Может быть это класс, содержащий распределение цен на жилье/офисы географически. А может быть и по времени средние цены. Я знаю только слово, которое меня приблизило к смыслу, но не дало мне понимания, что за тип и что с ним можно делать.
Но в данном вопросе мы разошлись во мнениях, что является семантикой.
Конечно. У меня цель — прочитать чужой код. Я его плохо понимаю изначально и хочу понять, что он делает и зачем. О том и речь. Мы спорим о вкусах. По вашему получается, что заменив тип на слово var, код становится читабельнее. Вы думаете, что уменьшив информацию, вы убрали ненужную информацию — шум, которая отвлекает от сути.
А я считаю, что это в большинстве случаев не шум. Опыт у меня такой. Мне тяжелее читать чужой код, где кругом пользуются var. У нас есть любители. И даже мой код правят, меняя типы на var.
Цель написания кода — чтобы его читали. Вот, я один из народа, у которого есть личный негатив и трудности со чтением реального кода. Можно, конечно, поспорить, что имена плохие давали, что причина не в var, а в названиях экземпляров. Может быть. Но глаза мои не сломались бы, если бы перед хорошими именами экземляров были прописаны хорошие имена классов.
Не смогу. Я и не спорю — статическая типизация не нарушается. Если я напишу var то я буду плохо понимать, что это. И мне придется больше времени въезжать, какую операцию я могу с именем сделать, а какую нет.
Хорошо, я почитаю статью, а то нехорошо как-то спорить без этого
Нет )) Не данные. Пользователи передают функции-константы 6 и 7. Это формальность, правда? Функциональные языки обобщают понятие функции, в результате чего можно данные рассматривать как функции. В результате обобщения появляются замечательные возможности.
«Передача делегатов в методы — это всего лишь передача делегатов в методы, тот или иной сценарий, тот или иной паттерн, если угодно»
Смотря что вы называете языком. Создавая делегаты, которые берут на вход делегаты, вы создаете возможность потом создавать код, но отвечающий спецификациям делегатов, что вам позволит ограничить действия тех, кто будет использовать ваш код — т.е. создать язык, которым можно будет что-то выражать в какой-то предметной области. Яркий пример — методы для коллекций, берущие на вход лямбды. Вы коллекциями вращать можете как хотите и у вас почти SQL. Это уже язык, в каком-то смысле. Добавив еще парсер и абстрактное дерево — получили и новый синтаксис — linq
Я совершенно согласен, что нужно обращать внимание на семантику, а не реализацию. Но типы могут быть элементами семантики. Если я пишу программу для банка, то Платеж — это элемент предметной области. И тип, следовательно, тоже. И я согласен, что сам тип имеет меньшее значение, чем его конкретный экземпляр (которому дают имя). Поэтому я и против венгерской нотации. Но все же даже если у меня есть конкретный экземпляр Платежа, мне не достаточно знать, что это какая-то var рента. Это уточняет — рента — это конкретный Платеж. Но я не уверен, что это вообще он. А именно тип определяет, что я с этим объектом могу делать.
Тип — это тоже объект, грубо говоря — экземпляр метакласса. И если это не int или decimal, которые никак не относятся к предметной области, то эта информация является семантической. Это не «реализации». К семантике, как я понимаю, может относиться также и сам код, если его писать правильно (как переводчики, а не реализаторы бизнес-логики. Она не реализуется, а описывается в идеале).
Как и в естественных языках, для передачи семантики важны не только слова, которым вы дали свои имена, а также и местоимения, запятые и структура предложения.
Над функциями. Ну, вот аналог на шарпе:
delegate int IntConst();
delegate IntConst IntOp(IntConst a, IntConst b);
IntOp plus = (a, b) => () => a() + b();
IntConst const6 = () => 6;
IntConst const7 = () => 7;
int r = plus(const6, const7)();
Входные параметры тоже функции. В конце я специально вызвал последнюю функцию, чтобы увидеть результат (шарп такой). Но вот пример, как данные можно представлять функциями.
«Там есть и скрытое поведение, и безумные затраты на изменение поведения программы»
Это смотря кто как пишет. Обычно шарп программисты очень плохо к SQL относятся, потому что слабые в нем специалисты. А если еще плохо транзакт использовать и тригеры, то можно вообще написать что-то жуткое и страшное. То же и к шарпу относится. Если человек плохо на шарпе пишет, может написать очень плохой код. В реляционных БД есть свои и немалые плюсы, вот я описал, на что обращать внимание. Та же логика относится, например, к linq. Передача делегатов в методы позволяет создавать свои языки.
Честно, пока нет. На работе развлекаюсь :)
Спасибо за статью, обязательно почитаю.
Для меня не отчетливо. Я же написал ниже: имена дают и свиньям. И собакам. А может быть кот Вася. И это разные типы объектов, не обязательно имеющие общего предка. Конечно, если Вася — тип человек, а человек имеет метод «Платить», то любой его потомок — Мужчина, Женщина, ПлатящийЧеловек — имеют метод «платить».
А если нет общего предка, то ничего сказать нельзя. Выражать семантику именами — хорошо и правильно. Я только о том, что не только именами нужно выражать семантику.
«var Вася = новый Человек();
Человек Вася = новый Человек();»
Я изначально говорил, что в таком примере я не имею ничего против var. Тут явно вызов конструктора. А если ссылку на Васю получают вызовом метода, то в этом случае остается только надеяться, что Вася — правильное и понятное имя, выражающее тип (я уже говорил, могут быть и свиньи), а также имя метода — правильное и понятное имя, подразумевающее точно, что у нас возвращается ровно тот тип, который, как мы подозреваем из имени, есть у Васи.
Две вещи подряд, которые не проверяются компилятором и условны. Произведение вероятностей — вероятность правильного ответа падает. Вероятность бага — растет.
Еще не разу не сталкивался с критическими тормозами, потому что Dictionary использовал. Оптимизация — это процесс, который проводится после написания кода, который проходит в определенном порядке. Вначале замеры производительности, выбор узких мест, поиск оптимальных решений для узких мест.
Узких мест почти всегда два-три. Где-то цикл, в нем цикл, а в нем самый внутренний цикл, где в сумме программа проводит почти всё время. Вот там и оптимизируют. И если код уже написан, а там замеры показали — тормозит Dictionarу, то нет проблем из него сделать switch.
А так, просто, на вскидку, ни разу не помню, чтобы Dictionary был в чем-то виноват.
Ошибся. Править нельзя. Конечно, константа — функция, которая ВСЕГДА возвращает одно и то же