7

对于那些不熟悉 D 字符串混合的人来说,它们基本上是编译时评估。您可以获取任何编译时字符串(无论是文字还是由模板元编程或编译时函数评估生成)并将其编译为代码。如果您使用简单的字符串文字,它基本上是编译器自动复制粘贴。

您是否认为在其他分解方法不太适合的情况下,使用文字的字符串混合作为简单代码重用的一种反模式?一方面,它基本上是编译器自动进行的文字复制和粘贴,这意味着一旦混合到实例中,它们之间就没有任何关系了。如果字符串 mixin 中的符号与混合范围内的符号冲突,将会发生坏事(尽管在编译时,而不是在运行时)。它是相对非结构化的,例如,可以将字符串混合到函数的中间,当且仅当范围内的变量根据特定约定命名时,该函数才会起作用。Mixin 还可以声明外部作用域可以根据需要使用的变量。

另一方面,因为复制和粘贴是编译器自动化的,所以在源代码级别有问题的代码只有一个事实点,如果需要修改,只需要在一个地方修改,一切都保持同步。字符串混合还极大地简化了代码的重用,这些代码很难以任何其他方式进行考虑,否则很有可能被手动剪切和粘贴。

4

3 回答 3

11

你提出的所有批评都是真实的。

无论如何,它仍然优于手动复制粘贴。

实际上,我的工具库中运行了类似的东西,字符串表扩展。示例代码,来自路径跟踪器的动态值实现:

  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 语句是多么可怕、冗余的混乱——这样,所有的丑陋都集中在底部,并且函数的实际行为很容易阅读一目了然。

于 2010-07-21T15:21:48.490 回答
3

如果可以的话,可能会更好地使用其他更优雅的解决方案,但字符串混合可能非常有用。它们允许代码重用和代码生成。它们在编译时被检查。生成的代码与您自己手动编写的代码完全相同,因此它的安全性并不比您自己手动编写的要差。

字符串混合的问题在于,它们比手写代码更难控制,因为它在源代码中的物理布局方式不同,行号可以清楚地追溯到错误,而且可能更难调试。例如,使用字符串 mixin 来打招呼:

import std.stdio;

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

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

如果我们在 之后删除分号writeln(),那么我们得到的错误将是

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

mixin 在第 5 行完成。第 7 行是一个空行。因此,行号在这里的用处有限。现在,这个 mixin 足够短,我们可以把它放在一行上,并让它说错误与 mixin 在同一行,但是对于更复杂的 mixin,这显然是行不通的。因此,通过使用字符串混合,您找出错误所在位置的能力会受到损害。如果代码是使用 CTFE 生成的,那么要弄清楚代码到底是什么样子以找出它的问题将变得更加困难。这很像弄清楚 C 风格的宏会变成什么代码,只是它可能更糟,因为它们可以生成而不是直接替换。但是,除非您明确告诉它们,否则它们不会替换,因此它们'

字符串混合是完全安全的,它们没有什么特别的问题,但它们确实在某些方面使维护变得更加困难。相应的手写代码会更容易调试。但是,字符串混合功能足够强大,它们可以为您生成大量代码并在这个意义上为您节省大量维护成本,并且它们允许您重用代码,这也可以带来很大的维护收益。

因此,在特定情况下使用字符串混合是否是一个好主意取决于该情况。我看不出它们有什么特别的问题,我当然不会称它们为反模式,但使用它们有利也有弊,因此它们是否是一个好主意取决于你在做什么. 在许多情况下,有更优雅、更清洁的解决方案会更好。在其他情况下,它们正是医生所要求的。

就个人而言,我认为如果您希望生成代码,它们非常棒,可以节省手动编写代码的工作,并且可能更容易为各种情况生成正确的代码并避免冒险创建新代码像您这样的错误可能是您在使用 mixin 的每个地方自己编写的。它也是直接重用代码的方法之一,而不必担心函数调用的成本或单一继承的限制问题或任何其他通过调用函数或继承更难重用代码的问题。您只需将代码复制并粘贴到每个地方,这样如果您更改代码,

因此,在适当的情况下使用字符串混合,如果不需要它们最好不要使用它们,但使用它们并没有什么问题。

于 2010-07-21T17:19:40.663 回答
1

字符串混合就像 goto:应该尽可能避免使用它,并且应该在需要的地方使用它。

于 2010-07-22T04:13:27.270 回答