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

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

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

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

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, которые были бы без строковых миксинов - таким образом, все безобразие сосредоточено внизу, и фактическое поведение функции легко читается с первого взгляда.

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

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

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.

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

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

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

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

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

Другие вопросы по тегам