论坛首页 Java企业应用论坛

什么时候使用assumption?

浏览 11240 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2007-02-16  
OO
问题背景,定义三个概念先:
1。percent。就是百分比。
2。weight。如果percent是10,weight就是0.1,weight=percent/100.
3。amount。如果percent是10,总数是1000,那么amount就是100. amount=total*weight.

在程序中,我们很多时候需要在amount, weight, percent之间来回转换。根据不同模块的需要,把同一个数据点转换成percent或者weight或者amount。

目标是设计一个类Portion,它的接口如下:
public class Portion {
  public double getPercent();
  public double getWeight();
  public double getAmount();
  public double getTotal();
  public static Portion fromPercent(double total, double percent);
  public static Portion fromWeight(double total, double weight);
  public static Portion fromAmount(double total, double amount);
}

另外,程序中还使用这样一个习惯:如果total为0,那么从amount计算percent和weight的时候也是0。(以避免divide by 0)


我的pair想这样实现:

public class Portion {
  private final double total;
  private final double amount;
  Portion(double total, double amount) {
    this.total = total;
    this.amount = amount;
  }
  public double getPercent() {
    return getWeight()*100;
  }
  public double getWeight() {
    return total==0?0:amount/total;
  }
  public double getAmount() {
    return amount;
  }
  public double getTotal() {
    return total;
  }
  public static Portion fromPercent(double total, double percent) {
    return new Portion(total, total*percent/100);
  }
  public static Portion fromWeight(double total, double weight) {
    return new Portion(total, total*weight);
  }
  public static Portion fromAmount(double total, double amount) {
    return new Portion(total, amount);
  }
}


我对这个设计是不满意的。我提出了几点问题:
1。假设在某一点total不可知,那么怎样用Portion来封装唯一可知的percent/weight?Portion.fromWeight(0, 0.1).getPercent()在这个设计中不会返回我期待的10,而是0。
2。double是有误差的。如果客户写一个
assertEquals(weight, Portion.fromWeight(777, weight).getWeight(), 0)

居然也要失败,不免尴尬。

所以我提议这样做:
public class Portion {
  private final double total;
  private final double amount;
  private final double percent;
  private final double weight;
  Portion(double total, double amount, double percent, double weight) {
    this.total = total;
    this.amount = amount;
    this.percent = percent;
    this.weight = weight;
  }
  public double getPercent() {
    return percent;
  }
  public double getWeight() {
    return weight;
  }
  public double getAmount() {
    return amount;
  }
  public double getTotal() {
    return total;
  }
  public static Portion fromPercent(double total, double percent) {
    return new Portion(total, total*percent/100, percent, percent/100);
  }
  public static Portion fromWeight(double total, double weight) {
    return new Portion(total, total*weight, weight*100, weight);
  }
  public static Portion fromAmount(double total, double amount) {
    double weight = total==0?0:amount/total;
    return new Portion(total, amount, weight*100, weight);
  }
}

这样,虽然double计算有误差,我至少可以保证Portion.fromWeight(total, weight).getWeight()永远等于weight。

但是,pair对这种设计感觉不舒服,他不喜欢把逻辑放在静态工厂方法中。
而且,pair对我提出的几个他的方案的缺陷如此回应:
1。total为0的情况本身就特殊。根据整个系统目前的情况,getWeight()和getPercent()都返回0可以接受。
2。我不care是否有误差,反正我们现在比较double的时候都是用一个tolerance的。


我的pair对整个系统了解的比我多,我相信他说的话。只不过,要我做一个单独的小模块,却总要依赖于外部系统的“对特殊情况A,我们可以容忍;对情况B,我们不在乎”这些assumption,让我深藏于心底的洁癖非常不舒服。

我并不是绝对排斥使用适当的assumption来简化实现复杂度。但是当两个方案的复杂度相近时,我很反感把外界的一些assumption引进来。

那么,你怎么想?
   发表时间:2007-02-17  
大家都忙着过年,我来给搂主唱个和。

四个量中,只有两个是自变量,其余两个是导出量。凭这一点,我否定楼主的的做法,因为太多的冗余。剩下的问题是选那两个作基本变量。直觉让我选Total和Percent。这个直觉有没有道理呢?

粗看之下,四个量是相关的,任选其中两个的结果应该是等价的。但细细考究的话还是有一些差别的。实数域对于四则运算是封闭的,只有一点例外,那就是0。0是除法(函数)的奇点。所以我们要尽量避免除法而用乘法。结论就是应该选用Total和Weidht作基本量。这样可以保证
assertEquals(weight, Portion.fromWeight(0, weight).getWeight())
assertEquals(weight, Portion.fromWeight(777, weight).getWeight())
assertEquals(percent, Portion.fromPercent(0, percent).getPercent())
assertEquals(percent, Portion.fromPercent(777, percent).getPercent())
唯一需要做假定的是fromAmount(double total, double amount),如果这个假定还是不能接受,可以让它抛出异常。

至于运算精度,那是另一回事了。既然说的是钱,你要么用assertEquals(A, x, 0.0001)来判是否相等,要么用long或者BigDecimal来实现Portion。
0 请登录后投票
   发表时间:2007-02-17  
pojo 写道
大家都忙着过年,我来给搂主唱个和。

四个量中,只有两个是自变量,其余两个是导出量。凭这一点,我否定楼主的的做法,因为太多的冗余。剩下的问题是选那两个作基本变量。直觉让我选Total和Percent。这个直觉有没有道理呢?

