Как стать автором
Обновить
92
0
Юрий @m36

Пользователь

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

Но в данном вопросе мы разошлись во мнениях, что является семантикой.
«Значит вы плохо себе представляете, что у вас происходит в этом куске кода.»

Конечно. У меня цель — прочитать чужой код. Я его плохо понимаю изначально и хочу понять, что он делает и зачем. О том и речь. Мы спорим о вкусах. По вашему получается, что заменив тип на слово var, код становится читабельнее. Вы думаете, что уменьшив информацию, вы убрали ненужную информацию — шум, которая отвлекает от сути.
А я считаю, что это в большинстве случаев не шум. Опыт у меня такой. Мне тяжелее читать чужой код, где кругом пользуются var. У нас есть любители. И даже мой код правят, меняя типы на var.

Цель написания кода — чтобы его читали. Вот, я один из народа, у которого есть личный негатив и трудности со чтением реального кода. Можно, конечно, поспорить, что имена плохие давали, что причина не в var, а в названиях экземпляров. Может быть. Но глаза мои не сломались бы, если бы перед хорошими именами экземляров были прописаны хорошие имена классов.
«И что, если вы напишете var вместо Платеж, то сможете сделать операцию, не принадлежащую платежу?»

Не смогу. Я и не спорю — статическая типизация не нарушается. Если я напишу var то я буду плохо понимать, что это. И мне придется больше времени въезжать, какую операцию я могу с именем сделать, а какую нет.

Хорошо, я почитаю статью, а то нехорошо как-то спорить без этого
«И пользователи тоже функции передают? И вы мне хотите сказать, что 6 и 7 — это не данные?»

Нет )) Не данные. Пользователи передают функции-константы 6 и 7. Это формальность, правда? Функциональные языки обобщают понятие функции, в результате чего можно данные рассматривать как функции. В результате обобщения появляются замечательные возможности.

«Передача делегатов в методы — это всего лишь передача делегатов в методы, тот или иной сценарий, тот или иной паттерн, если угодно»

Смотря что вы называете языком. Создавая делегаты, которые берут на вход делегаты, вы создаете возможность потом создавать код, но отвечающий спецификациям делегатов, что вам позволит ограничить действия тех, кто будет использовать ваш код — т.е. создать язык, которым можно будет что-то выражать в какой-то предметной области. Яркий пример — методы для коллекций, берущие на вход лямбды. Вы коллекциями вращать можете как хотите и у вас почти SQL. Это уже язык, в каком-то смысле. Добавив еще парсер и абстрактное дерево — получили и новый синтаксис — linq
Если тип int (т.е. тип, относящийся к реализации, а не предметной области), то не нужен. Всегда достаточно знать _роль_ объекта. Вопрос только в том, откуда вы это знаете. Кастомный тип — это и есть его роль (т.е. возможная работа над ним).

Я совершенно согласен, что нужно обращать внимание на семантику, а не реализацию. Но типы могут быть элементами семантики. Если я пишу программу для банка, то Платеж — это элемент предметной области. И тип, следовательно, тоже. И я согласен, что сам тип имеет меньшее значение, чем его конкретный экземпляр (которому дают имя). Поэтому я и против венгерской нотации. Но все же даже если у меня есть конкретный экземпляр Платежа, мне не достаточно знать, что это какая-то 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 был в чем-то виноват.
«Константа — это функция, которая не всегда возвращает одно и то же»!!!

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

«Если честно, я перестал понимать, что вы считаете данными, а что — кодом.»
Могу только интуитивные аналогии приводить. Разница между кодом и данными условна.
Вспоминаем паскаль. Там есть отличие между процедурами и функциями. Процедуры что-то делают, но значение не возвращают, функции что-то делают и возвращают. С, С++, шарп — проводят обобщение, добавляя тип void. Функциональный подход проводит еще большее обобщение — всё функции, данных нет. Т.е. функция — это значение, которое зависит от входных параметров. Константа — это функция, которая не всегда возвращает одно и то же. Любая программа — это функция — преобразование входных параметров в выходные. Поэтому нет данных. Или можно сказать, что функции — это данные. Список интов — это список из функций, например. Такой подход мощнее.

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

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

