Проектирование и рефакторинг: по стопам Эвклида

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

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

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

    Предисловие


    Почему же стал на такой путь и пишу о столь фундаментальной, скорее всего всеми давно понятой, теме?

    Несколько причин.

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

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

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

    Кто же я такой? Меня зовут Джош, я из Харькова, мне 22, и я всё ещё Junior Software Developer. Наверное. Примерно год назад я уже публиковался на хабре, и на тот момент мои размышления на тему компонентно-ориентированного движка на C# были встречены не так плохо, как ожидал. Более того, публикация вырвалась из песочницы и какое-то время набирала просмотры. Но это так, знакомства ради, с которым я и без того затянул.

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

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

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

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

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

    Структура


    Метафора 1. Иерархично всё. Вселенную можно рассматривать как набор уровней различной степени приближения: кварки, атомы, молекулы, вещества, клетки, ткани… Не секрет, что само человеческое мышление принимает иерархичную форму, когда модули нижнего уровня складывают модули верхнего уровня, а потому вопрос о том, иерархична ли Вселенная или, всё-таки, человек, я оставлю в качестве философского упражнения пытливому читателю, ибо подобные размышления не касаются основной линии сюжета.

    Понятие 1.1. Задача — требование к функциональности приложения.
    Понятие 1.2. Блок — код, сосредоточенный вокруг выполнения одной и только одной задачи.
    Понятие 1.3. Зависимость — использование одним блоком кода другого.
    Понятие 1.4. Степень приближения — количество уровней, на которые необходимо подняться от атомарного, чтобы достигнуть данного.
    Понятие 1.5. Абстракция — блок, не имеющий определённой реализации на этапе компиляции.

    Функция 1.1. Apr(x) — степень приближения x.
    Функция 1.2. Qd(х) — количество зависимостей блока х.

    Положение 1.1. С течением времени количество блоков, из которых состоит программная система, увеличивается.
    Положение 1.2. Атомарным для программной системы является уровень базовых операторов и ключевых слов.
    Положение 1.3. Чем выше степень приближения абстракции, т.е. чем более общую задачу она призвана решать, тем меньше вероятность того, что появятся изменения.
    Положение 1.3.1. Зависимость от абстракции имеет меньшую вероятность привести к косвенным изменениям.
    Положение 1.3.1.1. Абстракции понижают энтропию.
    Положение 1.4. Избыточность порождает изменения.

    Процессы


    Понятие 2.1. Создание — увеличение количества блоков в приложении путём написания нового кода.
    Понятие 2.2. Изменение — отображение изменения формулировки задачи на блоки.
    Понятие 2.3. Косвенное изменение — отображение изменения блока на зависимые от оного.
    Понятие 2.4. Корректность — количественная характеристика проверки. Показывает, насколько точно и полно работает блок относительно выдвинутых пред- и постусловий.
    Понятие 2.5. Энтропия — количественная характеристика качества кода, показывающая, сколько дополнительного бюджета потребуется на внедрение нового функционала. Выражается через отношение между средним временем изменения и средним временем создания блоков.

    Функция 2.1. Tc(х, y) — время создания блока х в рамках задачи y.
    Функция 2.2. Tu(х, y) — время изменения блока х в рамках задачи y.
    Функция 2.3. Qu(х, у) — кол-во изменений блока х в рамках задачи у.
    Функция 2.4. Qm(х, у) — кол-во кос. изменений блока х в рамках задачи у.
    Функция 2.5. Md(x) — отображение из множества косвенных изменений блока x в множество тех, что приведут к реальным.
    Функция 2.6. Cor(x) — показывает степень корректности блока x, т.е. отношение между теоретическим результатом и фактическим. Можно формально определить как отношение количества элементов множества, формирующегося путём пересечения результатов работы ожидаемой функции с фактической, к количеству элементов множества результатов работы ожидаемой функции.
    Функция 2.7. Ku(х) — коэффициент хрупкости блока х. Отношение между количеством Md(х) к количеству косвенных изменений x.

    Положение 2.1. Новый код увеличивает энтропию.
    Положение 2.2. Изменения увеличивают энтропию.
    Положение 2.3. Косвенные изменения косвенно снижают корректность.
    Положение 2.3.1. Косвенные изменения могут привести к не косвенным.
    Положение 2.3.1.1. Косвенные изменения косвенно увеличивают энтропию.
    Положение 2.3.2. Проверочные блоки снижают степень влияния косвенных изменений на корректность.

    Организация


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

    Понятие 3.1. Переиспользование — использование одного и того же блока для решения одной и той же задачи во всех местах приложения.
    Понятие 3.2. Полиморфизм блоков — возможность подставлять блок реализации в абстракцию.
    Понятие 3.3. Наследование — переиспользование дочерним блоком структуры родительского.
    Понятие 3.4. Инкапсуляция — сокрытие внутренней структуры блока от блоков, его использующих.

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

    SOLID


    Напоследок я хочу взять на себя смелость теоретически обосновать применение наиболее популярных принципов — SOLID.

    Single Responsibility Principle — принцип единой ответственности. Формально звучит так: программный модуль или класс должен иметь только ответственность только за одну функциональную часть, предоставляемую приложением. У него должна быть “только одна причина для изменения” (Роберт Мартин). В нашей теоретической модели это напрямую вытекает из определения зависимости: как уже было показано выше, состояние, когда логически один блок верхнего уровня содержит два блока нижнего уровня, выполняющих разные задачи, но зависящие друг от друга, имеет большую энтропию, чем состояние без циклической зависимости.

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

    Liskov Substitution Principle — принцип подстановки Барбары Лисков. Говорит о том, что должна существовать возможность заменить все объекты типа T на объекты типа S, где S — подтип T, без ущерба корректности и работоспособности программы. На формальном языке это можно выразить так: пусть функция f(x) справедлива для всех x типа T, тогда функция f(y) должна быть также справедливой для всех y типа S, где S — подтип T. Данный принцип является следствием стремления уменьшить коэффициент хрупкости приложения и, к сожалению, не даёт никаких конкретных рекомендаций, а лишь постулирует требование к программной системе.

    Interface Segregation Principle — принцип разделения интерфейсов. Объявляет, что большое количество мелких интерфейсов лучше, чем один большой, т.к. клиенты, зависящие от интерфейсов, могут пользоваться только той их частью, которая им нужна.
    Могу заметить, что такой принцип является продолжением SRP, прикладываемым на область абстракций. Аргументы и доказательства абсолютно те же.

    Dependency Inversion Principle — принцип инверсии зависимостей. Для меня один из самых труднопонимаемых принципов, гласящий, что объекты высокого уровня не должны зависеть от объектов низкого уровня, и наоборот — оба уровня должны зависеть от абстракций. Логичность и истинность данного принципа напрямую следует из Положения 1.3.1.1: вероятность изменения абстракции ниже, чем вероятность изменения конкретного блока-реализатора.

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

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

    Практика


    В качестве упражнения рассмотрим реальный пример и попытаемся количественно проанализировать его структуру.

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

    public class DamageMediator : GameComponent
    {
      public int Next()
      {
        var equipment = GetEquipment();
        var stats = GetStats();
    
        var weapon = equipment.Weapon;
    
        var damage = weapon.Damage;
        var isCrit = stats.CriticalChance.Next();
    
        var result = isCrit ? damage.Next() + damage.Next() : damage.Next();
    
        return result;
      }
    }
    

    Посчитаем количество зависимостей.

    Блоки-функции: GetEquipment, GetStats, Equipment.Weapon, Weapon.Damage, Stats.CriticalChance, CriticalChance.Next, Damage.Next.
    Блоки-типы: Equipment, Stats, Weapon, Damage, Chance.
    Таким образом Qd = 12.

    Наша задача заключается в том, чтобы минимизировать это значение, снизив тем самым энтропию.

    1. Избавимся от GetEquipment и GetStats, перенеся их в параметры.

    public class DamageMediator : GameComponent
    {
      public int Next(Equipment equipment, Stats stats)
      {
         var weapon = equipment.Weapon;
    
         var damage = weapon.Damage;
         var isCrit = stats.CriticalChance.Next();
    
         var result = isCrit ? damage.Next() + damage.Next() : damage.Next();
    
         return result;
      }
    }
    

    2. Заменим Equipment на Weapon.

    public class DamageMediator : GameComponent
    {
      public int Next(Weapon weapon, Stats stats)
      {
         var damage = weapon.Damage;
         var isCrit = stats.CriticalChance.Next();
    
         var result = isCrit ? damage.Next() + damage.Next() : damage.Next();
    
         return result;
      }
    }
    

    3. Заменим Weapon на Damage.

    public class DamageMediator : GameComponent
    {
      public int Next(Damage damage, Stats stats)
      {
         var isCrit = stats.CriticalChance.Next();
    
         var result = isCrit ? damage.Next() + damage.Next() : damage.Next();
    
         return result;
      }
    }
    

    4. Заменим Stats на Chance.

    public class DamageMediator : GameComponent
    {
      public int Next(Damage damage, Chance criticalChance)
      {
         var isCrit = criticalChance.Next();
         
         return isCrit ? damage.Next() + damage.Next() : damage.Next();
      }
    }
    

    Таким образом теперь состояние блока: Qd = 4.

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

    Во-первых, уменьшая количество зависимостей, мы временно избавились от двух, казалось бы, непримечательных методов: GetEquipment и GetStats. Тем лучше для текущего примера — можно будет более детально рассмотреть возникающие проблемы. Оказывается, данные методы получали экземпляры экипировки и характеристик персонажа, используя систему компонентов: сам DamageMediator является GameComponent и по соглашению имеет доступ к ссылке на родительский GameComponentContainer (прошу меня простить, что приходится это выслушивать, но в моей первой статье есть разъяснения), соответственно, первоначальная версия кода предполагала, что компоненты экипировки и характеристик будут также находиться в контейнере.

    Во-вторых, на самом деле просчёт урона не ограничивается броском кости на критический удар. Что, если теперь появилось (на самом деле, было) условие: на окончательное значение будет влиять одна из базовых характеристик, например, сила или ловкость, причём это влияние будет определяться особенностью оружия?

    Справляемся с неудобствами


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

    Тем не менее, не могу не заметить, что, движимые рациональным стремлением уменьшить одну из количественных характеристик блока, мы, сами того не подозревая, сумели вывести на чистую воду проблему многозадачности компонента: он ранее не только считал значение урона, но и знал, как и откуда достать требуемые для просчёта сущности. Это мой недочёт, который я ранее не замечал.

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

    Сперва взглянем на то, что из себя в концептуальном плане представляет DamageMediator: его изначальная цель заключается в том, чтобы сокрыть взаимодействие с рядом компонентов (экипировка, оружие, характеристики), хранящихся в контейнере персонажа, дабы переиспользовать это поведение во всех местах, где потребуется рассчитать урон.

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

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

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

    Дабы сгладить проблему, вернём старую версию метода Next, не удаляя новую. В старой версии оставим только ту часть работы, которая ответственна за взаимодействие с иерархией компонентов. Таким образом получается нечто такого плана:

    public class DamageMediator : GameComponent
    {
      // Возвращаем первоначальное API.
      public int Next()
      {
        // Вернули два старых метода.
        var equipment = GetEquipment();
        var stats = GetStats();
    
        return Next(equipment.Weapon.Damage, stats.CriticalChance);
      }
    
      private static int Next(Damage damage, Chance criticalChance)
      {
         var isCrit = criticalChance.Next();
         
         return isCrit ? damage.Next() + damage.Next() : damage.Next();
      }
    }
    

    Что же, в конце концов, поменялось? Стало быть, энтропия, потому как количество зависимостей уменьшилось: если ранее существовала циклическая зависимость между блоком просчёта урона и блоком получения компонентов, сейчас осталась лишь зависимость от блока получения компонентов к блоку расчёта урона. Кроме того, уменьшилась вероятность изменения блока просчёта урона, т.к. его количество зависимостей, как помните, составляет Qd = 4.

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

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

    public class DamageMediator : GameComponent
    {
      public int Next()
      {
        var equipment = GetEquipment();
        var stats = GetStats();
    
        return DamageUtil.Next(equipment.Weapon.Damage, stats.CriticalChance);
      }
    }
    

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

    Итоги


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

    Мною была рассмотрена теоретическая модель, состоящая из трёх уровней: структуры, т.е. тех базовых понятий и положений, на основании которых базируются следующие уровни; процессов, т.е. тех действий и событий, которые так или иначе воздействуют на структуру; организации, т.е. различных способов взаимодействия базовых структурных блоков и их последствия.

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

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

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

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

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

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

    Всем спасибо за внимание и до скорых встреч!
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 119
    • +1
      Меня зовут Джош, я из Харькова

      Вас действительно так зовут, или вы сами себя так называете? Для Харькова немного необычно.
    • +2
      А вот эти Apr(x) и Qd(x) как-нибудь вычисляются, или их наличие обусловлено исключительно флером научности?
      • 0
        Вы совершенно правы. Сейчас думаю, что все эти функции совершенно излишни, т.к. далее я ничего с ними не делаю (хотя планирую в след. частях, если тема будет актуальной и появятся наработки). Они присутствуют в статье исключительно полноты ради, показывая, какие количественные вычисляемые характеристики имеет кодовая база.

        Как считаете, если удалю, никто не расстроится?)
        • 0

          А как вы можете вычислить Apr(x)?

          • 0
            public class Test
            {
              public void ThirdLevel()
              {
                SecondLevel();
              }
            
              public void SecondLevel()
              {
                FirstLevel();
              }
            
              public void FirstLevel()
              {
                Console.WriteLine("1");
              }
            }
            

            Из Положения 1.2. и определения Степени приближения следует, что Apr(ThirdLevel) = 3.

            Зачем это нужно? В работе не было явно указано и я не проводил прямых расчётов, но, скажем, данная величина показывает, на каком уровне находится блок, а следовательно, она является модификатором вероятности того, что данный блок изменится. Это утверждение напрямую следует из определения (хотя не такого точного, как я уже вижу, за что сразу спасибо), т.к. изменение на любом из уровней приведёт к косвенному изменению уровня ThirdLevel (если в контексте примера) и т.д.
            • 0
              Из Положения 1.2. и определения Степени приближения следует, что Apr(ThirdLevel) = 3.

              … это если считать, что "уровень" — это глубина вызова, а Console.WriteLine — атомарная операция.


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


              Попробуйте заменить Console.WriteLine на _writer.WriteLine, где _writer — поле типа TextWriter, и посчитать вашу метрику снова.


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

              А вот и нет. Если SecondLevel — это публично определенный API, то вероятность изменения ThirdLevel складывается из вероятности изменения требований к ThirdLevel и вероятности изменения API SecondLevel, и вам совершенно не важно, сколько уровней внутри SecondLevel (собственно, вы этого и не знаете). Да здравствует инкапсуляция.

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


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

                А вот и нет. Если SecondLevel — это публично определенный API, то вероятность изменения ThirdLevel складывается из вероятности изменения требований к ThirdLevel и вероятности изменения API SecondLevel, и вам совершенно не важно, сколько уровней внутри SecondLevel (собственно, вы этого и не знаете). Да здравствует инкапсуляция.


                И ещё раз прошу прощения, но что такое «публично определённый API»? Если я верно догадываюсь, это метод, для которого определены входные и выходные данные, скажем, метод складывания двух чисел с параметрами x и y должен возвращать x + y, и совершенно неважно, как именно внутри он считает.
                Здесь я буду вынужден парировать ваш аргумент тем, что, независимо от этого, любое изменение (если таковое будет) внутри метода складывания двух чисел изменит косвенно все методы, его использующие, если не гарантировать, что даже после изменений для x и y метод всё ещё возвращает x + y.
                Подобную гарантию может дать только тестирование заявленного методом API. Если тестирования нет, значит всякое изменение внутреннего устройства на любом из N уровней, которые его разделяют от атомарного (или от данного) приведёт к косвенным изменениям. Напомню, что косвенные изменения — это не настоящие изменения, но могут стать таковыми.
                Говоря более простым языком, мы не можем гарантировать, что после того, как я поправлю одну-две строчки кода в каком-то методе, не отвалится ни одно из M мест по проекту.
                Здесь могу добавить, что, если речь идёт о библиотеке или нативной платформе, то вероятность того, что там что-то изменится — крайне низка.
                Возможно, я слегка не так вас понял, за что заранее прошу прощения.

                К слову, каш аргумент также касается и абстракций. Если SecondLevel — это абстракция, то вероятность считается также (у абстракций вообще нет уровней), т.е. вероятность изменения ThirdLevel равна вероятности изменения требований (спасибо за слово, запамятовал его как раз) и вероятность изменения абстракции (у которой есть только API).
                • 0
                  И ещё раз прошу прощения, но что такое «публично определённый API»?

                  Эмм, вы не знаете, что такое публичный API/контракт, но рассуждаете о LSP?


                  Если упрощать, то контракт — это то поведение, которое пользователь (программист) ожидает от кода.


                  Здесь я буду вынужден парировать ваш аргумент тем, что, независимо от этого, любое изменение (если таковое будет) внутри метода складывания двух чисел изменит косвенно все методы, его использующие, если не гарантировать, что даже после изменений для x и y метод всё ещё возвращает x + y.

                  Ну так метод, который говорит, что складывает x и y, и должен складывать x и y. Если вы будете это нарушать, работать с системой будет невозможно.


                  Подобную гарантию может дать только тестирование заявленного методом API.

                  … говорят, еще есть формальное доказательство корректности программ. Еще говорят, что есть рантайм-верификация пре- и пост-условий контрактов. И так далее. Но да, еще есть юнит-тестирование, которое ровно для этого и придумано.


                  После этого каждый публичный API имеет для вас "степень приближения" 0, что, в общем-то, сводит метрику к бессмысленной.


                  у абстракций вообще нет уровней

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

                  • 0
                    После этого каждый публичный API имеет для вас «степень приближения» 0, что, в общем-то, сводит метрику к бессмысленной.


                    Почему же? Если вы мне предоставляете API, которое гарантировано выполняет то, что обещает, и при этом все его изменения скрыты и их влияние нивелировано — спасибо, это отличное API. Буду пользоваться дальше и расскажу друзьям.)))

                    Тут идея в том, что сама степень приближений осталась той же: вероятность изменения N-го уровня всё ещё базируется на сумме вероятностей предшествующих ему, однако путём, как вы сказали, объявления публичного API, т.е. наложения контракта, а также написания Unit-теста, мы сводим вероятность того, что изменения некоторых уровней (для которых, следовательно, были произведены упомянутые операции) произойдут, к нулю. Можно заключить, что таким образом действия, которые сводят такую вероятность к нулю, т.е., скажем, написание Unit-тестов, логически обоснованы.

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

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

                    Согласен, ошибка в определении. Спасибо, поправлю.
                    • 0
                      Почему же?

                      Потому что публичный API — это черный ящик, вы не знаете (и не должны знать), что у него внутри. А у всего остального кода в вашей программе будет "степень приближения" в несколько единиц, что не даст вам нормальной базы для аргументации.


                      Тут идея в том, что сама степень приближений осталась той же: вероятность изменения N-го уровня всё ещё базируется на сумме вероятностей предшествующих ему

                      Мне интересно, вы МакКоннела читали? Про сокрытие сложности?


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

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


                      Мне становится интересно, а как вы вообще видите применение LSP в системе, где код не удовлетворяет своим контрактам? Или использование абстрактных зависимостей?

                      • 0
                        Потому что публичный API — это черный ящик, вы не знаете (и не должны знать), что у него внутри. А у всего остального кода в вашей программе будет «степень приближения» в несколько единиц, что не даст вам нормальной базы для аргументации.

                        Я вас прекрасно понял. Вы правы — для корректной оценки понадобится знать о том, сколько действительно существует уровней между x и y, что в принципе невозможно (да и не нужно), особенно в тех случаях, когда речь идёт о методе какой-нибудь библиотеки, в который упираешься, а на самом деле там сокрыто N слоев.
                        Но над чем я сейчас размышляю, так это над тем, что независимо от того, знаю я о количестве слоёв или нет, вероятность изменения моего кода всё равно будет равна сумме вероятностей изменения каждого из слоев. Попробую пояснить на том же примере:
                        public class Test
                        {
                          public void ThirdLevel()
                          {
                            SecondLevel();
                          }
                        
                          public void SecondLevel()
                          {
                            FirstLevel();
                          }
                        
                          public void FirstLevel()
                          {
                            Console.WriteLine("1");
                          }
                        }
                        


                        Предположим, что Console.WriteLine — это API не от Microsoft, а метод библиотеки, над которой постоянно ведётся работа. Также предположим, что данный метод имеет 40 слоёв. Скажем, первым слоем идёт какой-нибудь нативный вызов, затем он оборачивается в некую абстракцию с одним контрактом, потом ещё во что-то, и так далее. Даже не смотря на то, что я не могу посмотреть и узнать о том, что там 40 слоев, не означает ли это, что тем не менее, после выхода новой версии библиотеки, существует немаленькая вероятность того, что мой код сломается, т.к. изменения, происходящие на 40 (а если 400?) слоях (даже, если я о них не знаю) самой разной степени общности, рано или поздно приведут к нарушению корректности?

                        В чём здесь заключается неточность? В предположении о том, что вообще возможна такая структура, когда один слой оборачивает другой и т.д. или что это в принципе может привести к изменениям?

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

                        Однако, если всё же допустить возможность отсутствия Unit-тестов и вернуться к примеру: я так понимаю, что вы утверждаете, что потенциальное несоответствие между контрактом и фактической работой метода Console.WriteLine — это проблема самого метода и API, которую должны решать его разработчики, верно? Тут я вас прекрасно понимаю, всё верно.

                        А если предположить, что Console.WriteLine — это метод из моего проекта, который наворачивает базовый .NET API в уйму различных абстракций самого разного сорта, каждая из которых обещает выполнять все наложенные контракты, но ввиду постоянных изменений, не подкреплённых Unit-тестами, периодически что-то где-то отваливается (сейчас уже больше веду речь о реальной практике, а не вымышленных примерах)?

                        Мне интересно, вы МакКоннела читали? Про сокрытие сложности?

                        Как видно, нет. Раз вы спросили и это было упомянуто в беседе — значит, стало быть, надо. Добавлю в очередь.
                        • 0
                          вероятность изменения моего кода всё равно будет равна сумме вероятностей изменения каждого из слоев

                          … но вероятность изменения каждого из слоев вам неизвестна, следовательно, эта метрика для вас недоступна. Ну и зачем ее использовать тогда?


                          В чём здесь заключается неточность? В предположении о том, что вообще возможна такая структура, когда один слой оборачивает другой и т.д. или что это в принципе может привести к изменениям?

                          В том, что вы пытаетесь посчитать "вероятность изменения" кода на основании того, сколько под ним слоев, в то время, как единственная доступная вам метрика — это число зависимостей.


                          (и, заметим, весь этот наш разговор мило и незаметно нарушает SRP)


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


                          (начался этот разговор, напомню, с того, что ваше Apr(x) — неизмеримо)


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

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


                          А если предположить, что Console.WriteLine — это метод из моего проекта, который наворачивает базовый .NET API в уйму различных абстракций самого разного сорта, каждая из которых обещает выполнять все наложенные контракты, но ввиду постоянных изменений, не подкреплённых Unit-тестами, периодически что-то где-то отваливается?

                          … то у вас плохой процесс разработки. О качестве вашего кода по этому описанию сказать нельзя вообще ничего.

                          • 0
                            … но вероятность изменения каждого из слоев вам неизвестна, следовательно, эта метрика для вас недоступна. Ну и зачем ее использовать тогда?


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

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

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

                            Но, скажем, как бы я, всё же, пользовался данной метрикой в реальном проекте? Заранее предположим, что это очень плохой проект. Я вижу метод, который пользуется некоторым A для совершения некоторой операции. Метод A определён в этом же проекте и, глядя на его код, я нахожу, что он пользуется B для совершения операции ещё более низкого уровня (низкой степени приближения). Таким образом я опускаюсь на N уровней, когда ниже уже некуда: остались только List, да прочие библиотечные классы, вероятность изменения которых, как мы уже заключили из первого абзаца, посчитать невозможно.

                            Что я могу заключить об этом коде? Пока ничего. Но я уже точно знаю, что вероятность изменения метода, с которого был начат анализ, будет складываться из вероятностей изменения низлежащих методов (A, B и т.д.).

                            Вы можете заявить, что каждый из этих методов должен реализовывать некоторый контракт, а потому включать его в метрику не имеет смысла, однако его писали люди в разное время с разным багажом знаний: кто-то из них читал Макконелла и постарался оставить в наследство набор Unit-тестов для своего API, кто-то скомпилировал в голове и подумал, что всё и так понятно. И этот код всё ещё находится внутри проекта.

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

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

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

                            Тут я тоже полностью вас поддерживаю. К сожалению, решения писать или не писать Юнит-тесты в рабочих проектах в мою область ответственности не входит, а потому приходится работать с тем, что есть.
                            • 0
                              Но я уже точно знаю, что вероятность изменения метода, с которого был начат анализ, будет складываться из вероятностей изменения низлежащих методов (A, B и т.д.).

                              И что вам дало это знание?


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

                              Опять-таки, что вам даст эта вероятность?


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

                              Каким же образом?


                              Еще раз, non-actionable metrics бесполезны.

                              • 0
                                И что вам дало это знание?

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


                                Как мы уже поняли, она, если её можно измерить, количественно выражает фактор хрупкости и неустойчивости блока, т.е. вероятность того, что он сломается в конце концов.


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


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


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

                                • 0
                                  Как мы уже поняли, она, если её можно измерить, количественно выражает фактор хрупкости и неустойчивости блока, т.е. вероятность того, что он сломается в конце концов.

                                  "вероятность изменения метода"… количественно выражает "вероятность того, что он сломается в конце концов."


                                  Очень полезная метрика. Очень.


                                  Впрочем, даже если отойти от этой рекурсии, вы все равно не ответили на вопрос "что с этим делать". Вот вы померяли вероятность изменения, она, скажем, 0.56. И что вы будете делать с этой цифрой дальше?


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


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


                                  А на следующем этапе этого мысленного эксперимента вы вспомните, что согласно DI вы должны зависеть не от конкретных реализаций, а от абстракций — то есть, в некоем идеальном случае вы не знаете ни одной реализации используемых вами методов.


                                  Ну и как вы будете считать вашу метрику теперь?


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


                                  Мало того, что это вообще полезное теоретическое знание, ибо без него той полноты картины, какой требуется мною изначально

                                  Требуется зачем?


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

                                  Эмм. Вы уверены, что они именно следуют, однозначным и очевидным способом? Продемонстрируйте.


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

                                  Я не знаю, что такое "нездоровое по размерам наследование", я в среднем вообще не смотрю на глубину наследования (кроме целиком создаваемых мной иерархий), я смотрю на его семантику.

                                  • +1

                                    Почти со всем комментарием я вынужден согласиться и капитулировать. Как-то конкретно парировать или ответить по делу и в кратце, думаю, не смогу, мне нужно время.


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

                              • 0

                                Прошу прощения за нудность...


                                Но известность или неизвестность никак не влияет на то, что происходит в действительности.

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


                                Хотите верьте, хотите нет, но почти всё, что вы видите состоит из пустого пространства.

                                Существованию физического пространства (если Вы о нём) нет прямых доказательств. Общепринятые сегодня физические модели могут соответствовать, а могут совсем не соответствовать тому, что происходит в той самой самой-пресамой настоящей действительности (имхо, совсем не могут соответствовать).


                                Ваше знание или незнание этого факта никак не сделает реальность иной.

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


                                P.S. Огромное спасибо за статью и за Ваш с lair диалог — получил массу удовольствия!

                                • 0

                                  Спасибо за комментарий и отзыв.


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

                                  Я не сторонник субъективного релятивизма и прочего, так что интересно, о каких примерах идёт речь?

                                  • 0
                                    Я не сторонник субъективного релятивизма и прочего, так что интересно, о каких примерах идёт речь?

                                    Да не счесть их.


                                    Незнание правил дорожного движения влияет на частоту аварийных ситуаций в действительности. Знание тоже влияет.


                                    Незнание ситуации на поле боя влияет на действительность его исхода. Знание тоже влияет.


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


                                    И, кстати, причём тут субъективный релятивизм? Примеры актуальны и с позиций материализма.


                                    Я не сторонник субъективного релятивизма и прочего

                                    Вообще ничего не сторонник? Даже материализма? Который, кстати основан только на вере, и это не является секретом. Например, в своё время убеждённый материалист и виднейший учёный Бертран Рассел в "проблемах философии" прямо призвал верить в истинность материализма, т.к. убедился в том, что доказать это невозможно.


                                    Лично моё мнение, что мыслящие люди должны иметь, а пока не имеют — пытаться приобрести, незашоренное шаблонами представление о природе реальности, в которой они существуют. Причём это не подразумевает досконального знания физики, которая изучает лишь небольшую часть реальности. Но это всего-лишь моё мнение. Многие люди по разным причинам считают иначе.

                                    • 0
                                      Незнание правил дорожного движения влияет на частоту аварийных ситуаций в действительности. Знание тоже влияет.

                                      Ага, именно это "незнание" я и предположил.


                                      Могу заметить, что в данном случае произошла подмена с "опосредованно влияет" на "влияет". Я же имел ввиду, что "незнание" о том, что из себя представляет явление, никак не влияет на само явление и его свойства. Так что тут, вполне возможно, либо возникло недопонимание, либо я совершил оплошность (как всегда) и не до конца соблюдал точность высказываний.


                                      И, кстати, причём тут субъективный релятивизм? Примеры актуальны и с позиций материализма.

                                      Я исходил из того, что "релятивизм" — это не про "объективность", а т.к. я предполагаю, всё же, наличие абсолюта (независимого от познания, сознания и т.д.), то так и написал.


                                      Вообще ничего не сторонник? Даже материализма?

                                      Как раз материализма тот ещё сторонник (но не ортодокс). Хотя иногда и солипсистские мыслишки закрадываются, но так даже интереснее.


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

                                      Это хорошая мысль. К сожалению, реальность то и дело ускользает от нас.

                                      • 0
                                        Я же имел ввиду, что "незнание" о том, что из себя представляет явление, никак не влияет на само явление и его свойства.

                                        Дьявол в деталях. Явление потому и явление, что является. А что на самом деле происходит "там", "где" ничего не является, чисто эмпирически выяснить невозможно. Хотя поразительным образом современной физике приходится считать, что измерение (процесс получения знания) каким-то магическим образом влияет на переход из квантового состояния в классическое, т.е. на действительность, как её понимают материалисты. Как будто этого мало, есть экспериментальные подтверждения нарушения принципа локального реализма. Имхо, и без этого возможно узнать, что модель внешнего мира в сознании не является отображением независимой от сознания действительности, но то, что довольно толстые намёки на это появляются в самых продвинутых областях эмпирических наук — весьма занимательный факт.


                                        Я исходил из того, что "релятивизм" — это не про "объективность", а т.к. я предполагаю, всё же, наличие абсолюта (независимого от познания, сознания и т.д.), то так и написал.

                                        Я также убеждён в существовании независимого от сознания, познания и т.д. абсолюта. Правда, не имеющего множественную природу и, следовательно, в принципе не подлежащего явлению.


                                        Как раз материализма тот ещё сторонник (но не ортодокс). Хотя иногда и солипсистские мыслишки закрадываются, но так даже интереснее.

                                        Солипсизм же предполагает, что кроме носителя сознания ничего не существует, верно? Причём сознания той конкретной личности, которая об этом думает. Не берусь утверждать за всех, но мой личный опыт показывает, что такое невозможно. В случае, если бы существовал только я, вся действительность была бы во мне, и я бы знал всё о действительности, и ничто не происходило бы без моего ведома. На деле всё происходит не так, следовательно, солипсизм (по крайней мере в известной мне форме) не актуален.


                                        К сожалению, реальность то и дело ускользает от нас.

                                        Увы… Но к счастью, у нас есть абстрактное мышление, память и (в том числе электронные!) средства записи и хранения информации. :)

                                        • 0
                                          Дьявол в деталях. Явление потому и явление, что является. А что на самом деле происходит "там", "где" ничего не является, чисто эмпирически выяснить невозможно.

                                          Хм, тут немного потерял ход. Я, кажется, рассуждал о том, что "незнание" деталей явления не влияет на само явление, т.е., что "незнание" в сущности своей есть такое явление, не имеющее никакой объективной и прямой силы над другими явлениями. Например, от того факта, что вы не знали, что происходит на Солнце, выражаясь с позиции абсолютизма, можно смело заявить, что от этого с Солнцем ничего не случилось. Возможно, это слишком просто высказано.


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


                                          Более того, если подойти с иной точки зрения, незнание — это субъективный процесс, связанный с сознанием, а следовательно никакого отношения к объективной реальности не имеет.


                                          Солипсизм же предполагает, что кроме носителя сознания ничего не существует, верно? Причём сознания той конкретной личности, которая об этом думает. Не берусь утверждать за всех, но мой личный опыт показывает, что такое невозможно. В случае, если бы существовал только я, вся действительность была бы во мне, и я бы знал всё о действительности, и ничто не происходило бы без моего ведома. На деле всё происходит не так, следовательно, солипсизм (по крайней мере в известной мне форме) не актуален.

                                          Я с вами в некоторой степени согласен. Могу посоветовать послушать Алана Уоттса, можно ещё почитать Шопенгауэра. Это не всегда о "солипсизме", но дух восточной философии, всё же, чем-то привлекает, на мой взгляд.


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


                                          So in this idea, then, everybody is fundamentally the ultimate reality. Not God in a politically kingly sense, but God in the sense of being the self, the deep-down basic whatever there is. And you're all that, only you're pretending you're not.

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

                                          • 0
                                            Хм, тут немного потерял ход.

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


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

                                            Да, стабильность интерсубъективной модели явлений — в высшей степени потрясающее явление. Масштабы синхронизации восприятий в мельчайших деталях… На этом фоне наши разработки виртуальных миров даже не калькуляторы, а счёты.


                                            Я с вами в некоторой степени согласен. Могу посоветовать послушать Алана Уоттса, можно ещё почитать Шопенгауэра. Это не всегда о "солипсизме", но дух восточной философии, всё же, чем-то привлекает, на мой взгляд.

                                            Спасибо. "Мир как воля и представление" стоит на книжной полке среди прочего. Давно этим занимаюсь и, в основном, уже получилось пересобрать основные элементы мировоззрения, в том числе и касающиеся онтологии. Ох, как это непросто. Конечно, надо читать философов (имхо, только аналитического направления), но они часто не столько обосновывают, сколько описывают свои взгляды, что не особо помогает при самостоятельных проверках. Хотя и на том спасибо. Приходится штудировать логику и теорию множеств, и вообще внимательно присматриваться к математике. Программирование, кстати, тоже помогает. :)


                                            На данном этапе моя онтология, наверное, чем-то напоминает монадологию Лейбница (помнится, были с ним серьёзные разногласия) на базе 1-й части "Путеводителя растерянных" Маймонида и рациональных элементов традиционной Каббалы. Прояснению деталей, конечно, конца и края не видно, но, судя по всему, радикальных изменений уже не предвидится. Материализму окончательно сказал "прощай" около 3-х лет назад.


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

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

                                            • 0

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


                                              Т.е. я вывожу из явления субъективного наличие явления объективного, основываясь на моих рефлексивных представлениях о физическом устройстве мозга, глаза и иных инструментов, с помощью которых "явление общее" становится "явлением частным".


                                              Надеюсь, не слишком сумбурно донёс мысль.


                                              Приходится штудировать логику и теорию множеств, и вообще внимательно присматриваться к математике. Программирование, кстати, тоже помогает. :)

                                              Был бы очень рад, если бы порекомендовали несколько отличительных, понравившихся, работ.


                                              Материализму окончательно сказал "прощай" около 3-х лет назад.

                                              А что вас заставило отказаться от "материализма"?

                                              • +1
                                                Т.е. я вывожу из явления субъективного наличие явления объективного, основываясь на моих рефлексивных представлениях о физическом устройстве мозга, глаза и иных инструментов, с помощью которых "явление общее" становится "явлением частным".

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


                                                Был бы очень рад, если бы порекомендовали несколько отличительных, понравившихся, работ.

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


                                                Философия:
                                                Аналитическая философия — потрясающий по ширине и глубине обзор развития аналитической мысли, имхо, must read хотя бы пару раз.


                                                Онтологические проблемы референции — пока целиком не осилил, но определённо суперкнига.


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


                                                Ещё есть Платон… Знакомство с философией я начинал с него. Это увлекательное путешествие, но имхо, если стоит вопрос времени, то лучше сразу потратить его на формальную логику.


                                                Морэ Нэвухим (Путеводитель Растерянных) — этот труд был специально создан для запутавшейся еврейской интеллектуальной элиты 12 века. Т.е., под "растерянными" в нём подразумеваются перцы, владеющие всеми основами тогдашней науки и философии. Соответственно, чтение не из лёгких. Кроме того, труд открыто религиозен. И, тем не менее, имхо, это must read в квадрате, т.к. там с совершенно неожиданных точек зрения раскрывается понятие не-множества. Я много всего читал из разных культур, но подобного нигде больше не встречал. Линк на первую часть. Переводов на русский второй и третьей не нашёл (сам два раза читал на иврите всё, и это был катарсис). Однако, первая часть — основная и вполне самодостаточная.


                                                Монадология Лейбница — блестящий физик-математик описывает свой взгляд на вещи. Имхо, очень интересное описание варианта мировоззрения идеалиста.


                                                Логико-философский трактат — ранний Витгенштейн. Тогда Рассел с Уайтхедом мутили основания метаматематики и мат. логики, а Витгенштейн учился у Рассела и даже дружил с ним, а потом взял и замутил этот трактат, да так, что Рассел думал, что понял, а Витгеншнейн думал, что Рассел ничего не понял. Интрига, одним словом.


                                                Философские исследования — поздний Витгенштейн. Венский кружок возвёл его трактат в абсолют, а он, похоже, совсем не имел этого в виду, и для равновесия переродился как философ.


                                                Проблемы философии — онлайн-текст на русском не нашёл, так что english. Хотя всякие доки на русском скачать предлагают… Один из основателей современной логики копает вглубь, чтобы обнаружить, что материализм недоказуем, и поэтому надо в него просто верить. Хоть я и не согласен с позицией Рассела (насчёт того, что надо верить), но он, несомненно, был блестящим мыслителем, и этот его разбор полётов очень интересен и богат мыслью.


                                                Логика и математика — нижеследующие книги стоят у меня на полке, онлайн не искал, даю только названия.


                                                А.Д. Гетманова "Логика" — учебник по классической (т.н. аристотелевой) логике.


                                                С.К. Клини "Математическая логика" — автор один из учёных, стоявших у истоков метаматематики.


                                                Ю.Л. Ершов, Е.А. Палютин "Математическая логика" — изложение с явных позиций аксиоматического метода; "краткость — сестра таланта" может быть девизом книги (последующих, в общем, тоже), над иными страницами можно медитировать днями, а то и неделями.


                                                О.Е. Акимов "Дискретная математика. Логика, группы, графы" — изложение с позиций конструктивизма.


                                                Ю.Г. Карпов "Теория автоматов" — интересное введение в логику. Ну и теория автоматов, естественно, как основная тематика. Рассматривается программирование на Prolog.


                                                F. William Lawvere, Stephen H. Schanuel "Conceptual Mathematics. A first introduction to categories" — теория категорий.
                                                Charles C. Pinter "A Book of Set Theory" — теория множеств.


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


                                                Что касается Каббалы, то за колоссальным количеством шарлатанства, паразитирующего на этой тематике, настоящего предмета фактически не видно. Искать адекватную литературу на русском без хорошего знакомства с предметом вряд ли возможно. Вообще говоря, я упомянул эту тему прежде, чем успел хорошенько подумать. Но раз "язык" меня подвёл, дам рекомендацию, но сначала слово предостережения. Коротко: при неосторожном подходе, некритическом мышлении или излишней эмоциональности можно реально повредить психику, это не шутка. Здесь чуть подробнее. Зачем, в таком случае, я туда полез? Во-первых, меня никто не предупреждал. Во-вторых, там рассматривается очень любопытная онтология и метафизика, и частью всего этого является аксиология, т.е. интегрально рассматриваются вопросы и существования, и зачем оно, вообще, нужно. Единственная известная мне адекватная книга на русском языке, не являющаяся профанацией, и которую при этом, кажется, можно изучать без предварительной подготовки (кроме рационального беспристрастного логического мышления, об опасностях уже было):
                                                Рав Йехиэль Бар Лев "Начала теоретической каббалы" издания Толдот Йешурун. Подозреваю, что онлайн не найти, однако, можно заказать тут.


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


                                                А что вас заставило отказаться от "материализма"?

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


                                                Факт 1: всё, что известно, известно из восприятия.
                                                Факт 2: люди могут из актуального восприятия (непосредственного эмпирического опыта) делать ложные выводы, домысливать и иметь в итоге ложную картину мира.


                                                Единственный (!) аргумент в пользу существования воспринимаемого "внешнего" мира в той форме, в которой он является, заключается в регулярности воспроизведения определённых объектов и их взаимного порядка в повторяющемся восприятии. Запишем в виде факта:


                                                Факт 3: у меня (похоже, не только у меня) есть текущее восприятие, частью которого является область, индексируемая в качестве памяти, и в этой памяти воспроизводятся пропозиции, согласно которым различные конфигурации объектов повторяются в разные моменты восприятия независимо от моего желания, причём так, что возможно исследование и запись сценариев динамики тех конфигураций объектов, которые изменяются с течением времени.


                                                Из этого факта невозможно вывести существование внешнего мира в той форме, в которой он является.


                                                Тем более, что объекты внешнего мира есть не более и не менее, чем абстракции мышления. Фактически -


                                                Факт 4: при визуальном восприятии человеку даётся некоторое цветовое пятно в достаточно высокой резолюции, чтобы разум искал в нём и в области, индексированной под память, такие регулярности относительно имеющейся у него абстракции времени, которые можно обобщить (внимание, абстрагирование) до объектов разной сложности конфигурации.


                                                Все объекты, которые мы видим, проходя по улице — рефлексивные абстракции нашего (условно) разума, большинство из которых ассоциативно связано и закреплено с различными визуальными паттернами ещё в детстве. Поэтому, отстаивая существование материального мира, на деле мы отстаиваем независимое существование абстракций нашего мышления.


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


                                                Итак, достоверно узнать, что внешний мир существует, невозможно. А возможно ли вообще его существование?


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


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


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

                                                • 0
                                                  кстати, не понял, почему Вы называете их рефлексивными

                                                  Допустил неточность.


                                                  Т.е., получается, что мы берём непосредственные данные, абстрагируем их в модель, и заявляем, что эта модель существует вне нас сама по себе.

                                                  Я, верно, имел ввиду нечто другое. Например, по проекции параллелепипеда на плоскость предположить о существовании самого параллелепипеда, точнее, о том, что существует нечто, отбрасывающее такую тень, и наличие данного нечто я вывожу из самой тени. Естественно, необходимо взять в расчёт тот факт, что тень и нечто, её представляющее, связаны причинно-следственным законом.


                                                  Или вы имели ввиду, что раз тень есть явление в нас, то и её вероятные причины являются явлением в нас? К тени такой аргумент, в принципе, применим, но, если пойти дальше, и заменить причинно-следственный закон вида "отбрасывать тень" на "являться в субъективном представлении", то, возможно, стоит рассмотреть тот факт, что всё, что вы видите, является проекцией объективного на субъективную плоскость восприятия.


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


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

                                                  Спасибо большое за список, обязательно его себе где-нибудь сохраню. Шопенгауэра я читал, а Платона, к сожалению, нет. Не скажу, что мне и Шопенгауэр полностью понравился. Мне нравится он как прозаик, как литературный автор. У него есть настолько мощные высказывания, которые, будучи дистиллироваными и очищенными от контекста, вызывают невероятный шквал образов и представлений. Возможно, также повлиял тот факт, что я никогда не был оптимистом. В моём скудном уме оптимизм почти всегда направлен в сторону от океана истины (метафора со слов Ньютона), в то время как пессимизм — напротив, а потому высказывания о том, что "жизнь — это арена, усыпанная угольями" кажутся мне в какой-то степени вполне обоснованными.


                                                  Что я хотел сказать? Если не обращать внимание на сумбур потока мыслей, основная линия заключается в том, что мой фокус исследований, по всей видимости, всегда был направлен на объективную сторону реальности, а следовательно — на историю науки, а не философии. Экзистенциальные вопросы всегда имели более низкий приоритет, потому как, по всей видимости и говоря вашим языком, я уже стал закоренелым вассалом материализма. Вероятно, из-за любви к математике и физике, а не к философии.


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


                                                  Это всё достаточно интересно звучит, но воспринимается, скорее, как интересная повесть, нежели истина.


                                                  Ричард Фейнман говорил, что настоящую природу реальности не познать, ибо это подобно тому, как пытаться вычислить правила шахмат, просто глядя на игру: порою тебе будет это удаваться, но после очередной рокировки придётся пересмотреть свои взгляды (это вольная трактовка его слов).


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


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


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


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

                                                  • 0
                                                    Или вы имели ввиду, что раз тень есть явление в нас, то и её вероятные причины являются явлением в нас? К тени такой аргумент, в принципе, применим, но, если пойти дальше, и заменить причинно-следственный закон вида "отбрасывать тень" на "являться в субъективном представлении", то, возможно, стоит рассмотреть тот факт, что всё, что вы видите, является проекцией объективного на субъективную плоскость восприятия.

                                                    С позиций материализма ощущения называются отражением. Вот, смотрите, что пишет Гетманова в философском введении к своему предмету (глава 1 Предмет и значение логики, параграф 1 Формы познания):


                                                    … Предметы воздействуют на наши органы чувств и вызывают в них ощущения, которые воспринимаются мозгом.

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


                                                    … Ощущение — это отражение отдельных свойств предметов или явлений материального мира, непосредственно воздействующих на органы чувств (например, ощущения горького, соленого, теплого, красного, круглого, гладкого и т.д.).

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


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


                                                    я уже стал закоренелым вассалом материализма. Вероятно, из-за любви к математике и физике, а не к философии.

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


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

                                                    Не за что :) И да, адекватное решение метафизических вопросов требует много времени, увы.


                                                    В моём скудном уме оптимизм почти всегда направлен в сторону от океана истины (метафора со слов Ньютона), в то время как пессимизм — напротив, а потому высказывания о том, что "жизнь — это арена, усыпанная угольями" кажутся мне в какой-то степени вполне обоснованными.

                                                    У меня тоже был весьма мрачный взгляд на жизнь. Пока я не прочёл 3-ю часть "Путеводителя" Маймонида (увы, не нашёл её перевод на русский), в которой он (помимо прочего) полемизирует с пессимистами того времени. Основная мысль такова: на самом деле в мире и в индивидуальной жизни больше не страданий и несчастья, а счастья и удовольствия. Наша склонность преувеличивать количество несчастья происходит от того, что мы шокированы известиями о несчастье. Но почему мы шокированы? Ведь если бы несчастье было нормой жизни, то мы бы к этому привыкли. Само наше восприятие несчастья как чего-то шокирующего и ненормального свидетельствует о том, что в абсолютном большинстве случаев хорошего больше, нежели нехорошего. И что последнее мы склонны замечать больше, нежели ценить первое.


                                                    Это всё достаточно интересно звучит, но воспринимается, скорее, как интересная повесть, нежели истина.

                                                    Любая истина познаётся исключительно из личного исследования.


                                                    Ричард Фейнман говорил, что настоящую природу реальности не познать, ибо это подобно тому, как пытаться вычислить правила шахмат, просто глядя на игру: порою тебе будет это удаваться, но после очередной рокировки придётся пересмотреть свои взгляды (это вольная трактовка его слов).

                                                    Я пришёл к выводу, что единственные непосредственные истины доступны нам:


                                                    1. непосредственно из опыта
                                                    2. через логический вывод из опыта
                                                    3. через сведение к противоречию

                                                    Всё остальное — вопрос доверия.


                                                    Важная тема во всём этом — на каком основании нам следует доверять логике. Это вопрос, который крайне легко недооценить, а между тем, без логики у нас нет элементарных инструментов оценки истинности. Я для себя решил этот вопрос так (спасибо теории множеств): любое явление имеет субъектно-предикатную структуру (оно кому-то является); любая субъектно-предикатная структура имеет множественную природу; всё, что имеет множественную природу, доступно логическому анализу в математической логике. По этой причине возможны некоторые априорные суждения, выводимые не из позитивного опыта, на основе исключения противоречий. В конченом счёте я пришёл к выводу, что у познания есть чётко очерченные границы: всё, что имеет множественную природу — потенциально познаваемо при достаточных количественных ресурсах (типа объёма памяти и производительности при расчётах), а обо всём, что не имеет множественной природы, можно знать лишь то, что оно существует, причём в единственном числе.


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

                                                    От частного к общему — это индукция. В математике — незаменимый инструмент, с которым возможны точные результаты. В физике — это т.н. эмпирическая индукция, всегда предполагает принимать истинными ряд никем не доказанных аксиом (например, что природа сохраняет свои законы во времени). Эмпирическая индукция может быть и опасной дорожкой в некоторых случаях. Есть известный пример: телёнок с самого рождения имеет следующий частный случай — его кормят три раза в день; он индуктивно заключает: меня кормили три раза в день сегодня, вчера, и позавчера — следовательно, так будет всегда. В один прекрасный день этого телёнка ведут на скотобойню...


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

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


                                                    Это весьма серьёзная тема и, кажется, мне ещё нужно какое-то время, чтобы всё обдумать и выразить словами. Ещё раз спасибо за советы и рекомендации по прочтению литературы.

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


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

        • +2
          DamageMediator — простейший класс, представляющий из себя компонент посредника урона. Его задача — вобрать в себя хитрое взаимодействие между различными компонентами родительского контейнера так, чтобы посчитать урон персонажа с учётом экипировки, оружия, характеристик и прочего.

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


          Сначала посмотрим на сигнатуру класса: DamageMediator : GameComponent. Зачем нужно это наследование? Какую пользу оно приносит?


          Теперь посмотрим на сигнатуру метода: public int Next(). Эм. Что делает этот метод? Почему он возвращает число? Какая у него семантика?


          Наконец, посмотрим внутрь:


          var equipment = GetEquipment();
          var stats = GetStats();

          Кажется, методы GetEquipment и GetStats — унаследованные от базового класса. Того самого, который GameComponent. У компонента игры есть шмот и статы? Выглядит странно. Или это методы, которые смотрят в какой-то контекст, где есть какой-то "текущий игрок", у которого есть шмот и статы? Но тогда это не очевидно из названия.


          var weapon = equipment.Weapon;
          var damage = weapon.Damage;

          … а если никакого оружия сейчас нет? А если, наоборот, есть два оружия? Почему, GetEquipment — метод, но Weapon — свойство?


          var isCrit = stats.CriticalChance.Next();

          … и снова этот магический Next, который на этот раз, судя по всему, возвращает bool.


          (я даже не спрашиваю, почему криты зависят от статов напрямую, а не через оружие)


          Избавимся от GetEquipment и GetStats, перенеся их в параметры.

          … количество зависимостей в этот момент измениться не должно.


          Понимаете ли, в чем дело. Есть такой паттерн, domain-driven design, суть которого, если очень коротко и грубо, сводится к тому, что неплохо бы, чтобы доменная модель имела прямое выражение в сущностях кода. В игровой системе, скажем, есть вполне выраженные сущности со вполне определенными характеристиками, от которых и можно начинать строить дизайн приложения. Этот дизайн — если он хорошо сделан — будет отвечать на большую часть поставленных мной выше вопросов, и, тем самым, резко уменьшит wtf-метрику кода — а именно она влияет на стоимость поддержки кода намного сильнее, чем число его зависимостей.

          • 0
            Да ладно, предыдущие статьи по Unity которые недавно мелькали на хабре нам сообщают, что надо экономить прям на foreach и свойствах. А вы про DDD.

            Другое дело, что код стал только сложнее в результате рефакторинга, что очень подозрительно.
            • 0

              Да статья-то, вроде бы, и не о Unity.

              • 0
                Да, немного промахнулся, GameComponent оказывается из XNA.
              • 0
                Другое дело, что код стал только сложнее в результате рефакторинга, что очень подозрительно.


                Можете, пожалуйста, разъяснить, что именно подразумеваете под «сложнее» и как оцениваете?
                • +1
                  Оцениваю легко в данном случае.

                  Был один класс с кодом, в котором вычислялась так или иначе логика урона.
                  После рефакторинга, их стало два. Более того, никто не гарантирует, что над входными параметрами статического метода DamageUtil.Next не «подшаманит» внешний слой, который логику опять разделит.

                  Если у юнита урон х2 — это изменение пойдет в *Util или в компоненту конкретную? Не очевидно. Непонятно. Раньше в код расчета урона хотя бы одна точка входа была, теперь их две.
              • 0
                Спасибо большое за подробный комментарий! Сейчас постараюсь дать ответ по различным пунктам.

                1.
                Мне искренне кажется, что вся сложность, которая в этом классе присутствует, возникла из того, что вы зачем-то взяли модель компонентов/контейнеров, а теперь пытаетесь построить на ней бизнес-логику.

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

                2.
                Сначала посмотрим на сигнатуру класса: DamageMediator: GameComponent. Зачем нужно это наследование? Какую пользу оно приносит?


                Здесь уже точно подмечен главный недочёт примера: не объяснено более детально, что именно значит «быть GameComponent». На самом деле, это не «компонент игры», а «игровой компонент», что семантически означает «компонент, находящийся в контексте игры». Связано это с тем, что существует сущность Component, которую оный расширяет. Собственно, такие детали в рамках рассматриваемого примера я решил опустить.

                3.
                Теперь посмотрим на сигнатуру метода: public int Next(). Эм. Что делает этот метод? Почему он возвращает число? Какая у него семантика?


                Здесь вы также ловко указали на мой недочёт. На самом деле, я руководствовался простой логикой: методы класса Random имеют наименование Next, что означает, что каждый раз будет генерироваться отличное от предшествующего число. Вот, собственно, и я воспользовался той же нехитрой концепцией, полагая, что это не вызовет вопросов. Увы.

                4.
                Кажется, методы GetEquipment и GetStats — унаследованные от базового класса. Того самого, который GameComponent. У компонента игры есть шмот и статы? Выглядит странно. Или это методы, которые смотрят в какой-то контекст, где есть какой-то «текущий игрок», у которого есть шмот и статы? Но тогда это не очевидно из названия.


                Здесь я тоже попал в ловушку упрощения примера: эти методы на самом деле являются чисто декларативными абстракциями и в реальном коде представляют нечто вроде
                var equipment = Container.Get<Equipment>();
                

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

                5.
                … количество зависимостей в этот момент измениться не должно.

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

                6.
                wtf-метрику кода
                прошу прощения за вопрос, но что это за понятие?

                7. Я полностью согласен с вашим комментарием по поводу дизайна, однако хочу отметить, что
                если он хорошо сделан
                — неопределённое понятие с точки зрения логики. Более формально и точно определить подобные неопределённые человеческие абстракции вроде «красивый», «хороший» и было моей основной целью, по крайней мере, так мне это виделось.

                Ещё раз спасибо за комментарий, я многое почерпнул из него. Буду рад вашему ответу. Особенно интересует, что вы думаете по поводу непосредственно формализации: имеет ли право на жизнь подобная идея? Можно ли пытаться количественно оценивать код, полностью опуская своё человеческое начало или же это невозможно? Когда я спросил то же самое у Майкла Фезерса, указывая ему, что его «красивый код» — не будет таким же, как мой «красивый код», он сказал, что так не выйдет, и что из всех наиболее формальных систем можно назвать принципы SOLID, где даже вводятся некоторые характеристики вроде связности.
                • 0
                  полностью разделяю компонентную систему и бизнес-логику.

                  И где же ваша бизнес-логика оказалась?


                  На самом деле, это не «компонент игры», а «игровой компонент», что семантически означает «компонент, находящийся в контексте игры».

                  … неявный контекст — зло. Большое. Оно, кстати, и приносит вам кучу проблем.


                  в реальном коде представляют нечто вроде var equipment = Container.Get<Equipment>();

                  Ну так это же не меньшее зло и есть — вы как раз и смешиваете компоненты и бизнес. С точки зрения бизнеса, нет никакого контейнера. Хуже того, с точки зрения программиста все еще не понятно, какой же именно шмот мы получим.


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

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


                  прошу прощения за вопрос, но что это за понятие?

                  WTF-метрика — это количество восклицаний "what the f***?!", произнесенных за время чтения кода.


                  Более формально и точно определить подобные неопределённые человеческие абстракции вроде «красивый», «хороший» и было моей основной целью, по крайней мере, так мне это виделось.

                  Формальных определений этим понятиям нет, и это к счастью.


                  Особенно интересует, что вы думаете по поводу непосредственно формализации: имеет ли право на жизнь подобная идея?

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


                  Можно ли пытаться количественно оценивать код, полностью опуская своё человеческое начало или же это невозможно?

                  Это не нужно. Именно потому, кстати, что читают код люди.

                  • 0
                    И где же ваша бизнес-логика оказалась?

                    Первоначально класс DamageMediator занимался просчётом урона, а также отвечал за контейнеры и компоненты. После изменений (к слову, до написания статьи и проведённого в ней анализа, я искренне верил, что он в относительном порядке) код, ответственный за просчёт урона (простая математическая функция от нескольких переменных) был полностью вынесен за пределы DamageMediator. Это можно называть разделением бизнес-логики и компонентной системы?

                    Ну так это же не меньшее зло и есть — вы как раз и смешиваете компоненты и бизнес. С точки зрения бизнеса, нет никакого контейнера. Хуже того, с точки зрения программиста все еще не понятно, какой же именно шмот мы получим.

                    Здесь я полностью признаю ваши аргументы и, к сожалению, никак не могу защитить своё решение. Это недочёт. Теперь я его отчётливо вижу. Спасибо.

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

                    Тут, пожалуй, не соглашусь, и парирую ваш аргумент тем, что, технически, когда я вынес вызов двух методов из метода Next, а результат работы этих методов вынес в параметры, количество зависимостей внутри этого метода уменьшилось, потому что теперь он не зависит от той магии, которая происходила в этих методах. Как мне кажется, это важный момент, который нельзя упускать. То, что сам метод до сих пор зависит от типов Equipment и Stats — это осталось. Другое дело, что такое изменение не имеет смысла, если другой метод (уже не переделанный Next) не вызовет GetEquipment и GetStats сам и не воспользуется переделанным Next для генерации результата (что и получилось на примере). Существует принципиальное различие между вариантом, когда метод Next зависит от GetEquipment и GetStats, и когда не зависит.

                    Формальных определений этим понятиям нет, и это к счастью.

                    Тут я имел ввиду не «красивый» вообще, а «красивый код», т.е. что именно мы подразумеваем под «красивый код»? Насколько «красивый код» отличается от «выгодного бизнесу кода» (исходя из количественной оценки определённого набора параметров). Скажем, бизнесу было бы очень выгодно, если бы добавление новой функциональности (как я уже это заметил в статье) заняло не 20 минут, 10 из которых было потрачено на то, чтобы подстроить текущую негибкую архитектуру, а ровно 10. Было бы весьма удобно иметь возможность доказать, почему один вариант кода будет хуже, а другой — лучше.

                    К слову, с точки зрения тех рассуждений, которые я привёл, вами упомянутая WTF-метрика определяется через более базовые понятия.

                    Далее я бы хотел ещё раз прокомментировать domain driven design. Замечу, что вы оценили качество моего примера и его нелепой архитектуры, опираясь на свой опыт и понимание различных концепций, которые, в совокупности, можно определить как набор правил. Например, новая кора в мозге распознаёт образ семантически неочевидного наследования — и сразу реагирует. И таких образов уйма, причём с явной избыточностью (подробнее можно почитать в книге Рэя Курцвейла «Эволюция разума»).

                    Что пытаюсь сделать я? Всё же понять, на чём основаны данные правила? Как далеко и глубоко простирается их влияние?
                    Зачем это нужно? Для того, чтобы докопаться до самой сути, ведь, как я уже показал, даже SOLID-принципы основываются на некоторых соображениях более низкого уровня, а значит, их можно из оных вывести. А значит, из оных можно вывести не только SOLID, но и что-то другое. Это также значит, что можно оценивать различные варианты и делать это точно, а не просто потому, что это мнение кого-то, кто только и делает, что апеллирует на свой безграничный опыт и количество прочитанных книг (например).

                    Это не нужно. Именно потому, кстати, что читают код люди.

                    Здесь, возможно, я несколько смутно выразился, и вы меня не поняли. Идея не в том, чтобы писать неочевидный и непонятный код, а затем аргументировать это тем, что, якобы «теория так сказала». Нет, напротив, исходя из той же теории (громко сказано, очень) энтропия «неочевидного» и «непонятного» кода крайне высока.
                    Идея в том, чтобы иметь возможность оценивать код куда более глубоко и точно (то, о чём писал в предыдущем абзаце), чем обычное «Хм, этот код вполне неплох».
                    • +1
                      был полностью вынесен за пределы DamageMediator

                      Вынесен куда?


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

                      Это зависит от того, что у вас бизнес-логика. И в моем понимании вашего домена, бизнес-логика обсчета дамага — это далеко не просто "математическая функция от нескольких переменных". Так что что-то вы разделили, но вот что с чем?


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

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


                      Тут я имел ввиду не «красивый» вообще, а «красивый код», т.е. что именно мы подразумеваем под «красивый код»?

                      Я не знаю, что вы понимаете под "красивый код". Я стараюсь этим словосочетанием в работе пользоваться по минимуму, потому что красота — субъективное понятие.


                      Насколько «красивый код» отличается от «выгодного бизнесу кода» (исходя из количественной оценки определённого набора параметров).

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


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

                      Да, это было бы весьма удобно. Но это возможно не всегда и не для всех вариантов.


                      К слову, с точки зрения тех рассуждений, которые я привёл, вами упомянутая WTF-метрика определяется через более базовые понятия.

                      Это какие же?


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

                      Возможно, можно. Но набор этих правил будет существенно сложнее, чем тот код, который я ими оцениваю, поэтому какой в этом смысл?


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

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


                      Идея в том, чтобы иметь возможность оценивать код куда более глубоко и точно (то, о чём писал в предыдущем абзаце), чем обычное «Хм, этот код вполне неплох».

                      Это прекрасная идея, но построить ее на количественных показателях не выйдет.

                      • 0
                        Вынесен куда?


                        В DamageUtil.Next.

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


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

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

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

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

                        Здесь я с вами согласен, добавить особо нечего.

                        Это какие же?

                        Ну, скажем, мой код из примера вызвал у вас WTF-реакцию. Его нелепость следует из того, что блоки, ответственные за выполнение задач, переплетены между собой или, как вы уже заметили, компоненты и контейнеры лежат вместе с бизнес-логикой.
                        Единственное, о чём я совершенно не сказал — человеческий фактор, а именно такие понятия как «понятность» кода, которую сходу сложно определить, но которая означала бы, что метод asdgqw явно хуже, чем subtractHealth.

                        Возможно, можно. Но набор этих правил будет существенно сложнее, чем тот код, который я ими оцениваю, поэтому какой в этом смысл?

                        Возможно, существенно сложнее.

                        Пока что вы этого не показали. Вы просто притянули SOLID к каким-то своим соображениям, верность которых не доказана (равно как и верность приведенной вами связи). В частности, получить формальную оценку, нарушен ли SRP, весьма сложно — потому что сложно как провести границу «это один блок», так и провести границу «это одна причина для изменения».

                        Возможно, насчёт «притянули» вы правы, но хочу заметить, что я хотел показать, что SRP — это всего лишь название для определённой комбинации понятий и положений, подобно тому, как закон коммутативности — это всего лишь наименование того, что a + b = b + a.
                        Если намеченной цели добиться с первого раза не удалось — не беда. Ваши комментарии и критика чрезвычайно полезны, так что не останавливайтесь. Если есть ещё положения или понятия, в убедительности которых вы сомневаетесь, пишите, хотя очень не хотелось бы отнимать ваше время.
                        • 0
                          В DamageUtil.Next.

                          Как верно замечено, классы с названием *Util — это уже само по себе запашок, а уж если они содержат бизнес-логику, то все становится еще более неприятно.


                          Я показал это (вероятно, неубедительно), а затем намеренно убрал магию, перенеся сущности в параметры.

                          … и теперь код точно так же зависит от того, какие параметры ему передадут. Любое изменение в вызывающем коде, меняющее эти параметры, точно так же отразится на обсуждаемом коде.


                          Ну, скажем, мой код из примера вызвал у вас WTF-реакцию. Его нелепость следует из того, что блоки, ответственные за выполнение задач, переплетены между собой или, как вы уже заметили, компоненты и контейнеры лежат вместе с бизнес-логикой.

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


                          Возможно, существенно сложнее.

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


                          SRP — это всего лишь название для определённой комбинации понятий и положений

                          Это, извините, совершенно бессмысленное утверждение, потому что любое правило — это определенная комбинация понятий и положений. И… что?

                          • 0
                            Как верно замечено, классы с названием *Util — это уже само по себе запашок, а уж если они содержат бизнес-логику, то все становится еще более неприятно.

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


                            … и теперь код точно так же зависит от того, какие параметры ему передадут. Любое изменение в вызывающем коде, меняющее эти параметры, точно так же отразится на обсуждаемом коде.

                            Функция складывания двух чисел тоже зависит от того, какие числа в неё передадут. Сейчас приведу пример.


                            Допустим для простоты, что "функция складывания" — что-то намного более сложное.


                            public static int Add(int? x, int? y)
                            {
                              if (x.HasValue && y.HasValue)
                                return x.Value + y.Value;
                            
                              if (!x.HasValue && y.HasValue)
                                return y.Value;
                            
                              if (x.HasValue && !y.HasValue)
                                return x.Value;
                            
                              return 0;
                            }

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


                            Думаю, очевидно, что текущая вариация метода Add делится на два блока: тот, который обрабатывает структуру Nullable, и тот, который суммирует два числа.
                            Количество зависимостей в методе складывается из int + Nullable<T> + Nullable<T>.HasValue + Nullable<T>.Value + (x + y) (как вы помните, это типа очень сложная операция). Итого Qd = 5.


                            Проведу рефакторинг, аналогичный тому, что есть в статье:


                            public static int Add(int? x, int? y)
                            {
                              if (x.HasValue && y.HasValue)
                                return Add(x, y);
                            
                              if (!x.HasValue && y.HasValue)
                                return Add(0, y);
                            
                              if (x.HasValue && !y.HasValue)
                                return Add(x, 0);
                            
                              return 0;
                            }
                            
                            public static int Add(int x, int y)
                            {
                              return x + y;
                            }

                            Тяжело не заметить, что количество зависимостей первого Add осталось таким же, а второго, напротив, значительно уменьшилось. К тому же, его теперь можно переиспользовать (чего ранее нельзя было сделать).

                            • 0
                              Где, по вашему, должна в таком случае находится бизнес-логика?

                              В объекте, к которому она семантически принадлежит.


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

                              … если бы я моделировал игровую боевку — скажем, для D&D или GURPS — то я бы шел одним из двух путей. В первом я бы разбил расчет урона на две части — созданный урон и понесенный урон (простите за кривые термины). Соответственно, созданный урон вычислялся бы тем, что его наносит (оружием/заклинанием/внешней силой), а дальше это значение передавалось бы объекту действия и тот бы уже вычислял понесенный урон. Потому что в реальности на урон может влиять: сила, тхака, критикалы, конкретные особенности оружия, окружающая среда, спасы, расы, буфы и еще миллион вещей. Собственно, эта сложность и порождает второй потенциальный путь: создать класс DamageCalculationRule, который бы описывал — существующую в реальности в PH — сущность "правила расчета урона", и передавал бы все нужные параметры ему. Более того, возможно, на каждый расчет таких правил было бы больше одного, и их вычисления применялись бы каскадом.


                              Я использовал наименование Util, т.к. по негласному соглашению стараюсь придерживаться правила, что в *Util лежат исключительно статические методы, которые что-то считают

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


                              Тяжело не заметить, что количество зависимостей первого Add осталось таким же

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


                              Так что единственным оправданием подобного рефакторинга (в его правильном варианте, конечно) может быть только появление потребности к переиспользованию метода Add(int, int), причем не гипотетической "когда-то в будущем", а реальной, прямо сейчас.

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

                                Спасибо, этот момент я понял, учту на будущее.


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


                                В примере класс Damage представляет из себя сущность, основанную на DnD-правилах броска костей, т.к. бросок кости с N граней M раз даёт нормальное распределение, что, в принципе, неплохо для баланса.
                                Сам класс Damage зависит от класса Dice, как и, например, CriticalChance.


                                Идея DamageMediator была в том, что он являлся посредником между сложной структурой контейнера Player и различными компонентами вроде Stats, Weapon, Equipment (например, кольца, влияющие на урон, тоже там хранились бы).


                                Когда модуль битвы хотел бы посчитать урон, который конкретно сейчас может нанести игрок (без учёта защиты оппонента и прочего), он бы, соответственно, обратился к медиатору игрока, а далее уже модифицировал значение урона в зависимости от различных защитных характеристик оппонента (скажем, из DefenceMediator).


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


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


                                Приведу пример:


                                public static class DamageCalculationService
                                {
                                  public static int Calculate(Damage damage, Chance criticalChance)
                                  {
                                    // такой же код.
                                  }
                                
                                  public static int Calculate(Weapon weapon, Chance criticalChance)
                                  {
                                    // здесь, скажем, учитываются свойства оружия (кинжал это или нет).
                                  }
                                }

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


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

                                • 0
                                  Более того, как вы уже поняли, компонентная система позволяет модулю битвы вообще не заботится о том, кто наносит урон и кому.

                                  … вообще-то, для этого достаточно обычного старого доброго ООП, компоненты для этого вообще не нужны.


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

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


                                  Но даже если есть необходимость ее переиспользования, совершенно непонятно, зачем ее куда-то выносить… но для этого давайте сначала определимся — вы в рамках объектно-ориентированной парадигмы мыслите, или в рамках функциональной?


                                  Я нахожу этот пример корректным

                                  Этот пример мог бы быть корректным в рамках функционального программирования. Вы этой парадигмой пользуетесь?

                                  • 0
                                    Я уже отвечал на этот вопрос в предыдущем комментарии: это необоснованно. Нет никакого смысла выносить "функцию" из места ее использования, если только нет очевидной необходимости ее переиспользования.

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


                                    Это всё, между тем, описано в "Совершенном коде", о котором вы так часто вспоминаете, а именно — Часть 2. Глава 7. Раздел 1. Разумные причины для выделения методов (с. 160).


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


                                    Этот пример мог бы быть корректным в рамках функционального программирования. Вы этой парадигмой пользуетесь?

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

                                    • 0
                                      Можете, пожалуйста, обосновать утверждение "нет никакого смысла"?

                                      Очень просто: покажите, какой смысл в этой операции есть, если никакого повторного использования вынесенного кода не подразумевается.


                                      При этом общая сложность исходного метода увеличилась — это видно по количеству допущенных ошибок.


                                      Ежели это не так, я предлагаю разобраться в этом моменте, по меньшей мере, мне любопытно, чем именно данный пример мог бы отличаться?

                                      В "чистом" ООП нет ни статических классов, ни статических функций — потому что все операции совершаются над внутренним состоянием объектов. В идеале.


                                      В вашем конкретном случае должне были бы возникнуть методы Damage.Calculate(CriticalChance) и Weapon.CalculateDamage(CriticalChance).

                                      • 0
                                        Очень просто: покажите, какой смысл в этой операции есть, если никакого повторного использования вынесенного кода не подразумевается.

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


                                        Итак. Есть ряд аргументов, свидетельствующих в пользу второго варианта (я про методы Add) против первого:


                                        1. Повысилась переиспользуемость как таковая. Возможность что-то переиспользовать уже побуждает другого программиста к переиспользованию. В противном случае ему бы пришлось самому искать и выносить работу в отдельный метод. Иными словами — проще перейти от состояния с 0 переиспользований к состоянию с 1.
                                          Это необходимо учитывать отдельно от факта того, что метод вообще в принципе можно переиспользовать, потому что он актуален даже при переходе от состояния с N переиспользований к N+1.
                                          Простым языком выражаясь: метод, который можно переиспользовать, получает очки в карму всякий раз, как кто-то его переиспользует, кроме того он получает дополнительное очко за первое переиспользование, потому что он выглядел соблазнительно и побуждал к переиспользованию.
                                        2. Самый же главный аргумент заключается в том, что две ранее связанные между собой ответственности были разделены, тем самым снизилась вероятность изменения первоначального Add. Теперь его версия отвечает исключительно за то, чтобы подготовить корректные данные, в то время как работа по сложению чисел перешла к новой версии Add. И хотя пример со сложением чересчур упрощён, даже в нём ясно прослеживается преимущество разделения: проще тестировать, строго очерченные границы функциональности, ранее размытые, и, скажем, проще разобраться.

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


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


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


                                        В "чистом" ООП нет ни статических классов, ни статических функций — потому что все операции совершаются над внутренним состоянием объектов. В идеале.
                                        В вашем конкретном случае должны были бы возникнуть методы Damage.Calculate(CriticalChance) и Weapon.CalculateDamage(CriticalChance).

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


                                        Было бы интересно от вас услышать комментарий по поводу того, чем один вариант "хуже/лучше" иного, если опустить формальности наименований и используемых парадигм.

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

                                          Это не важно. Они случились.


                                          Повысилась переиспользуемость как таковая.

                                          Переиспользуемость "когда-то в будущем" не важна. Вы не знаете, будет она, или нет, какие условия у нее будут, насколько метод вам подойдет. Иными словами, вы делаете работу, нужность которой недоказуема. Так что нет.


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

                                          С точки зрения пользователя, есть ровно одна функция — Add(int?, int?), вероятность изменения которой не поменялась. То, что вы разбили ее на две функции, каждая из которых имеет свою вероятность изменения, его не волнует.


                                          И хотя пример со сложением чересчур упрощён, даже в нём ясно прослеживается преимущество разделения: проще тестировать,

                                          Проще? Протестируйте первый (проксирующий) метод в отрыве от второго. Можете? Нет. Значит, чтобы протестировать первый метод, вам придется написать все сценарии от второго, плюс сценарии от первого. Иными словами, вам придется повторить все сценарии тестирования второго метода дважды. Так что тестировать проще не стало.


                                          Мне кажется, что сложность в общем понизилась

                                          Да?


                                          Вот смотрите, у вас был первый вариант, который имел (грубо) следующий контракт:


                                          int x, y;
                                          Add(null, null).Should().Be(0);
                                          Add(x, null).Should().Be(x);
                                          Add(null, y).Should().Be(y);
                                          Add(x, y).Should().Be(x + y);

                                          Ваш второй вариант этому контракту удовлетворяет (иначе какой это, нафиг, рефакторинг).


                                          Теперь давайте скажем, что у нас поступило новое требование, и
                                          "сложение" двух чисел всегда должно округлять до десятка. Т.е., Add(2, 7).Should().Be(10). Конечно же, мы правим метод Add(int, int), потому что логика "сложения" — она в нем.


                                          Прогоняем тесты на контракт… и второй и третий тесты падают:


                                          Add(2, null).Should().Be(2); // FAIL: 0 != 2
                                          Add(null, 7).Should().Be(7); // FAIL: 10 != 7

                                          Ээээ, что?! Приятного дебага.


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

                                          То, что вы их не видите, означает, что вы не понимаете разницы между объектно-ориентированной и функциональной парадигмой.


                                          В частности, метод объекта может опираться на любые внутренние детали реализации Weapon и Damage в то время как функция над двумя объектами — только на их публичный контракт. Соответственно, разные возможности по инкапсуляции.


                                          если опустить формальности наименований и используемых парадигм.

                                          Вот понимаете, для вас наименование — это формальность. А для меня это первое, на что я смотрю — потому что именно по наименованиям я читаю код.

                                          • 0
                                            Конечно же, мы правим метод Add(int, int), потому что логика "сложения" — она в нем.

                                            Прошу прощения, но там логика "сложения", и к "округлению" она не имеет никакого отношения. Вы пытаетесь внести конкретику не того уровня в абстрактный пример.

                                            • 0

                                              Какой пример — такая и конкретика. Внесение округления в математические операции — это очень частое бизнес-требование. А куда в приведенном вами примере нужно вносить такое изменение?

                                              • 0
                                                Какой пример — такая и конкретика.

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


                                                А куда в приведенном вами примере нужно вносить такое изменение?

                                                Это уже выходит за рамки примера, но если на вскидку, то выделяется новый метод RoundTo.


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

                                                • 0
                                                  Конкретика уровня ниже, чем оговорённый для примера уровень абстрактности.

                                                  А как вы этот уровень посчитали, напомните?


                                                  Это уже выходит за рамки примера, но если на вскидку, то выделяется новый метод RoundTo.

                                                  Который вызывается откуда? Можете привести новый код метода Add(int?, int?)?


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

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

                                                  • 0
                                                    … я, собственно, все пытаюсь вам продемонстрировать, что ни того, ни другого вы не достигли.

                                                    Ради экономии времени давайте отойдём от примера, и вы покажете, как алгоритмическая декомпозиция метода, дробление на мелкие составляющие, каждая из которых занята своей работой, его усложняет?

                                                    • 0

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


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


                                                      В-третьих, формально, исходный метод уже состоит из мелких составляющих, каждая из которых занята своей работой. Другое дело, что эти составляющие слишком мелки, и вы, наоборот, занимаетесь их укрупнением: из x + y вы делаете Add (из трех мелких — один крупный, если грубо).


                                                      На самом деле, "дробление на мелкие составляющие" — это, грубо говоря, вот так (сделаем вид, что класса Uri не существует):


                                                      //было
                                                      var url = "http://habrahabr.ru/post";
                                                      var host = url.Substring(url.IndexOf("://" + 3)).Split('/', 2)[0];
                                                      var ip = Dns.GetHostEntry(host).AddressList[0].MapToIPv4().ToString();
                                                      
                                                      //стало
                                                      var url = "http://habrahabr.ru/post";
                                                      var host = GetHostNameFromUrl(url);
                                                      var ip = GetIPv4ByHost(host);

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

                                                      • 0
                                                        из x + y вы делаете Add (из трех мелких — один крупный, если грубо).

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


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

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


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


                                                        Ещё вопрос: что такое "когнитивная дистанция"?


                                                        И ещё: к какой категории вы относите случаи, когда вместо комментирования кода, он выносится в метод с декларативным названием? Это описано, кажется, у Мартина Фаулера в книге про рефакторинг.

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

                                                          … но это (потенциальное) упрощение сделано за счет того, что много мелких составляющих вы заменили одной более крупной.


                                                          верно ли сказать, что, когда мы выносим "как" в "что", мы не только улучшаем читаемость исходного метода, но и создаём независимый от места использования кусок функциональности, что, в целом, лучше.

                                                          Нет.


                                                          Ещё вопрос: что такое "когнитивная дистанция"?

                                                          Умозрительная метрика, позволяющая грубо оценить, насколько сложно переключаться между разными семантическими пластами в коде. Грубо говоря, "достать из строки символы 5-8" — это один семантический пласт, а "достать из uri имя хоста" — это другой, "отправить емейл на адрес" — третий, а "отправить уведомление пользователю" — четвертый. Переключение между пластами стоит усилий — поэтому для чтения выгодно, когда весь код подряд оперирует терминами из одного семантического пласта.


                                                          И ещё: к какой категории вы относите случаи, когда вместо комментирования кода, он выносится в метод с декларативным названием?

                                                          Это ровно то, что я проделал в своем примере выше.

                                                          • 0
                                                            Нет.

                                                            Почему?

                                                            • 0

                                                              Потому что не всякое вынесение создает независимый кусок функциональности, и не всякое разбиение на куски лучше, чем целое.

                                                              • 0
                                                                Потому что не всякое вынесение

                                                                Не всякое вынесение "как" в "что"?


                                                                Хорошо, а если детализировать.
                                                                Положим, есть метод подсчёта… например… Топ N пользователей по определённому критерию. Допустим также для примера, что он реализован так:


                                                                Инициализировать результирующий список с N элементами
                                                                
                                                                Пройтись по ВСЕМ пользователям
                                                                  Посчитать баллы критерия по текущему пользователю
                                                                
                                                                  Если баллы больше, чем первый элемент результирующего списка
                                                                    Сместить все элементы списка вправо
                                                                    Записать новый элемент вместо первого
                                                                
                                                                Конец цикла
                                                                
                                                                Вернуть результирующий список

                                                                Видно, что кусок "Сместить все элементы списка вправо" может лежать, по меньшей мере, в отдельном методе, а по большей — как метод расширения (если .NET) для массива.


                                                                Это упрощение или нет?

                                                                • 0
                                                                  Не всякое вынесение "как" в "что"?

                                                                  Да. И вообще (и тем более) не всякий introduce method refactoring имеет благоприятные последствия.


                                                                  Это упрощение или нет?

                                                                  Скорее всего да. Но зависит от того, как этот кусок выглядел изначально.


                                                                  (И, что вероятнее, существенно большее упрощение будет, если выделить сущность "ограниченная сортированная куча", и пользоваться ей.)

                                                                  • 0

                                                                    Что ж, можно заключить, что мы пришли к согласию.


                                                                    Спасибо за пояснения и комментарии, я много узнал и многое понял.

                                          • 0

                                            Отдельно рекомендую подумать над вот таким "рефакторингом":


                                            public static int Add(int? x, int? y)
                                            {
                                              if (!(x.HasValue && y.HasValue))
                                              {
                                                if (x.HasValue)
                                                  return x.Value;      
                                                if (y.HasValue)
                                                  return y.Value;      
                                                return 0;
                                              }
                                            
                                              //весь остальной многокод про сложение
                                            }

                                            PS


                                            match (x, y) with
                                            | (None, None) -> 0
                                            | (Some(x), None) -> x
                                            | (None, Some(y)) -> y
                                            | (x, y) ->
                                              //весь остальной многокод про сложение
                                            • 0

                                              … ну или если не любить вложенные скобки, то:


                                              public static int Add(int? x, int? y)
                                              {
                                                if (!x.HasValue && !y.HasValue)
                                                  return 0;
                                              
                                                if (!y.HasValue)
                                                    return x.Value;      
                                              
                                                if (!x.HasValue)
                                                    return y.Value;      
                                              
                                                //весь остальной многокод про сложение
                                              }

                                              Но здесь очень много отрицаний, а отрицания затрудняют чтение.

                                        • 0

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


                                          К тому же у вас уже создан DamageMediator, единственная обязанность которого — расчитывать урон. Соответственно название надо изменить на DamageCalculator и названием метода на Calculate. Введение еще одного уровня абстракции не упрощает дело, а только усложняет. Представьте себе, что для вычисления понадобится еще один параметр — придется менять DamageMediator и DamageUtil.

                                          • 0

                                            Спасибо за комментарий!


                                            С вашими утверждениями я вполне согласен, как раз читаю сейчас Макконнелла и нахожу примерно то же самое. В частности вариант с DamageCalculator и Calculate — действительно очень хорошая абстракция.


                                            Представьте себе, что для вычисления понадобится еще один параметр — придется менять DamageMediator и DamageUtil.

                                            Надо сказать, что меняться они будут в разных смысловых категориях: первый научиться доставать новый компонент, а второй — реализует ещё одну версию метода просчёта урона с учётом нового параметра. Например, при расчёте урона с учётом бонусов от оружия и характеристик надо будет одновременно совместить и характеристики, и оружие, и урон, и шанс критического удара. С точки зрения ООП не совсем понятно, куда это корректно положить. С другой стороны stateless статический метод выглядит вполне верно, хотя и не как наилучшая абстракция. По крайней мере он скрывает то, как именно влияют параметры на результирующий урон, а человек, тестирующий метод, сможет согласно документации легко всё проверить. Мне это так видится, но не уверен, что правильно.


                                            Признаю, конечно, — сам пример с просчётом урона был выбран неудачно и, как видно, вызывает много вопросов.

                                            • 0
                                              Надо сказать, что меняться они будут в разных смысловых категориях: первый научиться доставать новый компонент, а второй — реализует ещё одну версию метода просчёта урона с учётом нового параметра

                                              Для этого в оба элемента надо будет добавить этот новый компонент. Но зачем это делать — непонятно. Получается, что они будут тесно свзаны (High coupling).


                                              Признаю, конечно, — сам пример с просчётом урона был выбран неудачно и, как видно, вызывает много вопросов.

                                              Рекомендую прочесть серию постов Naming is a process и попробовать применить то, что там написано к примеру расчета урона, обратив внимание на то, чтобы код хорошо читался

                                              • 0
                                                Для этого в оба элемента надо будет добавить этот новый компонент. Но зачем это делать — непонятно. Получается, что они будут тесно свзаны (High coupling).

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


                                                По идее, всегда будут, по меньшей мере, расширяться два места:
                                                1) место, откуда достаются и формируются данные для просчёта;
                                                2) место, которое считает значение на основании данных.


                                                Если у вас есть какие-то быстрые соображения по этому поводу — пожалуйста, буду рад выслушать.


                                                Рекомендую прочесть серию постов Naming is a process и попробовать применить то, что там написано к примеру расчета урона, обратив внимание на то, чтобы код хорошо читался

                                                Спасибо, обязательно прочитаю!

                                                • +1
                                                  По идее, всегда будут, по меньшей мере, расширяться два места:
                                                  1) место, откуда достаются и формируются данные для просчёта;
                                                  2) место, которое считает значение на основании данных.

                                                  Если оба этих "места" находятся в одном классе, а изменение логики не преполагает добавления новых данных (например: раньше считали попадание от STR, начали считать попадание от STR и DEX — но DEX и раньше была у PC, просто не учитывалась) — понадобится только изменить метод.


                                                  Если у вас нормальная развитая система dependency injection, то добавление новой зависимости в расчет тоже будет влиять только на метод расчета (технический код, отвечающий за прокидывание зависимости, мы не считаем, он тривиальный).

                                                  • 0

                                                    Количество "если" нивелирует смысл всех вопросов и рассуждений.


                                                    "Если вы пишите код так, чтобы он легко расширялся, он будет легко расширяться".


                                                    Ответьте, пожалуйста, на вопрос, что делать, если вдруг для просчёта урона надо учитывать эффект от кольца… надетого на персонаже члена группы?


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

                                                    • 0

                                                      Проще всего для этого использовать rule-based-механизм, где правила имеют доступ ко всему контексту хода. Ну и тогда там будет банальное party.SelectMany(p => p.ActiveEquipment).OfType<IRing>().


                                                      Но в принципе, можно и от оружия попрыгать: this.Character.Party — далее аналогично.

                                                      • 0

                                                        Не понял.


                                                        Где в вашем примере просчёт урона затем происходит и какие данные он использует?

                                                        • 0

                                                          В зависимости от выбранной модели (я уже описывал их в одном из ранних комментариев), либо у нас есть IDamageCalculationRule.Calculate(weapon, target), который опирается на все возможные знание об игровом поле, либо же IDamageDealer.CalculateDamage(target) (кстати, первому ничто не мешает внутри вызывать второе, но это уже отдельный вариант).


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

                                                          • 0

                                                            Насколько я понимаю, IDamageDealer — это интерфейс, который в вашем примере может реализовывать оружие, являясь (что правда), наносителем урона.


                                                            Не до конца разобрался в том, что конкретно делает абстракция CalculateDamage и как она сделает так, чтобы на оружие влияли предметы, характеристики, что угодно?


                                                            Или вы предлагаете сделать так, чтобы урон вычислялся в различных сущностях отдельно, а затем аккумулировался по некоторым правилам, скажем, аддитивно или мультипликативно (которые, соответственно, в ином месте лежат)?

                                                            • 0
                                                              Не до конца разобрался в том, что конкретно делает абстракция CalculateDamage и как она сделает так, чтобы на оружие влияли предметы, характеристики, что угодно?

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


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


                                                              Если "через правила", то будет взято "обобщенное правило расчета урона от оружия и цели", оно запросит дополнительные правила (модификаторы) у всех существующих в сцене сущностей, применит их согласно игровой логике и отдаст обратно.

                                  • 0
                                    По DDD stateless методы бизнес-логики, явно к сущностям и т. п не относящиеся — это Domain Service. Статическими их делать или обычными — компромисс между гибкостью и скоростью. Нормальное название DamageCalcService или DamageCalculator.
                        • +1
                          Можно ли пытаться количественно оценивать код, полностью опуская своё человеческое начало или же это невозможно?

                          Собственно, ваш код — иллюстрация того, почему это бессмысленно. С точки зрения количественной метрики нет разницы между q = A.Next() и attackDamage = currentPlayer.Attack(selectedTarget) — но вот с точки зрения программиста, читающего код, разница фундаментальная.

                          • 0
                            На самом деле, я руководствовался простой логикой: методы класса Random имеют наименование Next, что означает, что каждый раз будет генерироваться отличное от предшествующего число.

                            Для генерации псевдослучайных чисел это название оправдано, потому что следующее число зависит от предыдущего по формуле.

                            • 0

                              Спасибо за пояснение! Я ошибался.)))

                        • +1

                          Напомнило диплом и прочие институтские "особенности" — многовато теоретической теории и обоснования всем известных фактов. Некоторые термины и положения, приведенные в начале вообще неизвестно зачем даны. Разделить бы эту статью на несколько маленьких статеек поконкретнее и примеров побольше в каждую. Объявление терминов и положений засунуть в Lazy)

                          • 0
                            Спасибо за комментарий!

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

                            Думаю, если приступлю писать вторую часть, то обязательно разъясню все моменты и реализую больше непосредственно расчётов и формул (если получится). Или даже, как вы посоветовали, разобью всё на маленькие части.
                            • 0
                              Тем не менее, Ваша манера излагать, импонирует. Я думаю не только мне. Пишите сударь, больше и глубже!
                              • 0

                                Что же, спасибо на добром слове!


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

                          • –1
                            Финальным штрихом будет вынесение статической функции просчёта урона во вспомогательный класс

                            DamageUtil

                            Я когда делаю codereview все классы оканчивающиеся на Utils и Helpers считаю дефектами.
                            • 0

                              Тут наминг просто адский и ничего непонятно. Почему Mediator, если он вычисляет урон, а не Calculator что такое CriticalChance? и почему Next вычисляет урон и его возвращает? Почему именно Next?

                              • 0

                                Спасибо за комментарий. Ниже я уже дал пояснение касаемо общих архитектурных соображений примера.

                            • –2
                              Да, под веществами после Виттгенштейнов с Курцвелями и не такое придумать можно…
                              • 0
                                смог осилить только первый том данного ноучного труда, и сразу полез в коментарии — удостовериться, один ли я настолько тупой, что не понимаю нах… на зачем вот это вот все писать???
                                зачем увеличивать и без того зашкаливающую энтропию информационного пространства???

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

                                    Спасибо за комментарий!


                                    Можете, пожалуйста, объяснить, где была допущена не точность в понимании Single Responsibility? Буду премного благодарен.

                                • 0
                                  Подумал — не первый раз приходится читать какие-то теории со своим понятийным аппаратом, естественно, что выстраданная автором терминология сразу не запоминается, нужен механизм подсказок — навёл на термин всплыло авторское определение.
                                  • 0
                                    Пример я поленился разбирать, но идея формализации критериев качества кода интересна, вот только формализацией это станет после того как будет сделан программный инструмент, который сможет сам вычислить все эти метрики и поставить оценку коду.
                                    • 0
                                      > Dependency Inversion Principle — принцип инверсии зависимостей. Для меня один из самых труднопонимаемых принципов

                                      А что в нем непонятного? Как Вы его понимаете?

                                      > Логичность и истинность данного принципа напрямую следует из Положения 1.3.1.1: вероятность изменения абстракции ниже, чем вероятность изменения конкретного блока-реализатора.

                                      Из этого тезиса следует, что достаточно добавить интерфейс для существующей конкретной реализации и зависеть от него. Но этого недостаточно.

                                      Главное в этом принципе — более общее, находящееся выше по стеку абстракций, не должно зависеть от менее общего. Собственно, это есть в определении :).
                                      • 0
                                        А что в нем непонятного? Как Вы его понимаете?

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


                                        Если класс A использует класс B, то вероятность изменения класса A составляется из: вероятности изменения требований к классу A, вероятности изменения требований к классу B, вероятности изменения тех методов, что используются классом A.


                                        Если инвертировать зависимость от A к B, внедрив между ними абстракцию IC, то получится, что теперь вероятность изменения A составляет: вероятность изменения требований к классу A, вероятность изменения IC. Идея в том, что B теперь тоже зависит от IC, т.к. реализует этот интерфейс. Оба класса зависят от абстракции и от налагаемых ею ограничений. Наверное, я упустил разъяснить в статье этот момент, однако очевидно, что теперь, в случае изменения B, которое сломает корректность реализуемой абстракции, разгребать это придётся классу B.


                                        Есть ощущение, что я где-то допустил неточность, поэтому поправьте, пожалуйста.

                                        • 0
                                          Собственно, все так. Но вся соль в том, чтобы правильно выбрать эту новую абстракцию, а это уже чисто практический аспект.
                                      • 0

                                        Определенные метрики у кода существуют, например, ciclomatic complexity для метода, класса; количество зависимостей; количество copy paste; в конце концов, покрытие кода тестами. Если достаточно исследовать вопрос существующих метрик (для многих языков есть инструменты автоматического анализа), то этого будет достаточно для выявления разницы "хороший/плохой", "красивый/некрасивый" код.


                                        Ну а стиль изложения прекрасен, за стилистику текста и "энтропию" отдельное спасибо.

                                        • 0

                                          Спасибо за комментарий!


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


                                          Возможно, как раз именно в них и нет необходимости. Необходимость есть скорее в том, чтобы объяснить, как и из чего складывается "хорошая" архитектура: от каких величин зависит, как её можно измерить, в конце концов на чём зиждется её "мироздание". Существует уйма аббревиатур вроде SOLID, KISS, GRASP, YAGNI, DRY (больше не знаю), которые, кажется, нужны только для того, чтобы в резюме занимать место наряду с джаваскрипт-фреймворками (да простит мне комьюнити шутку). Безусловно, суть таких принципов ясна и уловима, однако мне, возможно очень субъективно, кажется и хочется верить, что всё можно изучить и понять гораздо глубже: нащупать почву, на которой стоит этот исполинский бастион.

                                          • +1
                                            Безусловно, суть таких принципов ясна и уловима, однако мне, возможно очень субъективно, кажется и хочется верить, что всё можно изучить и понять гораздо глубже: нащупать почву, на которой стоит этот исполинский бастион.

                                            … а почва эта описана, например, у МакКоннела, которого вы не читали, и сводится к простому тезису: одна из основных (если не основная) задач при проектировании/написании кода — это управление сложностью (ее минимизация). А принципы, на которые вы ссылаетесь — это проверенные опытом способы решения этой задачи.

                                            • –1
                                              … а почва эта описана, например, у МакКоннела, которого вы не читали, и сводится к простому тезису: одна из основных (если не основная) задач при проектировании/написании кода — это управление сложностью (ее минимизация). А принципы, на которые вы ссылаетесь — это проверенные опытом способы решения этой задачи.

                                              Благодаря вашим настоятельным советам (завуалированным), я уже прочитал четверть книги. Спасибо!


                                              Насчёт "минимизации сложности" и "Главного технического императива разработки ПО" могу сказать лишь одно: видавшую виды Бритву Оккама можно было и попроще выразить.)))


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

                                              • 0
                                                Насчёт "минимизации сложности" и "Главного технического императива разработки ПО" могу сказать лишь одно: видавшую виды Бритву Оккама можно было и попроще выразить

                                                Кажется, прочитали, но еще не поняли. Управление сложностью сложнее (простите) бритвы Оккама ("Не следует множить сущее без необходимости").

                                                • –1

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


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

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

                                                    … скажем, правило "именование должно быть семантически корректным" не имеет никакого отношения к бритве Оккама. То есть вообще никакого. Или введение абстракций.

                                                    • 0
                                                      не имеет никакого отношения к бритве Оккама. То есть вообще никакого.

                                                      Мне кажется, семантически некорректное наименование есть часто форма умножения сущностей. В приведенном примере, например, вводится понятие медиатора, причем сразу же вводится пояснения что медиатор есть такая штука, которая вычисляет.

                                                      • +1

                                                        Это уже демагогия. Сущность есть, а поименовали ее "медиатор", "калькулятор", "утилита" или "сервис" — бритве все равно. Бритве не все равно, есть ли эта сущность, но это тема другого разговора.

                                                        • 0

                                                          Мне кажется в данном случае наименование есть отражение мышления — если человек придумывает новые термины при существовании старых, он не до конца понимает что это одно и то же и придумывает новую сущность.

                                                          • +1

                                                            Именование, конечно, отражение мышления — но когда человек называет метод, рассчитывающий нанесенный вред, Next, ни о каком "старом термине" (в пространстве программы) речи не идет.

                                                            • 0

                                                              Да, я именно о том, что человек выдумывает термины специально для программы, когда можно этого не делть, а просто взять их из предметной области. Типа ubiquitous language.

                                                              • 0

                                                                Так для этого же надо предметную область анализировать. А это вопрос требований, и уууууууууу.....

                                                                • 0
                                                                  Типа ubiquitous language.

                                                                  Одна из самых сложных стадий проекта. Ещё выделить контексты.
                                                        • –1
                                                          … скажем, правило "именование должно быть семантически корректным" не имеет никакого отношения к бритве Оккама. То есть вообще никакого. Или введение абстракций.

                                                          Здесь вы в праве как согласиться, так и нет. Я не зря выделил слово "форма", и как по мне "принцип минимизации сложности", т.е. Главный Технический Императив — это форма бритвы Оккама в разработке ПО. То, что данный императив далее выливается в различные конкретные принципы — иной разговор.


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

                                                          • –1

                                                            Простите, но это демагогия. Если вы не видите разницы между "не множить сложное", "не умножать сущности" и "минимизировать сложность" — я не вижу смысла ее с вами обсуждать.


                                                            Я понимаю, что очень хочется свести чужую практику к простой фразе, но поверьте, все совсем не так тривиально.

                                                            • –1

                                                              Вы трактуете Бритву Оккама "буквально", используя формулировку русскоязычной Википедии, совершенно не беря во внимание следующие факторы:
                                                              1) это перевод;
                                                              2) принцип имеет корни в 4 веке до нашей эры (Аристотель: "We may assume the superiority ceteris paribus [other things being equal] of the demonstration which derives from fewer postulates or hypotheses.");
                                                              3) существует ряд трактовок, основанных на понятии простоты. ("Prior to the 20th century, it was a commonly held belief that nature itself was simple and that simpler hypotheses about nature were thus more likely to be true.").


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


                                                              Интересно, использовали ли бы вы фразу "умножать сущности", если бы прочитали англоязычный вариант перевода: "Among competing hypotheses, the one with the fewest assumptions should be selected.". Я уже не говорю о том, что версия на латыни буквально переводится как "закон экономии".


                                                              Могу предположить, что вы бегло пробежались по описанию в Википедии, и принялись обсуждать то, что восприняли "буквально". Но это домысел, догадка.


                                                              Я понимаю, что очень хочется свести чужую практику к простой фразе, но поверьте, все совсем не так тривиально.

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


                                                              Кроме того, в данной фразе вы:
                                                              1) выдвинули предположение о моих намерениях в виде утверждения с заранее определённой истинностью ("очень хочется свести чужую практику к простой фразе");
                                                              2) охарактеризовали мои предыдущие утверждения независимо от их сути ("всё совсем не так тривиально").


                                                              Таким образом, отсюда, по моему скромному мнению, вы использовали следующие демагогические приёмы:


                                                              1. Подмена тезиса (про свод чужой практики к простой фразе).
                                                              2. Концентрация на частностях (слово "сущности" в одном из вариантов перевода Бритвы Оккама).
                                                              3. Апелляция к очевидности ("Если вы не видите разницы между "не множить сложное", "не умножать сущности" и "минимизировать сложность" — я не вижу смысла ее с вами обсуждать.").

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

                                                        • 0
                                                          ибо полагается, что истина в простоте.

                                                          Скорее полагается, что если простая гипотеза в качестве рабочей даёт те же практические результаты, что и сложная, то нет никаких объективных причин использовать сложную. Наука не оперирует истинами, а только фактами и теориями/гипотезами их обїясняющими.
                                              • 0
                                                Я искренне убеждён, что код — не искусство, это строгие, поддающиеся анализу, структуры, и мне не видится эффективным ориентироваться на рефлексивные ощущения «красоты»


                                                Жёсткая заявка. Я склонен не согласиться. Архитектура, имхо — это то, благодаря чему программирование будет оставаться искусством всегда. Это нечто на стыке философии и инженерии. При этом как в философии, так и в инженерной деятельности важно умение смотреть на задачу неформально, всегда под новым углом в поиске новых решений. Больше того — даже если есть совершенное решение в какой-то области (например, самолёты и машины уже практически обрели идеальное воплощение) всегда будут возникать новые области (там, электромобили, у которых другие принципы построения могут быть или даже какие-нибудь ракеты для гражданских лиц), формализация которых может потребовать новых подходов к проектированию в принципе. И в этом смысле базовая метафора статьи: «Метафора 1. Иерархично всё» несколько упрощённо рассматривает устройство мира и, как следствие, упрощает особенности проектирования дизайна кода.

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

                                                Философские разглагольствования
                                                Архитектура кода рассматривалась в дипломе как инструмент систематизации знаний о реальном мире. Парадигмы (ООП, ФП, и т.д.) диктуют логику превращения понятий из предметной области в программные сущности, а задача задаёт степень конкретизации предметных областей, отображаемых в код в рамках программой модели. Рефакторинг рассматривался как эволюция знаний: анализ, поиск закономерностей с учётом новых фактов, поиск понятий, которые следует уточнить или пересмотреть с учётом новых знаний — и, в конце, интеграция знаний в существующую систему… Так вот, к чему это всё… Не может быть никаких формальных правил, которые в 100% или даже в 80% позволят сформулировать оптимальный способ отображения модели реального мира с (важно!) нужной степенью конкретизации. Сам по себе процесс принятия решения того, какие именно понятия отображать в сущности и концепции программной модели — процесс весьма нетривиальный и близок, опять-таки, к философской деятельности.


                                                P.S.: Кстати, если уж пытаться подкладывать какую-нибудь математическую основу под рассуждения об архитектуре — лучше использовать теорию категорий. Вроде, даже встречал некоторое время назад какие-то статьи про использование теории категорий для тривиального рефакторинга кода.
                                                • 0
                                                  Жёсткая заявка. Я склонен не согласиться. Архитектура, имхо — это то, благодаря чему программирование будет оставаться искусством всегда.

                                                  Забавно, но это то, как я думал, когда только начинал свой путь. Сейчас же, спустя года два с лишним, могу сказать, что поменял своё мнение.


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


                                                  Во-первых, хочу сказать, списывая у Шопенгауэра, что искусство — это всегда и в первую очередь познанное непосредственно и интуитивно, т.е. in concreto.
                                                  Во-вторых, источник признания искусства всегда покоится в самом человеке.


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


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

                                                • 0
                                                  Статья интересная, но приведённый пример ужасен. Рефакторинг ради рефакторинга.
                                                  Принципы SOLID стоит рассматривать в контексте изменений требований:
                                                  S. Если за одно требование отвечает несколько сущностей (методов, классов, модулей), то при изменении требований придётся проверять их все. Если одна сущность имеет несколько ответственностей, то при изменении надо проверить, что изменение не затронет другую ответственность.
                                                  O. Если в одном месте понадобится изменить поведение компонента, то стоит создать наследника с нужным поведением и передать его.
                                                  L. Гарантирует, что все остальные части будут взаимодействовать с изменённым компонентом также.
                                                  I. Меньше знаешь — крепче спишь. Меньше вероятность, что одни изменения повлекут другие.
                                                  D. Благодаря ему мы можем передать наследника из второго принципа.
                                                  Подобные принципы локализуют изменения и уменьшают их количество.
                                                  В примере автора любое изменение затронет оба метода: например, множитель критического урона определяемый типом оружия.
                                                  Пример из комментариев тоже неудачен, его приводят, когда описывают монады, и там он разобран лучше.
                                                  • 0

                                                    Спасибо за комментарий и разъяснения по поводу SOLID.


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

                                                    На самом деле нет. По крайней мере это верно для множителя критического урона. Т.к. за эту область сейчас ответственнен метод DamageUtil.Next, то он и изменится. Метод DamageMediator.Next изменится лишь косвенно.

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

                                                      Спасибо за комментарий!


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


                                                      У меня, например, было так: у оружия существует набор свойств (это всё основано на ДнД отчасти). Скажем, "тяжёлое" (Heavy) означает, что в расчёте урона участвует некоторый модификатор. Есть ещё "искуссное" (Finesse), что отвечает, если мне память не изменяет, за то, что на результат начинает влиять одна из характеристик (наибольшая): ловкость или сила, которые хранятся в компоненте Stats.


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


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


                                                      Возьмём в пример игру World of Warcraft с её рейтингом устойчивости (на момент патча 3.3.5а информация актуальна). Рейтинг устойчивости персонажа снижает наносимый по нему урон (это мы пока можем посчитать), шанс критического удара (уже не можем) и модификатор критического удара (фактор увеличения урона при крите). Последние два свойства, увы, на данный момент совершенно невозможно посчитать ни в какой из реализаций.


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


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


                                                      Таким образом сформируется отдельная ответственность за комбинирование различных значений вместе.


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

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

                                                    Интересные публикации