Добрый день, Андрей!

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

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

 

«Заниматься переписыванием читабельной программы на нечитабельную — это, 
по-моему, как раз пример „антипаттерна“»

Согласен. Но если руководствоваться профилировщиком, корячить приходится не всю 
программу, а только условно 3 %.

 

«IMHO, это есть пример антипаттерна: повторное использование имени для нового 
значения. Я бы здесь обязательно написал е.Expr1. А то легко не понять при 
чтении и ошибиться при рефакторинге.»

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

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

Допустим, у нас есть некоторая сущность, которую нужно как-то обновить два раза 
и использовать:

F {
  … e.Entity …
    = <FirstUpdate e.Entity> : e.Entity^
    = <SecondUpdate e.Entity> : e.Entity^
    = <UseEntity … e.Entity …>
}

Допустим, нам теперь надо упростить структуру после обновлений. Добавляем одну 
строчку:

F {
  … e.Entity …
    = <FirstUpdate e.Entity> : e.Entity^
    = <SecondUpdate e.Entity> : e.Entity^
    = <Simplify e.Entity> : e.Entity^
    = <UseEntity … e.Entity …>
}

А теперь нам нужно между обновлениями привести её к канонической форме:

F {
  … e.Entity …
    = <FirstUpdate e.Entity> : e.Entity^
    = <Canonize e.Entity> : e.Entity^
    = <SecondUpdate e.Entity> : e.Entity^
    = <Simplify e.Entity> : e.Entity^
    = <UseEntity … e.Entity …>
}

А с нумерацией возни будет больше. Исходный вариант:

F {
  … e.Entity …
    = <FirstUpdate e.Entity> : e.Entity1
    = <SecondUpdate e.Entity1> : e.Entity2
    = <UseEntity … e.Entity2 …>
}

Теперь добавляем упрощение:

F {
  … e.Entity …
    = <FirstUpdate e.Entity> : e.Entity1
    = <SecondUpdate e.Entity1> : e.Entity2
   = <Simplify e.Entity2> : e.Entity3
    = <UseEntity … e.Entity3 …>
}

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

F {
  … e.Entity …
    = <FirstUpdate e.Entity> : e.Entity1
    = <Canonize e.Entity1> : e.Entity4
    = <SecondUpdate e.Entity4> : e.Entity2
   = <Simplify e.Entity2> : e.Entity3
    = <UseEntity … e.Entity3 …>
}

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

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

Зачем нумеровать вручную, когда это может сделать сам компилятор?

Эта цепочка присваиваний компилятором переписывается во вспомогательные 
функции. Если бы программист вручную писал вспомогательные функции, то никаких 
номеров бы не было:

F {
  … e.Entity … = <F-FirstUpdated … <FirstUpdate e.Entity>>;
}

F-FirstUpdated {
  … e.Entity = <F-Canonized … <Canonize e.Entity>>;
}

F-Canonized {
  … e.Entity = <F-SecondUpdated … <SecondUpdate e.Entity>>;
}

F-SecondUpdated {
  … e.Entity = <F-Simplified … <Simplify e.Entity>>;
}

F-Simplified {
  … e.Entity = <UseEntity … e.Entity …>;
}

Поэтому номера — отстой!

Если уж не хочется переопределять имена, или язык не позволяет, то лучше давать 
понятные имена разным версиям:

F {
  … e.Entity …
    = <FirstUpdate e.Entity> : e.Entity-FirstUpdated
    = <Canonize e.Entity-FirstUpdated> : e.Entity-Canonized
    = <SecondUpdate e.Entity-Canonized> : e.Entity-SecondUpdated
   = <Simplify e.Entity-SecondUpdated> : e.Entity-Simplified
    = <UseEntity … e.Entity-Simplified …>
}

К слову, в Рефале Плюс переменные в перестройке совершенно спокойно перекрывают 
имена. Можно сказать, крышка ^ там подразумевается неявно.

 

««А нет ли в Рефале Плюс другой боязни — боязни конкатенации?»»
«Нет.»

Ну а как тогда Enum будет записан на Рефале Плюс? Если его дословно переписать 
на Рефале Плюс, не будет ли там квадратичной сложности?

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

 

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

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

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

 

С уважением,
Александр Коновалов

 

From: Andrei Klimov andrei_AT_klimov.net <refal@botik.ru> 
Sent: Monday, December 14, 2020 11:50 AM
To: refal@botik.ru
Subject: Re: Запахи кода и антипаттерны в Рефале

 

пн, 14 дек. 2020 г., 10:46 Александр Коновалов a.v.konovalov87_AT_mail.ru 
<http://a.v.konovalov87_AT_mail.ru>  <refal@botik.ru <mailto:refal@botik.ru> >:

Доброе утро всем!

> 1.  Выворачивание скобок наизнанку, для организации прохода по выражению.

К представлению объектных выражений этот приём (антиприём) не имеет отношение.

 

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

 

Он, скорее, характерен для базисного Рефала (Рефала-2), в котором нет ничего 
подобного let-конструкциям (условия, перестройки, действия).

 

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

 

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

 

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

 

А нет ли в Рефале Плюс другой боязни — боязни конкатенации?

 

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

 

 

Enum {
  e.Expr
    = <DoEnum e.Expr 1> : e.Expr^ s.Num
    = e.Expr;
}

 

IMHO, это есть пример антипаттерна: повторное использование имени для нового 
значения. Я бы здесь обязательно написал е.Expr1. А то легко не понять при 
чтении и ошибиться при рефакторинге. 

 

Также избегаю повторения имени в популярных функ языках типа ML с let, ну, 
кроме совсем легко читаемых шаблонных случаев:

 

... 

let x = x+1 in

...

 

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

 

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

 

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

 

Андрей

  • Зап... Александр Коновалов a . v . konovalov87_AT_mail . ru
    • ... Sergei M. Abramov
      • ... Александр Коновалов a . v . konovalov87_AT_mail . ru
        • ... Andrei Klimov andrei_AT_klimov . net
          • ... Александр Коновалов a . v . konovalov87_AT_mail . ru
        • ... Василий Стеллецкий swi_AT_cnshb . ru
          • ... Александр Коновалов a . v . konovalov87_AT_mail . ru
            • ... Василий Стеллецкий swi_AT_cnshb . ru
              • ... Александр Коновалов a . v . konovalov87_AT_mail . ru
                • ... Василий Стеллецкий swi_AT_cnshb . ru
                • ... Александр Коновалов a . v . konovalov87_AT_mail . ru
                • ... Василий Стеллецкий swi_AT_cnshb . ru
                • ... Александр Коновалов a . v . konovalov87_AT_mail . ru
                • ... Arkady Klimov arkady . klimov_AT_gmail . com
                • ... nikolai . kondratiev_AT_gmail . com

Ответить