На счет аналогии, выше, попытаюсь еще раз на пример. Вы знаете C# и думаю, знаете SQL и реляционные СУБД. Можно сказать, что SQL — это хоть и не обычный, но функциональный язык. Когда вы моделируете предметную область на шарпе, вы вынуждены строить кирпичик за кирпичиком, дописывая код и скрывая еще в классах, вовне только видимые интерфейсы. Каждую программу можно представить, как структуру в N-мерном пространстве. Где N — это количество переменных. Каждая ось — это переменная. В шарпе программист пытается кодом ограничить точки в пространстве, в которых может находиться программа. Он пытается снизить нагрузку на свое внимание и допустить меньше багов с помощью инкапсуляции. И в каждом методе и классе он оперирует небольшим набором объектов и прописывает пути в н-мерном пространстве. Далее на каждом новом уровне он скрывает код, который пишется на более низком уровне. В результате он получает код вверху, который оперирует небольшим количеством объектов и может делать небольшое ограниченное число действий. Надежда, что этого достаточно для покрытия требований.

Что происходит в БД. Моделируются связи между данными и структуры данных декларативно. Уже после создания структуры БД смоделированы большинство зависимостей, которые не позволяют программе находиться в запрещенных точках в N-мерном пространстве. И есть мощный язык SQL, который позволяет почти без усилий делать любые преобразования, а структура БД следит за целостностью. Таким образом у нас с одной стороны нет скрытого поведения, с другой стороны затраты на изменения поведения программы минимальны.

Т.е. проблемы решаются не скрыванием кода, а наличием ограничений и выразительного языка.
Где-то так и в функциональном подходе. Вы создаете язык и ограничения, а потом пользуетесь сколько угодно гибко.
На семантика и правила именования идентификаторов компилятор не обращает внимания. Язык программирования можно разбить на две части — то, что имеет значение для компиляции и не имеет.
Задача программистов, как переводчиков — переводить требования в наиболее подходящий текст на языке программирования. И хорошо и правильно уделять внимание семантике, ставить ее на первое место. Но удерживать смысл только в недоступных компилятору местах — нехорошо. Семантика не только в именах, но и в конструкциях. Паттернах, коде. Старый пример. Хотите сделать обход коллекции, используете foreach, а не for. Это говорит читающему ваш код, что вам не важен порядок элементов в коллекции. Семантика также и в типах. Кстати, когда создаете тип, тоже даете имя — и это семантика. Тип — человек, экземпляр Вася.

var Вася
Человек Вася

Второй пример понятнее и более строгий. И тип здесь — элемент семантики. Ведь могло быть и:
Поросенок Вася.

Конечно, можно использовать правила именование:
var ВасяЧеловек;
Но такой подход дает меньше уверенности. И смысла всю семантику переносить в имена тоже нет.
Когда скрол прокрутится, да, видно не будет, что за тип. Точно также как и с var. Но с var его не видно при любом положении скрола.
Конечно. Я в курсе. Писал только о сути. А делегаты или указатели на функцию — это уже детали реализации.
Делегаты — аналоги указателей на функции в большинстве случаев.
В функциональных языках часто нет разницы между данными и кодом. Можно сказать, что данных нет — всё функции. Вполне общий подход.
Шарп не является функциональным языком. Но можно к этому подходить так: языки не связаны с парадигмами, они лишь в некоторой степени предопределяют стиль программирования на них.

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

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

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

«С моей точки зрения, это не «общая», а _частная_ рекомендация, применяемая в ограниченном числе случаев.»
Извините, не могу сейчас нарыть ссылку. Кто-то из великих рекомендовал. Причем рекомендации были для языка С. Что сути не меняет.

«Угу. Работающее в конкретных случаях, которых, по моему опыту, меньшинство. „
Это опять вопрос крайностей. Мощное средство потому и мощное, что с помощью его можно решать как простые задачи, так и сложные. А вот немощное средство сложные задачи не решает. Вопрос только в языке программирования, к чему он предрасполагает и какой код будет более запутанный и сложный для восприятия, а какой — прямое выражение мысли.
Конечно.

Вот только есть разница между разными типами. Например. Если вы определяете переменную — возраст человека, то важнее суть, а не тип — целый или decimal или временной интервал.

Или если linq выражение возвращает некую коллекцию, то не важно, какую именно. Главное что коллекция и понятен тип элементов.

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

