30

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

Groovy 给了我鸭式的能力,但我看不出它的价值。鸭式打字比静态打字更有效率吗?我在代码实践中可以做哪些事情来帮助我掌握它的好处?

我问这个问题时考虑到了 Groovy,但我知道这不一定是 Groovy 问题,所以我欢迎每个代码营的回答。

4

10 回答 10

20

许多关于鸭子打字的评论并没有真正证实这些说法。不必“担心”类型对于维护或使应用程序可扩展是不可持续的。我真的有很好的机会在我的上一份合同中看到 Grails 的运行情况,而且真的很有趣。每个人都对能够“创建应用程序”并开始工作所获得的收益感到高兴 - 遗憾的是,这一切都在后端赶上了你。

Groovy 对我来说似乎是一样的。当然,您可以编写非常简洁的代码,并且在我们如何使用属性、集合等方面肯定有一些很好的糖... 有时你会挠头,想知道为什么这个项目变成了 80% 的测试和 20% 的工作。这里的教训是,“更小”并不意味着“更易读”的代码。抱歉,它的逻辑很简单——你必须直观地了解越多,那么理解代码的过程就越复杂。这就是为什么 GUI 多年来不再变得过于标志性的原因 - 确实看起来很漂亮,但 WTH 的进展并不总是显而易见的。

该项目中的人们似乎很难“确定”所吸取的教训,但是当您有方法返回 T 类型的单个元素、T 的数组、ErrorResult 或 null 时......它变得相当明显。

然而,与 Groovy 合作为我做了一件事 - 令人敬畏的计费时间!

于 2010-01-18T23:10:38.667 回答
11

Duck 打字削弱了大多数现代 IDE 的静态检查,它可以在你打字时指出错误。有些人认为这是一个优势。我希望 IDE/编译器尽快告诉我我犯了一个愚蠢的程序员把戏。

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

class SimpleResults {
    def results
    def total
    def categories
}

whereresults原来是类似的东西Map<String, List<ComplexType>>,只有通过跟踪不同类中的方法调用,直到找到它的创建位置,才能发现它。对于最终好奇的人,totalList<ComplexType>scategories的大小之和, 是Map

最初的开发人员可能很清楚,但是可怜的维护人员(ME)在追踪这个问题时失去了很多头发。

于 2008-10-30T13:09:55.503 回答
9

在您使用一段时间之前,很难看到鸭式打字的价值。一旦你习惯了它,你就会意识到不必处理接口或不必担心某个东西到底是什么类型是多么沉重。

于 2008-09-07T00:42:26.033 回答
7

如果你使用 Haskell,静态类型没有什么问题,它有一个令人难以置信的静态类型系统。但是,如果您使用的是 Java 和 C++ 等类型系统非常糟糕的语言,那么鸭式类型绝对是一种改进。

想象一下尝试在 Java 中使用像“ map ”这样简单的东西(不,我不是指数据结构)。甚至泛型的支持也很差。

于 2008-09-07T01:09:15.250 回答
6

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

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

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

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

于 2008-11-09T15:12:25.043 回答
5

恕我直言,当您遵守某些约定时,鸭子类型的优势会被放大,例如以一致的方式命名变量和方法。以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);

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

缺点是您必须格外小心,以确保您的代码在其他人更改时不会中断(即,其他人必须知道方法名称和参数上的隐式约定)。

请注意,这在 Java 中尤其严重,其语法没有尽可能简洁(例如,与Scala相比)。一个反例是Lift 框架,他们说框架的 SLOC 计数与Rails相似,但测试代码的行数更少,因为它们不需要在测试中实现类型检查。

于 2008-11-09T14:25:50.537 回答
5

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

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

对比

//Dynamic typing
def someMap = [:]   
于 2013-11-17T08:30:44.630 回答
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 是一个接口,或者至少是一个抽象类,并创建一个完整的模拟实现。这是劳动密集型的,或者您可以使用复杂的单一用途模拟框架。但是鸭式打字是通用的,并且对我们有所帮助。

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

强调一下——你可以在静态类型语言中做到这一点,仔细使用接口和类层次结构。但是使用鸭子打字,您可以用更少的思考和更少的击键来完成它。

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

于 2010-02-16T11:20:42.417 回答
2

对我来说,如果您将动态类型语言视为简单的静态类型形式,其中所有内容都继承自足够抽象的基类,它们并没有太大的不同。

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

归结为任何人都可以编写糟糕的代码。静态打字是一种很好的安全装置,但有时当你想感受头发中的风时,头盔会妨碍你。

于 2018-04-18T06:53:44.197 回答
1

并不是说鸭式打字比静态打字更有效率,因为它只是不同而已。使用静态类型,您总是需要担心您的数据是正确的类型,而在 Java 中,它通过转换为正确的类型来显示。使用鸭子类型,只要它有正确的方法,类型就无关紧要了,所以它真的消除了很多类型之间的转换和转换的麻烦。

于 2008-09-07T00:31:58.897 回答