首页 > 解决方案 > 如何将此 Javascript 片段转换为 C#

问题描述

Javascript:

function bounds(a, b, c) {
  null != b && (a = Math.max(a, b));
  null != c && (a = Math.min(a, c));
  return a
}

老实说,我什至不明白这里发生了什么。如果b不为空,则被a设置Math.max(a, b)。如果c不为空,a则从Math.min(a, c)?

如何将其转换为 C#?

PS这是我的翻译。这个对吗?

private double bounds(double a, double? b, double? c) {
    if (b != null) a = Math.Max(a, b.Value);
    if (c != null) a = Math.Min(a, c.Value);

    return a;
}

标签: javascriptc#

解决方案


老实说,我什至不明白这里发生了什么。

我也不。该代码似乎实现了合同“如果c是最小元素,则返回它,否则,返回 、 和 的中间元素ab以及c一些未知目的的可空性检查。

这是一个奇怪的实现合同,它让我认为它可能是一个未经测试的“返回中间元素”的实现,它有一个错误;如果c在实践中永远不是最小的元素,也许这个错误永远不会被捕获。

如何将其转换为 C#?

了解上下文中代码的含义,然后在惯用的 C# 中实现这些语义。理解是重要的部分;一旦你理解了代码,编写一个正确的、可测试的实现就很简单了。

这是我的翻译。这个对吗?

private double bounds(double a, double? b, double? c) {
  if (b != null) a = Math.Max(a, b.Value);
  if (c != null) a = Math.Min(a, c.Value);
  return a;
}

这是正确的,因为它准确地实现了原始的、可能有错误的代码的语义。但也好不到哪里去。让我们改进它。突出三点:

  • 该方法不消耗this,所以应该是static
  • 该方法是 C# 方法,应遵循 C# 的命名约定。
  • 覆盖其形式的方法在设计时难以阅读,在运行时也难以调试。就此而言,任何改变变量的方法都比不改变变量的方法更难理解。
  • 对它们有意义的名为a, b,的变量c很难阅读,因为它们掩盖了这些含义。

让我们修复所有这些。

private static double Bounds(double a, double? b, double? c) 
{
  double larger = (b == null) ? a : Math.Max(a, b.Value);
  double smaller = (c == null) ? larger : Math.Min(larger, c.Value);
  return smaller;
}

我们仍然没有 , 和 的好名字abc至少我们知道largersmaller反映了它们的含义。

我们完了吗?总是问自己我怎样才能让这更简单

您可以通过将检查移动到帮助程序中来使其更简单。

private static double Max(double x, double? y) =>
  (y == null) ? x : Math.Max(x, y.Value)

private static double Min(double x, double? y) =>
  (y == null) ? x : Math.Min(x, y.Value)

现在我们的方法变成了:

private static double Bounds(double a, double? b, double? c) 
{
  double larger = Max(a, b);
  double smaller = Min(larger, c);
  return smaller;
}

现在我们意识到我们可以将其重写为

private static double Bounds(double a, double? b, double? c) => 
  Min(Max(a, b), c);

当然还不清楚这个东西的目的是什么,但我希望你同意我的实现在它的行动中再清楚不过了。在我的实现中,很明显语义是“如果c最少,则返回它,否则返回中间元素”。将我的版本的可理解性与原始的可怕混乱进行比较,看看哪个更容易理解。

这里的教训是什么?

  • 将代码组织成单一赋值形式
  • 将复杂的逻辑移到辅助方法中,将其抽象出来
  • 重写代码以消除不必要的解释变量。
  • 代码变得干净且易于理解。

进一步的沉思;假设我的猜想是对的,意图是这样写:

private static double Middle(double test, double? bottom, double? top)

该方法的语义是:

  • bottom并且top可能都为空。
  • 如果两者都不为空,则bottom必须是较小的。
  • 如果bottomtop都不为空,则返回中间的值。
  • 如果bottomtop都为空,那么test就是中间的值。
  • 如果bottom为null但top不为null,则和中较小testtop值是中间的值。在这里,我们将“null”视为底部的“负无穷大”。
  • 类似地,如果top为空但bottom不是,则 和 中的较大者bottomtest中间的。在这里,空顶部被视为“正无穷大”。

这实际上是一个非常明智的合约,但它要求我们知道它bottom总是比top它们不都为空时要小。C# 中的一个好的实现要么是Debug.Assert这个事实(如果方法是private),要么throw是违反了先决条件(如果方法是public)。

如果是这种情况,那么更好的选择是这样做:

private static double Middle(double test, double? bottom, double? top) =>
  Middle(
    test, 
    bottom ?? Double.NegativeInfinity, 
    top ?? Double.PositiveInfinity);

private static double Middle(double test, double bottom, double top) =>
  Math.Min(Math.Max(test, bottom), top);

(如果您不熟悉??,则读作“coalesce”,x??y意思是“如果x不为空则使用x.Value,否则使用y”。这是一个非常有用的运算符。)

再一次,看看当你在编写代码时考虑到可读性时代码看起来多么漂亮和简单。很明显,“null 表示底部为负无穷大,顶部为正无穷大”,因为这就是代码所说的!

或者,编写一个正确的 Middle 实现,不需要对三个输入进行任何排序:

// True if test is between b1 and b2, false otherwise
private static bool Between(double test, double b1, double b2) =>
  ((b1 <= test) & (test <= b2)) | ((b2 <= test) & (test <= b1));

// Returns the middle value of a, b, and c.
private static double Middle(double a, double b, double c)
{
  if (Between(a, b, c) return a;
  if (Between(b, a, c) return b;
  return c;
}

或者,如果你喜欢

private static double Middle(double a, double b, double c) =>
  Between(a, b, c) ? a : Between(b, a, c) ? b : c;

现在我们可以再次轻松地编写我们的 Bounds 方法:

private static double Bounds(double test, double? bottom, double? top) =>
  Middle(
    test, 
    bottom ?? Double.NegativeInfinity, 
    top ?? Double.PositiveInfinity);

再一次,想想我们在这里努力的目标。做一件事并且做得非常好的简单方法。一旦你有了这些,你就可以使用这些方法来构建其他简单的方法,并逐渐增加程序的复杂性,而不会使其难以理解。


推荐阅读