粗看之下,四个量是相关的,任选其中两个的结果应该是等价的。但细细考究的话还是有一些差别的。实数域对于四则运算是封闭的,只有一点例外,那就是0。0是除法(函数)的奇点。所以我们要尽量避免除法而用乘法。结论就是应该选用Total和Weidht作基本量。这样可以保证
assertEquals(weight, Portion.fromWeight(0, weight).getWeight())
assertEquals(weight, Portion.fromWeight(777, weight).getWeight())
assertEquals(percent, Portion.fromPercent(0, percent).getPercent())
assertEquals(percent, Portion.fromPercent(777, percent).getPercent())
唯一需要做假定的是fromAmount(double total, double amount),如果这个假定还是不能接受,可以让它抛出异常。

至于运算精度,那是另一回事了。既然说的是钱,你要么用assertEquals(A, x, 0.0001)来判是否相等,要么用long或者BigDecimal来实现Portion。

不管冗余与否。那都是内部的实现细节。

问题是你这个方法有两个问题:
fromAmount(1, 0).getAmount() != 1
fromAmount(1, 777).getAmount() != 1

强迫客户代码使用精度?觉得这个api比较不自然。fromAmount(1, x).getAmount()不能原样保持原来的amount?

我认为对客户保持一个最友好的接口,least surprise,比内部是否冗余重要的多。毕竟内部的冗余也都是封装好了的。你就当它是四个计算结果的数好了。
而且,用四个数据点还有容易测试的好处。test case还可以这么写:
assertEquals(new Portion(0, 1, 0, 0), Portion.fromAmount(0, 1));

0 请登录后投票
   发表时间:2007-02-17  
为Portion定义三个够找函数,是否更好点。 在够找函数里面去计算total;amount;percent;weight;的值
0 请登录后投票
   发表时间:2007-02-17  
fromAmount的签名是
Portion fromAmount(double total, double amount)
fromAmount(1, 0).getAmount()的返回值是0,这应该是对的啊,你的输入是0,输出当然也是0。

在fromAmount(1, 777).getAmount()的例子,输入的值就是不合理的,不管在哪一种实现中都会出错的,这是具体实现的问题。

运算精度的问题,不是强迫客户代码使用精度,而是说对于金额而言,精度超过3位(或4位)是没有意义的,它们在实用上是相等的。运算的最后结果最终要进位到小数点后2位。
0 请登录后投票
   发表时间:2007-02-17  
dengyin2000 写道
为Portion定义三个够找函数,是否更好点。 在够找函数里面去计算total;amount;percent;weight;的值


打自己的嘴巴。签名都是一样的
0 请登录后投票
   发表时间:2007-02-18  
pojo 写道
fromAmount的签名是
Portion fromAmount(double total, double amount)
fromAmount(1, 0).getAmount()的返回值是0,这应该是对的啊,你的输入是0,输出当然也是0。

在fromAmount(1, 777).getAmount()的例子,输入的值就是不合理的,不管在哪一种实现中都会出错的,这是具体实现的问题。

运算精度的问题,不是强迫客户代码使用精度,而是说对于金额而言,精度超过3位(或4位)是没有意义的,它们在实用上是相等的。运算的最后结果最终要进位到小数点后2位。

1. fromAmount(0, 1).getAmount() != 1
2. fromAmount(777, 1).getAmount() != 1

想想Double,不管double运算是否有误差,
double val = 3.14
new Double(val)==val绝对是保证的。

我希望Portion达到同样的效果。
0 请登录后投票
   发表时间:2007-02-18  
我的fromAmount(double total, double amount)和你的一样是有判断的:
    public static Portion fromAmount(double total, double amount)
    {
        if (total== 0)
            return new Portion(0, 0);
        return new Portion(total, weight/total);
    }
所以fromAmount(777, 1).getAmount() == 1

至于fromAmount(0, 1),我认为最好的办法是抛出异常,因为这是一个不合理的输入,如果让它进入系统,会引起麻烦的。
0 请登录后投票
   发表时间:2007-02-18  
如果需要精确的计算一致性本来就不能用 double, 既然用了double就不应该期望任意运算结果都落在一定的误差范围内.

我觉得:

如果需要很高精度, 就必须把double换成BigDecimal.

如果需要绝对精度, 就要用像IBM的无误差运算库之类的专门解决方案.

double精度造成的suprise不是应用程序的问题, 而是 IEEE FP 规范的问题.

如果说项目需求要求在应用程序里实现基于double的精度保障, 那楼主的方案很好, 但是如果没有这方面的明确需求, 就没有必要了.
0 请登录后投票
   发表时间:2007-02-18  
complystill 写道
如果需要精确的计算一致性本来就不能用 double, 既然用了double就不应该期望任意运算结果都落在一定的误差范围内.

我觉得:

如果需要很高精度, 就必须把double换成BigDecimal.

如果需要绝对精度, 就要用像IBM的无误差运算库之类的专门解决方案.

double精度造成的suprise不是应用程序的问题, 而是 IEEE FP 规范的问题.

如果说项目需求要求在应用程序里实现基于double的精度保障, 那楼主的方案很好, 但是如果没有这方面的明确需求, 就没有必要了.

double是系统范围内采用的。但是我并不认为采用了double,就可以容忍val != val,或者new Double(val).doubleValue()!=val的情况出现。


ieee标准并没有造成new Double(val).doubleValue()!=val。同样,我也希望fromAmount(total, val).getAmount==val。觉得这个要求不过分。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics