使用 D 字符串 mixin 进行代码重用是一种反模式吗?

发布于 2024-09-10 23:20:52 字数 496 浏览 2 评论 0原文

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

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

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

For those of you who are not familiar with D string mixins, they're basically compile-time evals. You can take any compile time string (whether a literal or generated by template metaprogramming or compile time function evaluation) and compile it as code. If you use a simple string literal, it's basically compiler-automated copy-paste.

Would you consider it an antipattern to use string mixins of literals as a means of simple code reuse where other methods of factoring don't quite fit? On the one hand, it's basically compiler-automated literal copy-and-paste, meaning that once mixed in the instances have nothing whatsoever to do with each other. Bad Things will happen (though at compile time, not at runtime) if a symbol in the string mixin collides with a symbol in the mixed in scope. It's relatively unstructured in that one can, for example, mixin a string into the middle of a function that will work if and only if variables in the scope are named according to a certain convention. Mixins can also declare variables that outer scopes can then use as they see fit.

On the other hand, because the copy-and-paste is compiler automated, there is a single point of truth for the code in question at the source level and if it needs to be modified, it only needs to be modified in one place, and everything stays in sync. String mixins also vastly simplify reusing code that's very hard to factor in any other way and would otherwise have a very high probability of being cut-and-pasted manually.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

浪推晚风 2024-09-17 23:20:52

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

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

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

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

All the criticisms you raised are true.

Regardless, it's still superior to manual copypaste.

Actually, I've got something similar running in my tools library, string table expansion. Example code, from a path tracer's dynamic value implementation:

  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); `);
  }

I'm sure it's easy to see what a horrible, redundant mess of nested ifs and case statements that would be without string mixins - this way, all the ugliness is concentrated at the bottom, and the actual behavior of the function is easy to read off at a glance.

甜`诱少女 2024-09-17 23:20:52

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

字符串混合的问题在于,它们比手写代码更难控制,因为它在物理上并没有以相同的方式布置在源代码中,行号可以清楚地追溯到错误,并且可能更难调试。例如,使用字符串 mixin 来表示 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

The mixin is did on line 5. Line 7 is a Blank线。因此,行号在这里的用处有限。现在,这个 mixin 足够短,我们可以将它放在一行上,并让它显示错误与 mixin 在同一行,但对于更复杂的 mixin,这显然不起作用。因此,通过使用字符串混合,您找出错误所在的能力会受到损害。如果代码是使用 CTFE 生成的,那么要弄清楚代码究竟是什么样子从而找出其中的问题就会变得更加困难。这很像弄清楚 C 风格的宏会变成什么代码,只不过情况可能更糟,因为它们可以被生成而不是直接替换。但是,除非您明确告诉它们,否则它们不会进行替换,因此它们比 C 样式宏安全得多。

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

因此,在特定情况下使用字符串混合是否是个好主意取决于具体情况。我不认为它们有什么特别的问题,我当然不会称它们为反模式,但使用它们既有优点也有缺点,因此它们是否是一个好主意取决于你在做什么。在许多情况下,有更优雅、更干净的解决方案会更好。在其他情况下,它们正是医生所吩咐的。

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

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

While other, more elegant solutions may be better to use if you can, string mixins can be extremely useful. They allow for both code re-use and code generation. They're checked at compile time. The code that results is exactly the same as if you'de written it yourself by hand, so it's not any less safe than if you had written it yourself by hand.

The problem with string mixins is that they're harder to control than hand-written code in the sense that it's not physically laid out in your source in the same manner with line numbers clearly traceable to errors, and it may be harder to debug. For instance, take hello world with a string mixin:

import std.stdio;

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

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

If we were to remove the semicolon after writeln(), then the error we got would be

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

The mixin is done on line 5. Line 7 is a blank line. So, the line number is of limited usefulness here. Now, this mixin is short enough that we could have put it on a single line and gotten it to say that the error was on the same line as the mixin, but with more complicated mixins, that obviously won't work. So, by using a string mixin, your ability to figure out where an error is is impaired. If the code is generated using CTFE, then it would become that much more difficult to figure out exactly what the code even looks like in order to figure out what's wrong with it. It's a lot like figuring out what code a C-style macro turns into, except that it could be worse because they could be generated rather than a direct replacement. However, they don't replace except where you explicitly tell them to, so they're much safer than C-style macros.

String mixins are totally safe, and there's nothing particularly wrong with them, but they do make maintenance harder in some ways. The corresponding hand-written code would be easier to debug. However, string mixins are powerful enough that they can do a lot of code generation for you and save you a lot of maintainence costs in that sense, and they allow you to re-use code, which can be a big maintanence gain as well.

So, whether using a string mixin is a good idea in a particular situation depends on that situation. I don't see anything particularly wrong with them, and I certainly wouldn't call them an anti-pattern, but there are both pros and cons to using them such that whether they're a good idea depends on what you're doing. In many cases, there are more elegant, cleaner solutions which would be better. In others, they're exactly what the doctor ordered.

Personally, I think that they're fantastic if you're looking to generate code, saving yourself the effort of having to write that code by hand, and possibly making it easier to generate correct code for a variety of situations and avoiding risking creating new bugs like you might have had you written it yourself in each of those places where you used the mixin. It also is one of the ways to just outright re-use code without having to worry about the cost of a function call or issues with the limits of single-inheritance or anything else that makes code re-use by calling functions or inheritance harder. You're simply copying and pasting the code into each place in a manner which makes it so that if you change the code, the changes will be properly pasted everywhere without you having to worry about tracking them all down like if you had hand copy and pasted.

So, use string mixins where appropriate, and it's probably best not to use them if they're not needed, but there's nothing really wrong with using them.

静水深流 2024-09-17 23:20:52

字符串 mixin 就像 goto:应该尽可能避免它,并且应该在需要时使用它。

String mixin is like goto: it should be avoided where ever possible and should be used wherever required.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文