Является ли использование миксинов D-строки для повторного использования кода анти-шаблоном?

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

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

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


person dsimcha    schedule 21.07.2010    source источник


Ответы (3)


Все высказанные вами критические замечания справедливы.

Тем не менее, это все еще лучше, чем ручная копипаста.

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

  T to(T)() {
    static if (!is(T == Scope)) {
      T value;
      if (flatType == FlatType.ScopeValue) value = sr.value().to!(T);
    }
    const string Table = `
                 | bool          | int         | string               | float   | Scope
      -----------+---------------+-------------+----------------------+---------+----------
      Boolean    | b             | b           | b?q{true}p:q{false}p | ø       | ø
      Integer    | i != 0        | i           | Format(i)            | i       | ø
      String     | s == q{true}p | atoi(s)     | s                    | atof(s) | ø
      Float      | ø             | cast(int) f | Format(f)            | f       | ø
      ScopeRef   | !!sr          | ø           | (sr?sr.fqn:q{(null:r)}p) | ø   | sr
      ScopeValue | value         | value       | value                | value   | sr`;
    mixin(ctTableUnrollColMajor(Table,
      `static if (is(T == $COL))
        switch (flatType) {
          $BODY
          default: throw new Exception(Format("Invalid type: ", flatType));
        }
      else `,
      `case FlatType.$ROW:
        static if (q{$CELL}p == "ø")
          throw new Exception(q{Cannot convert $ROW to $COL: }p~to!(string)~q{! }p);
        else return $CELL;
      `
    ).litstring_expand() ~ `static assert(false, "Unsupported type: "~T.stringof); `);
  }

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

person FeepingCreature    schedule 21.07.2010
comment
принюхаться Я люблю тебя, Фип. Это действительно одна из ваших самых удивительных идей. - person DK.; 21.07.2010
comment
Каждый заслуживающий доверия полухитрый трюк, подобный этому, имеет три или четыре идеи, которые просто ужасны — как текстовый синтаксический анализатор графа конечного автомата CTFE, спрятанный глубоко в tools.base :) - person FeepingCreature; 22.07.2010

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

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

import std.stdio;

void main()
{
    mixin(hello());
}

string hello()
{
    return "
    writeln(\"hello world\");
";
}

Если бы мы убрали точку с запятой после writeln(), то получили бы ошибку

d.d(7): found 'EOF' when expecting ';' following statement

Миксин выполняется в строке 5. Строка 7 — пустая строка. Таким образом, номер строки здесь имеет ограниченную полезность. Теперь этот миксин достаточно короткий, чтобы мы могли поместить его в одну строку и заставить сказать, что ошибка находится в той же строке, что и миксин, но с более сложными миксинами это, очевидно, не сработает. Таким образом, при использовании строкового миксина ваша способность выяснить, где ошибка, снижается. Если код генерируется с использованием CTFE, то становится намного сложнее понять, как именно выглядит код, чтобы выяснить, что с ним не так. Это очень похоже на выяснение того, в какой код превращается макрос в стиле C, за исключением того, что это может быть хуже, потому что они могут быть сгенерированы, а не прямой заменой. Однако они не заменяют, за исключением случаев, когда вы явно указываете им, поэтому они намного безопаснее, чем макросы в стиле C.

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

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

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

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

person Jonathan M Davis    schedule 21.07.2010
comment
Я обнаружил, что единственный способ решить проблему с номерами строк — это вставить директивы #line в исходный код примеси строк. Таким образом, ошибки обычно попадали примерно в нужное место; сделал отладку всей библиотеки привязок, построенной из сгенерированных CTFE строковых миксинов, менее полным и совершенным кошмаром. - person DK.; 21.07.2010
comment
Знаете, вы можете просто написать функцию CTFE для удаления новых строк. ^^ - person FeepingCreature; 22.07.2010
comment
@FeepingCreature, лол. Милый. Это помогло бы со строкой в ​​сообщении об ошибке, указывающей строку, в которой смешивается строка, а также с сообщениями об ошибках позже в файле, но это не помогло бы найти ошибку в самом миксине. Конечно, поскольку вы даже не обязательно знаете, как выглядит смешиваемый код, знание того, какой номер строки в нем плохой, в любом случае не обязательно вам поможет. Я полагаю, что мораль этой истории в том, что строковые примеси, которые работают, великолепны, но исправлять глючные примеси может быть очень сложно. - person Jonathan M Davis; 22.07.2010
comment
Weeell .. точка принята, но в принципе говоря, как только вы знаете миксин, вы можете просто вернуть новые строки и вычесть номера строк. :-) - person FeepingCreature; 22.07.2010
comment
Однако, учитывая, что смешивание строк имеет тенденцию искажать номера строк ошибок для остальной части файла (даже если миксин в порядке), может быть хорошей практикой смешивать новые строки в миксине только для того, чтобы другие сообщения об ошибках разумны. - person Jonathan M Davis; 22.07.2010

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

