All streams
Search
Write a publication
Pull to refresh
93
0
Юрий @m36

User

Send message
«у вот у меня под рукой МакКоннел, в котором явно написано, что привлекательность табличных методов (а это, как мы уже выяснили, частный случай) зависит от сложности кода.»
Читал я МакКонела. Согласен с ним в данном случае. От языка это зависит. И от сложности кода и задачи. Так не будем и спорить, представляя себе крайние противоположные случаи.

«Если честно, я перестал понимать, что вы считаете данными, а что — кодом.»
Могу только интуитивные аналогии приводить. Разница между кодом и данными условна.
Вспоминаем паскаль. Там есть отличие между процедурами и функциями. Процедуры что-то делают, но значение не возвращают, функции что-то делают и возвращают. С, С++, шарп — проводят обобщение, добавляя тип 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 или не важно каком.

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

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

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

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

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

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

Эта фраза всё убила. Нет тестов — следовательно нет рефакторинга. Кстати, и не будет нормального рефакторинга, если нет тестов.

А если нет рефакторинга, то вообще никак не будет происходит не только преждевременное, но и своевременное обобщение.
12 ...
31

Information

Rating
Does not participate
Location
Киев, Киевская обл., Украина
Date of birth
Registered
Activity