鸭子打字与静态打字有什么好处?

时间:2008-09-07 00:28:08

标签: groovy duck-typing

我正在使用Groovy进行更多的研究和实验,并且我试图围绕在Groovy中实现事物的优点和缺点,我不能/不能用Java做。动态编程对我来说仍然只是一个概念,因为我已经深深沉浸在静态和强类型语言中。

Groovy让我能够duck-type,但我无法真正看到它的价值。鸭子打字比静态打字更有效率?在我的代码练习中我可以做些什么来帮助我掌握它的好处?

我在Groovy中提出这个问题,但我知道它不一定是一个Groovy问题所以我欢迎来自每个代码阵营的答案。

12 个答案:

答案 0 :(得分:16)

很多关于鸭子打字的评论并没有真正证实这些说法。对于某种类型而言,“不必担心”是不可持续的维护或使应用程序可扩展。我真的有机会看到Grails在我的上一份合同中采取行动,真的非常有趣。每个人都对能够“创造应用程序”并开始行动所获得的收益感到高兴 - 遗憾的是,它们都会在后端赶上你。

Groovy对我来说似乎是一样的。当然你可以编写非常简洁的代码,并且肯定有一些很好的糖,我们如何使用属性,集合等...但不知道什么是来回传递的成本变得越来越糟。在某些时候,你会挠头,想知道为什么该项目已经成为80%的测试和20%的工作。这里的教训是“较小”不会产生“更易读”的代码。对不起,伙计们,它的逻辑简单 - 你必须越直观地知道,然后理解代码的过程就越复杂。这就是为什么GUI多年来已经成为过度标志性的原因 - 肯定看起来很漂亮但WTH正在发生并不总是显而易见的。

那个项目上的人似乎有麻烦“确定”所学到的经验教训,但是当你有方法返回T类型的单个元素,T的数组,ErrorResult或null ...时它变得相当明显

然而,使用Groovy的一件事已经为我做了 - 令人敬畏的计费小时数!

答案 1 :(得分:10)

如果您正在使用具有令人难以置信的静态类型系统的Haskell,则静态类型没有任何问题。但是,如果您使用的Java和C ++等语言具有非常严重的类型系统,那么鸭子打字肯定是一种改进。

想象一下,尝试在Java中使用像“map”这样简单的东西(不,我不是指the data structure)。即使是泛型也很难得到支持。

答案 2 :(得分:9)

鸭子打字削弱了大多数现代IDE的静态检查,可以在您键入时指出错误。有些人认为这是一个优势。我希望IDE / Compiler告诉我,我已经尽快制作了一个愚蠢的程序员技巧。

我最近最喜欢的论点反对鸭子打字来自Grails项目DTO:

class SimpleResults {
    def results
    def total
    def categories
}

其中results类似于Map<String, List<ComplexType>>,只能通过跟踪不同类中的方法调用来发现,直到找到它的创建位置。对于最终好奇的人来说,totalList<ComplexType>的大小总和,而categoriesMap的大小

最初的开发人员可能已经清楚了,但是那个可怜的维修人员(ME)失去了大量的头发跟踪。

答案 3 :(得分:7)

在您使用它一段时间之前,看到鸭子打字的价值有点困难。一旦你习惯了它,你就会意识到你不必处理界面或者不必担心某些类型的东西是多少负担。

答案 4 :(得分:6)

接下来,哪个更好:EMACS还是vi?这是正在进行的宗教战争之一。

以这种方式思考:如果语言是静态类型的,任何正确的程序都是正确的。静态类型的作用是让编译器有足够的信息在编译时检测类型不匹配而不是运行时。如果你正在进行增量编程,这可能是一种烦恼,尽管(我坚持认为)如果你清楚地思考你的程序并不重要;另一方面,如果你正在构建一个非常大的程序,比如操作系统或电话交换机,有数十或数百或数千人在上面工作,或者具有非常高的可靠性要求,那么让编译器能够为您检测一大类问题,而无需测试用例来运行正确的代码路径。

动态类型不是一个新的和不同的东西:例如,C是有效动态类型的,因为我总是可以将foo*转换为bar*。这只是意味着我作为C程序员的责任,当地址真正指向bar*时,永远不要使用适合foo*的代码。但是由于大型程序的问题,C增长了像lint(1)这样的工具,用typedef强化了它的类型系统,最终在C ++中开发了一个强类型变体。 (当然,C ++反过来开发了强类型的方法,包括所有类型的演员表和泛型/模板以及RTTI。

另一件事,但是 - 不要将“敏捷编程”与“动态语言”混为一谈。 Agile programming是关于人们在项目中协同工作的方式:项目能否适应不断变化的需求以满足客户的需求,同时为程序员保持人性化的环境?它可以使用动态类型语言来完成,并且通常是因为它们可以更高效(例如,Ruby,Smalltalk),但它可以在C甚至汇编程序中成功完成。实际上,Rally Development甚至使用敏捷方法(特别是SCRUM)来进行营销和文档编制。

答案 5 :(得分:4)

恕我直言,当你遵守一些约定时,鸭子打字的优势就会被放大,例如以一致的方式命名变量和方法。以 Ken G 为例,我认为它会读得最好:

class SimpleResults {
    def mapOfListResults
    def total
    def categories
}

假设您在一些名为'calculateRating(A,B)'的操作上定义合约,其中A和B遵守另一个合约。在伪代码中,它将显示为:

Long calculateRating(A someObj, B, otherObj) {

   //some fake algorithm here:
   if(someObj.doStuff('foo') > otherObj.doStuff('bar')) return someObj.calcRating());
   else return otherObj.calcRating();

}

