我正在对 Groovy 进行更多的研究和实验,并且我正在努力思考在 Groovy 中实现我在 Java 中不能/不做的事情的优缺点。 动态编程对我来说仍然只是一个概念,因为我已经深入了解静态和强类型语言。
Groovy 使我能够duck-type,但我无法真正看到其价值。 鸭子类型如何比静态类型更高效? 在我的代码实践中我可以做哪些事情来帮助我掌握它的好处?
我问这个问题时考虑的是 Groovy,但我知道这不一定是 Groovy 问题,所以我欢迎来自每个代码营的答案。
I'm researching and experimenting more with Groovy and I'm trying to wrap my mind around the pros and cons of implementing things in Groovy that I can't/don't do in Java. Dynamic programming is still just a concept to me since I've been deeply steeped static and strongly typed languages.
Groovy gives me the ability to duck-type, but I can't really see the value. How is duck-typing more productive than static typing? What kind of things can I do in my code practice to help me grasp the benefits of it?
I ask this question with Groovy in mind but I understand it isn't necessarily a Groovy question so I welcome answers from every code camp.
发布评论
评论(10)
这是一种鸭子类型可以节省工作量的场景。
这是一个非常简单的类,
现在进行单元测试:
由于缺少静态类型检查,我们能够用 Expando 替换搜索引擎。 通过静态类型检查,我们必须确保 SearchEngine 是一个接口,或者至少是一个抽象类,并创建它的完整模拟实现。 这是劳动密集型的,或者您可以使用复杂的单一用途模拟框架。 但鸭子类型是通用的,并且对我们有帮助。
由于鸭子类型,我们的单元测试可以提供任何旧对象来代替依赖项,只要它实现被调用的方法即可。
强调一下 - 您可以使用静态类型语言来完成此操作,并仔细使用接口和类层次结构。 但通过鸭子打字,你可以用更少的思考和更少的击键来完成它。
这是鸭子类型的优点。 这并不意味着动态类型是在所有情况下使用的正确范例。 在我的 Groovy 项目中,当我觉得有关类型的编译器警告会对我有所帮助时,我喜欢切换回 Java。
Here's one scenario where duck typing saves work.
Here's a very trivial class
Now for the unit test:
We were able to substitute an Expando for the SearchEngine, because of the absence of static type checking. With static type checking we would have had to ensure that SearchEngine was an interface, or at least an abstract class, and create a full mock implementation of it. That's labour intensive, or you can use a sophisticated single-purpose mocking framework. But duck typing is general-purpose, and has helped us.
Because of duck typing, our unit test can provide any old object in place of the dependency, just as long as it implements the methods that get called.
To emphasise - you can do this in a statically typed language, with careful use of interfaces and class hierarchies. But with duck typing you can do it with less thinking and fewer keystrokes.
That's an advantage of duck typing. It doesn't mean that dynamic typing is the right paradigm to use in all situations. In my Groovy projects, I like to switch back to Java in circumstances where I feel that compiler warnings about types are going to help me.
恕我直言,当您遵守某些约定(例如以一致的方式命名变量和方法)时,鸭子类型的优势就会被放大。 以 Ken G 为例,我认为读起来最好:
假设您在名为“calculateRating(A,B)”的某个操作上定义了一个合约,其中 A 和 B 遵守另一个合约。 在伪代码中,它会这样写:
如果你想在 Java 中实现这个,A 和 B 都必须实现某种接口,其内容如下:
此外,如果你想概括你的计算评级的契约(假设你有另一个评分计算算法),您还必须创建一个接口:
使用鸭子类型,您可以放弃您的接口,只依赖于运行时,A和B都会正确响应您的
doStuff ()
调用。 不需要特定的合同定义。 这可能对你有利,但也可能对你不利。缺点是您必须格外小心,以保证您的代码在其他人更改时不会中断(即其他人必须了解方法名称和参数上的隐式约定)。
请注意,这在 Java 中尤其严重,因为 Java 的语法并不那么简洁(与 Scala 相比)例如)。 一个反例是 Lift 框架,他们说该框架的 SLOC 计数类似于 Rails,但测试代码行数较少,因为它们不需要在测试中实现类型检查。
IMHO, the advantage of duck typing becomes magnified when you adhere to some conventions, such as naming you variables and methods in a consistent way. Taking the example from Ken G, I think it would read best:
Let's say you define a contract on some operation named 'calculateRating(A,B)' where A and B adhere to another contract. In pseudocode, it would read:
If you want to implement this in Java, both A and B must implement some kind of interface that reads something like this:
Besides, if you want to generalize you contract for calculating ratings (let's say you have another algorithm for rating calculations), you also have to create an interface:
With duck typing, you can ditch your interfaces and just rely that on runtime, both A and B will respond correctly to your
doStuff()
calls. There is no need for a specific contract definition. This can work for you but it can also work against you.The downside is that you have to be extra careful in order to guarantee that your code does not break when some other persons changes it (ie, the other person must be aware of the implicit contract on the method name and arguments).
Note that this aggravates specially in Java, where the syntax is not as terse as it could be (compared to Scala for example). A counter-example of this is the Lift framework, where they say that the SLOC count of the framework is similar to Rails, but the test code has less lines because they don't need to implement type checks within the tests.
这并不是说鸭子类型比静态类型更有效率,而只是不同而已。 使用静态类型,您始终需要担心数据的类型是否正确,而在 Java 中,它是通过转换为正确的类型来显示的。 使用鸭子类型,只要它有正确的方法,类型并不重要,因此它实际上消除了类型之间的强制转换和转换的许多麻烦。
It's not that duck typing is more productive than static typing as much as it is simply different. With static typing you always have to worry that your data is the correct type and in Java it shows up through casting to the right type. With duck typing the type doesn't matter as long as it has the right method, so it really just eliminates a lot of the hassle of casting and conversions between types.
对我来说,如果您将动态类型语言视为静态类型的一种形式,其中所有内容都继承自足够抽象的基类,那么它们并没有太大的不同。
正如许多人指出的那样,当你开始对此感到奇怪时,问题就会出现。 有人指出一个函数返回单个对象、集合或 null。 让函数返回特定类型,而不是多个类型。 单个函数与集合函数使用多个函数。
归根结底,任何人都可能写出糟糕的代码。 静态打字是一种很好的安全装置,但有时当你想感受风吹过头发时,头盔会妨碍你。
To me, they aren't horribly different if you see dynamically typed languages as simply a form of static typing where everything inherits from a sufficiently abstract base class.
Problems arise when, as many have pointed out, you start getting strange with this. Someone pointed out a function that returns a single object, a collection, or a null. Have the function return a specific type, not multiple. Use multiple functions for single vs collection.
What it boils down to is that anyone can write bad code. Static typing is a great safety device, but sometimes the helmet gets in the way when you want to feel the wind in your hair.
如果您使用 Haskell,静态类型没有任何问题,它具有令人难以置信的静态类型系统。 然而,如果您使用像 Java 和 C++ 这样的语言,它们的类型系统非常严重,那么鸭子类型绝对是一种改进。
想象一下尝试在 Java 中使用像“map”这样简单的东西(并且不,我的意思不是 数据结构)。 即使是泛型也得不到很好的支持。
There is nothing wrong with static typing if you are using Haskell, which has an incredible static type system. However, if you are using languages like Java and C++ that have terribly crippling type systems, duck typing is definitely an improvement.
Imagine trying to use something so simple as "map" in Java (and no, I don't mean the data structure). Even generics are rather poorly supported.
有了TDD + 100% 代码覆盖率 + IDE 工具来不断运行我的测试,我不再需要静态类型了。 没有强类型,我的单元测试变得如此简单(只需使用映射来创建模拟对象)。 特别是,当您使用泛型时,您可以看到区别:
vs
With, TDD + 100% Code Coverage + IDE tools to constantly run my tests, I do not feel a need of static typing any more. With no strong types, my unit testing has become so easy (Simply use Maps for creating mock objects). Specially , when you are using Generics, you can see the difference:
vs
在您使用一段时间之前,要了解鸭子类型的价值有点困难。 一旦习惯了它,您就会意识到不必处理接口或不必担心某些东西到底是什么类型,这会给您带来多大的负担。
It's a little bit difficult to see the value of duck typing until you've used it for a little while. Once you get used to it, you'll realize how much of a load off your mind it is to not have to deal with interfaces or having to worry about exactly what type something is.
接下来,EMACS 和 vi 哪个更好? 这是正在进行的宗教战争之一。
可以这样想:如果语言是静态类型的,任何正确的程序都将是正确的。 静态类型的作用是让编译器有足够的信息来在编译时而不是运行时检测类型不匹配。 如果您进行增量编程,这可能会很烦人,尽管(我认为)如果您清楚地思考您的程序,这并不重要; 另一方面,如果您正在构建一个非常大的程序,例如操作系统或电话交换机,有数十人、数百人或数千人在处理它,或者具有非常高的可靠性要求,那么让编译器能够为您检测一大类问题,而无需测试用例来执行正确的代码路径。
这并不是说动态类型是一个新的、不同的东西:例如,C 就是有效的动态类型,因为我总是可以将
foo*
转换为bar*
。 这只是意味着,当地址真正指向foo*
时,作为 C 程序员,我有责任永远不要使用适合bar*
的代码。 但由于大型程序的问题,C 开发了像 lint(1) 这样的工具,使用typedef
增强了其类型系统,并最终在 C++ 中开发了强类型变体。 (当然,C++ 反过来又开发了围绕强类型的方法,包括各种类型的强制转换、泛型/模板以及 RTTI。不过,还有一件事——不要将“敏捷编程”与“动态语言”混淆”。敏捷编程是关于人们在项目中协作的方式:项目能否适应不断变化的需求以满足客户的需求' 需要同时为程序员维护一个人性化的环境吗?这可以用动态类型语言来完成,而且通常是这样,因为它们可以提高生产力(例如,Ruby,Smalltalk),但它可以做到,并且已经成功做到了, C 甚至汇编程序 事实上,Rally Development 甚至使用敏捷方法(特别是 SCRUM)来进行营销和文档编写。 。
Next, which is better: EMACS or vi? This is one of the running religious wars.
Think of it this way: any program that is correct, will be correct if the language is statically typed. What static typing does is let the compiler have enough information to detect type mismatches at compile time instead of run time. This can be an annoyance if your doing incremental sorts of programming, although (I maintain) if you're thinking clearly about your program it doesn't much matter; on the other hand, if you're building a really big program, like an operating system or a telephone switch, with dozens or hundreds or thousands of people working on it, or with really high reliability requirements, then having he compiler be able to detect a large class of problems for you without needing a test case to exercise just the right code path.
It's not as if dynamic typing is a new and different thing: C, for example, is effectively dynamically typed, since I can always cast a
foo*
to abar*
. It just means it's then my responsibility as a C programmer never to use code that is appropriate on abar*
when the address is really pointing to afoo*
. But as a result of the issues with large programs, C grew tools like lint(1), strengthened its type system withtypedef
and eventually developed a strongly typed variant in C++. (And, of course, C++ in turn developed ways around the strong typing, with all the varieties of casts and generics/templates and with RTTI.One other thing, though --- don't confuse "agile programming" with "dynamic languages". Agile programming is about the way people work together in a project: can the project adapt to changing requirements to meet the customers' needs while maintaining a humane environment for the programmers? It can be done with dynamically typed languages, and often is, because they can be more productive (eg, Ruby, Smalltalk), but it can be done, has been done successfully, in C and even assembler. In fact, Rally Development even uses agile methods (SCRUM in particular) to do marketing and documentation.
鸭子类型会削弱大多数现代 IDE 的静态检查功能,静态检查可以在您键入时指出错误。 有些人认为这是一个优势。 我希望 IDE/编译器尽快告诉我我做了一个愚蠢的程序员把戏。
我最近最喜欢的反对鸭子类型的论点来自 Grails 项目 DTO:
其中
结果
结果类似于Map> 。
,只能通过跟踪不同类中的方法调用来发现它,直到找到它的创建位置。 对于极度好奇的人来说,total
是List
的大小总和,categories
是的大小地图
最初的开发人员可能已经很清楚了,但是糟糕的维护人员(ME)在追踪这个问题时损失了很多头发。
Duck typing cripples most modern IDE's static checking, which can point out errors as you type. Some consider this an advantage. I want the IDE/Compiler to tell me I've made a stupid programmer trick as soon as possible.
My most recent favorite argument against duck typing comes from a Grails project DTO:
where
results
turns out to be something likeMap<String, List<ComplexType>>
, which can be discovered only by following a trail of method calls in different classes until you find where it was created. For the terminally curious,total
is the sum of the sizes of theList<ComplexType>
s andcategories
is the size of theMap
It may have been clear to the original developer, but the poor maintenance guy (ME) lost a lot of hair tracking this one down.
许多关于鸭子打字的评论并没有真正证实这些说法。 不“不必担心”类型对于维护或使应用程序可扩展来说是不可持续的。 在我的上一份合同中,我确实有一个很好的机会看到 Grails 的运行,而且看起来真的很有趣。 每个人都对能够“创建应用程序”并开始工作所获得的收益感到高兴 - 遗憾的是,这一切都在后端赶上了你的脚步。
Groovy 对我来说似乎也是如此。 当然,你可以编写非常简洁的代码,并且在我们如何使用属性、集合等方面肯定有一些很好的糖分……但是不知道来回传递的到底是什么的成本只会变得越来越糟。 有时您会挠头,想知道为什么该项目变成了 80% 的测试和 20% 的工作。 这里的教训是“更小”并不意味着“更易读”的代码。 抱歉,各位,它的逻辑很简单 - 您必须直观地了解越多,那么理解代码的过程就会变得越复杂。 这就是为什么 GUI 多年来不再变得过于标志性——看起来确实很漂亮,但发生的事情并不总是显而易见的。
该项目的人们似乎很难“确定”所学到的经验教训,但是当您有方法返回 T 类型的单个元素、T 数组、ErrorResult 或 null 时……它就变得相当明显了。
然而,使用 Groovy 为我做了一件事 - 很棒的计费时间!
A lot of the comments for duck typing don't really substantiate the claims. Not "having to worry" about a type is not sustainable for maintenance or making an application extendable. I've really had a good opportunity to see Grails in action over my last contract and its quite funny to watch really. Everyone is happy about the gains in being able to "create-app" and get going - sadly it all catches up to you on the back end.
Groovy seems the same way to me. Sure you can write very succinct code and definitely there is some nice sugar in how we get to work with properties, collections, etc... But the cost of not knowing what the heck is being passed back and forth just gets worse and worse. At some point your scratching your head wondering why the project has become 80% testing and 20% work. The lesson here is that "smaller" does not make for "more readable" code. Sorry folks, its simple logic - the more you have to know intuitively then the more complex the process of understanding that code becomes. It's why GUI's have backed off becoming overly iconic over the years - sure looks pretty but WTH is going on is not always obvious.
People on that project seemed to have troubles "nailing down" the lessons learned, but when you have methods returning either a single element of type T, an array of T, an ErrorResult or a null ... it becomes rather apparent.
One thing working with Groovy has done for me however - awesome billable hours woot!