Еще раз этот пример:
var result = Function.SomeMethod();

Непонятно. Но, конечно, я не придираюсь, это не реальный пример. В реальных будут адекватные названия. Но тогда нужны правила именования. Допустим, у нас стандартный пример: типы Figure, Rectangle, Circle.
И вот мы именуем метод:
var result = Function.GetLastCircle();

тип result семантически нам нужен, потому что он определяет круг операций с ним. GetLast — добавили, допустим, для конкретного метода, чтобы объяснить смысл. А вот слово Circle просто обязано быть в имени, чтобы мы поняли, с чем имеем дело. Это хорошо и понятно. Но это выполняет также замена слова var на тип.

Circle result = Function.GetLastCircle();

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

А по возможности реализовывать структурами данных, а не структурами кода — это общая рекомендация. Она не всегда уместна. Мы спорим о крайностях. Если нужен свич из 3-5 веток и больше не предвидится, то возможно это не нужно. А если и нужно будет, то можно будет переделать.
Но при равных условиях читаемости, вариант с данными предпочтительнее.

«А оно нужно кому-то?

Вот от этого вопроса и надо плясать. „

Конечно, только от этого и плясать. Я же описал варианты развития событий, когда оно может понадобиться.

И с нечитаемостью не всё очевидно. Заменять структуры кода на структуры данных и код представлять как данные — это мощное средство.
Вот этого всегда сторонился.
Например:
Dictionary<int,string> dic = new Dctionary<int,string>();
заменяется на:
var dic = new Dctionary<int,string>();
т.к. тут понятно, что мы ньюкаем.

А вот:
var result = Function.SomeMethod();
Не понятно. Хорошо компилятору, он умный. А я нет. Не хочу помнить наизусть, что SomMethod возвращает. И когда коллеги повально кругом этот var используют, мне сложнее в коде разобраться.

Может я не прав? Хочу узнать аргументацию против. Потому что я очень редко var использую. В случае, когда только действительно точный тип для восприятия не важен — результат линькью выражения, например.
Вполне нормально с читабельностью. На счет «поведение этого PerformOperation в один взгляд», то возможно, где-то сложнее. Но шарп, не тот язык, где код и данные — это одно и то же. И слышал где-то рекомендацию, что в императивных языках желательное, где это возможно, заменять структуры кода на структуры данных. Т.е. switch на Dictionary в данном случае. Чтобы не очень разносить заполнение словаря и метод, можно объявлять словарь и заполнять прямо в том же методе, где предполагался switch. При случае будет просто его вынести потом в поле экземпляра. Или еще обобщить — в статическое поле. Или еще в статическое свойство, которое кэширует изменения. И далее вообще сделать расширяющееся поведение в рантайме. Что нельзя было бы сделать с помощью switch

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

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

Во всех других языках есть существенный недостаток — их гораздо тяжелее поддерживать адекватными текущей реализации. Потому что самый честный товарищ, объясняющий, что делает код — это код. Во вторых это дублирование логики и по сути двойная работа. Так что лучше минимизировать комментарии, насколько это возможно, не теряя читабельность
Если писать тесты до написания кода, то всё станет через время на свои места. Они, да, сдерживают полет фантазии и обрезают крылья.
Но так и надо. Через время, такое сдерживание заставляет и к коду относиться иначе. Стараться писать его более кратко, более четко выражая мысль. А тесты по сути становятся для разработчика не тестами — а требованиями. Он уже интуитивно считает написание тестов — это написание требований, которым должен удовлетворять код. Тесты делают краткими и простыми. А когда проводят рефакторинг, как раз в этом и сила тестов — они падают. И их надо поправить.

Если бы их не было, то как-то стремно код менять. Что-то падает только в платоновском мире идей. А когда оно здесь упадет — неизвестно.

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

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

Потом, я переписал и перелопатил и изменил логику всего движок и еще одну важную систему в текущей конторе. Эта система работала уже. Любые сбои дорого стоят. И приятно, когда я два раза выкатывал практически на ходу — и всё бесшовно.

И на тесты не так много времени ушло. И на поддержку на самом деле не так много уходит.

Информация

В рейтинге
Не участвует
Откуда
Киев, Киевская обл., Украина
Дата рождения
Зарегистрирован
Активность