如果你想用Java实现它,A和B都必须实现某种类似于这样的接口:

public interface MyService {
    public int doStuff(String input);
}

此外,如果您想概括计算评级的合约(假设您有另一种评级计算算法),您还必须创建一个界面:

public long calculateRating(MyService A, MyServiceB);

使用duck typing,您可以放弃接口,只需依赖运行时,A和B都会正确响应您的doStuff()来电。不需要特定的合同定义。这可能对你有用,但也可能对你不利。

缺点是你必须格外小心,以保证你的代码在其他人改变时不会中断(即,另一个人必须知道方法名称和参数的隐式契约)。

请注意,这在Java中特别恶化,其语法不尽如人意(例如,与Scala相比)。反例的是Lift framework,他们说框架的SLOC计数类似于Rails,但测试代码的行数较少,因为它们不需要实现类型检查在测试中。

答案 6 :(得分:3)

这是鸭子打字可以节省工作的一种情况。

这是一个非常简单的课程

class BookFinder {
    def searchEngine

    def findBookByTitle(String title) {
         return searchEngine.find( [ "Title" : title ] ) 
    }
}

现在进行单元测试:

void bookFinderTest() {
    // with Expando we can 'fake' any object at runtime.
    // alternatively you could write a MockSearchEngine class.
    def mockSearchEngine = new Expando()
    mockSearchEngine.find = {
        return new Book("Heart of Darkness","Joseph Conrad")
    }

    def bf = new BookFinder()
    bf.searchEngine = mockSearchEngine
    def book = bf.findBookByTitle("Heart of Darkness")
    assert(book.author == "Joseph Conrad"
}

由于缺少静态类型检查,我们能够将Expando替换为SearchEngine。使用静态类型检查,我们必须确保SearchEngine是一个接口,或者至少是一个抽象类,并创建一个完整的模拟实现。这是劳动密集型的,或者您可以使用复杂的单一用途模拟框架。但鸭子打字是通用的,并帮助了我们。

由于duck typing,我们的单元测试可以提供任何旧对象来代替依赖,只要它实现了被调用的方法。

要强调 - 您可以使用静态类型语言执行此操作,并仔细使用接口和类层次结构。但是通过鸭子打字你可以用更少的思维和更少的击键来做到这一点。

这是鸭子打字的一个优点。这并不意味着动态类型是在所有情况下使用的正确范例。在我的Groovy项目中,我喜欢在我认为有关类型的编译器警告将帮助我的情况下切换回Java。

答案 7 :(得分:2)

使用 TDD + 100%代码覆盖率 + IDE工具来不断运行我的测试,我不再需要静态类型了。由于没有强大的类型,我的单元测试变得如此简单(只需使用Maps创建模拟对象)。特别是,当您使用泛型时,您可以看到差异:

//Static typing 
Map<String,List<Class1<Class2>>> someMap = [:] as HashMap<String,List<Class1<Class2>>>

<强> VS

//Dynamic typing
def someMap = [:]   

答案 8 :(得分:1)

并不是说鸭子打字比静态打字更有效率,因为它只是简单的不同。使用静态类型,您始终必须担心数据是正确的类型,而在Java中,它通过强制转换为正确的类型。使用duck类型,只要它具有正确的方法,类型无关紧要,因此它实际上消除了很多类型之间的转换和转换的麻烦。

答案 9 :(得分:1)

@Chris Bunch

并不是说静态打字比打字更有效率,因为它只是不同而已。使用duck typing你总是要担心你的数据有正确的方法,并且在Javascript或Ruby中它通过大量的方法测试显示出来。使用静态类型并不重要,只要它是正确的接口,所以它实际上消除了很多类型之间的测试和转换的麻烦。

抱歉,我必须这样做......

答案 10 :(得分:1)

我的意见:

动态打字或鸭子打字的语言是玩具。您无法获得智能感知,并且您将失去编译时间(或编辑时间 - 使用像VS这样的真实IDE,而不是其他人认为是垃圾的IDE)代码验证。

要清楚所有非静态输入的语言,其他一切都只是纯粹的受虐狂。

答案 11 :(得分:0)

对我而言,如果您将动态类型语言视为静态类型的一种形式,并且所有内容都从一个足够抽象的基类继承,那么它们并不是完全不同。

正如许多人所指出的那样,当你开始对此感到陌生时,会出现问题。有人指出了一个返回单个对象,集合或null的函数。让函数返回特定类型,而不是多个。使用多个函数进行单个vs集合。

归结为是任何人都可以编写错误的代码。静态打字是一种很好的安全装置,但是当你想要感受到头发上的风时,有时头盔就会挡住。