person BCS    schedule 22.07.2010
comment
В этом есть доля правды, но я обычно избегаю goto как чумы, в то время как я вполне готов использовать строковые миксины в самых разных обстоятельствах. Теперь, если есть решения получше, чем строковые примеси, я обязательно возьму его, но я вполне готов поместить строковые примеси в свой код, пока я хорошенько подумаю и определенно пересмотрю свое решение, прежде чем на самом деле помещать goto в свой код. - person Jonathan M Davis; 23.07.2010
comment
@Jonathan M Davis: Мне кажется, что вы более неохотно используете goto, чем я. OTOH, очень мало случаев, когда вам нужно использовать goto в D с пометкой break и continue. - person BCS; 23.07.2010
comment
@BCS Я бы никогда не использовал goto ни на одном языке, если бы у меня не было другого выбора, чтобы получить необходимую мне производительность. Мне не очень нравятся помеченные продолжения или помеченные разрывы, но я был бы готов использовать их, если бы это имело смысл в конкретной ситуации. Но прямо идти? На это практически нет шансов. Если вам действительно не нужна производительность, всегда есть лучший способ. Так что, да, похоже, я более неохотно использую goto, чем вы. - person Jonathan M Davis; 23.07.2010
comment
@Jonathan: Что не так с пометкой продолжения/перерыва? Как еще (кроме goto) вы должны вырваться из вложенной структуры управления? - person dsimcha; 12.08.2010
comment
О, они могут быть очень полезными. Но код, как правило, намного чище, если он написан таким образом, что не требует таких прыжков. Я бы сказал, что в большинстве случаев код, который требует помеченного продолжения или прерывания, можно было бы переписать чище, не требуя их (и без таких вещей, как уродливые флаги, которые указывают, что вы должны просто выпасть из набора циклов, которые вы находитесь внутри - помеченные продолжения и разрывы, безусловно, лучше, чем код такого рода). Итак, я думаю, что язык должен их иметь, но если ваш код их использует, он должен быть рефакторинг, если это вообще возможно. - person Jonathan M Davis; 13.08.2010
comment
@Jonathan: @dsimcha: Самый простой способ избежать помеченных разрывов — обернуть внешний цикл в функцию и выполнить возврат. OTOH, который (если вам повезет) в конечном итоге возвращается к тому же самому. @Jonathan: Не могли бы вы привести пример того, о каком рефакторинге вы думаете? - person BCS; 13.08.2010
comment
@BCS Мне действительно нужно посмотреть на конкретные примеры, чтобы сказать, хотя рефакторинг некоторых из них в функцию - это один из способов. Во многих случаях это просто означает немного по-другому подойти к проблеме или разбить ее по-другому. Лично я редко сталкиваюсь с необходимостью разорвать внешний цикл, и когда я сталкивался, я обычно находил способы реорганизовать эту необходимость. Но это достаточно редко, чтобы у меня не было хороших примеров в голове. - person Jonathan M Davis; 13.08.2010
comment
Я тоже редко сталкивался с этим, но я никогда не чувствовал необходимости реорганизовывать его. - person BCS; 13.08.2010
comment
@BCS Это больше проблема в C ++, чем в D, потому что в C ++ вам нужно использовать какую-то переменную флага, поскольку в C ++ нет помеченных продолжений или прерываний. Меня бы не так сильно беспокоил рефакторинг в D, как в C++, но, вероятно, это все же достаточно беспокоило бы меня, чтобы реорганизовать его, если бы я мог разумно это сделать. - person Jonathan M Davis; 13.08.2010