To this point, I've avoided much participation in the 'duck typing' discussions that seem to have pervaded the CF community for the past months. But I saw this point on Dooce (which is a non-technical blog that just happened to be accidentally relevant) about it, and had to provide some commentary.
The narrative, while totally divorced from the world of software development, illustrates an interesting facet: what if it's "kind of" a duck? Obviously Jon (the father) is not Heather (the mother – who is not my wife Heather either). But does he pass the test in this one scenario? Yes. Is he duck enough? No.
You might be tempted to say "yeah, he's duck enough", based on the narrow confines of this discussion, but I encourage you to not fall into the trap. If he's duck enough in one scenario, but not duck enough in a different scenario, there's some major inconsistencies.
Back to programming, say you have an object parameter to some method that does a few different things (via delegation, of course) that don't involve a database (so they're not protected by a DB transaction). The method uses it's argument to initiate the first process without issue. Same for the second. But the third errors, because the object isn't duck enough to help with the third process, even though it worked for the first two.
What can you do? You can enforce formal typechecks where possible (on the CFARGUMENT tag in this case), but other than that, you just have to pray. But we all know prayer isn't a good tactic for building robust enterprise applications, so we have to very carefully monitor all the places that call the method and ensure they don't pass something that's "kind of" a duck, but that's a PITA. Or we have to build in a bunch of extra "duck enough" validation at the top of the method to ensure all three parts will succeed, but that leads to greatly increased coupling. Or we could use some structured exception handling to catch any errors and manually undo the stuff that's already been done (a la a transaction), but hard to accomplish with sending emails or something.
I'm not against duck typing or dynamically typed languages, though I do prefer the statically typed ones in general. Dynamic typing can grant you flexibility that can massively speed development, but it can also introduce weird loopholes in logic that are very difficult to deal with, because of this "duck enough / not duck enough" scenario.
Hmm, I would point out that if you need transactional integrity in your method then you have to consider all sorts of other possible errors too. Using something other than duck typing here would catch one class of runtime errors but not any of the others…
With a dynamic language, you can't rely on programming-by-contract to avoid errors. I could pass you an object that implements your "contract" but have the third method throw an exception and you still wouldn't have transactional integrity.
I understand your argument but I'm not much swayed by it…
Here's is my problem with this type of argument: what kind of application are you building where someone could, down the road, start entering data that is bunk? I can understand getting corrupted data during the developmental process (3rd of three processes fails leaving two completed), but I figure this sort of thing would be worked out by the time you went to production.
Once a system is in production, not only are the methods built to use the appropriate objects (the three passed in), but the code that PASSES in these objects should also be set up to ONLY pass in appropriate objects.
Now, granted, I don't have much experience in large application developmnet or large develpment groups, but I have never come across a scenario where I didn't have control over the "sending in" code prior to the move to production server.
Theory is one thing, but could you illustrate or give a specific example of where this could happen?
@Ben – One of the benefits of explicit typing and interfaces are that they increase the amount of compile time checking (assuming a compiled language like Java). When would that be an issue? I've just joined a 45 person team building a big app and I'm asked to add a new controller. I write a new controller that doesn't fully implement the interface (hey, the guy who defined the interface left the project 3 years ago to "write something cool in Ruby instead") but the deficiencies in my implementation are subtle and don't show up in the unit testing.
One class of deficiencies can be eliminated by checking that I implement the interface in terms of the types passed and the method signatures.
As sean points out, the problem with interfaces is that they are not a complete specification. There are lots of OTHER ways I might not interact appropriately as a controller even if I happen to match the Java or c# interface such as the manner in which I return or handle error conditions.
I think that static typing and interfaces do remove whole classes of runtime errors. They also eliminate whole classes of runtime flexibility.
Reading my own post I wasn't really clear. Key point is that with dynamic typing I could not fully implement the controller interface and it would only be caught if the unit codes executed the right code path. With static typing my code wouldn't even compile.
It is not that you can't have interfaces in a dynamically typed language, but you rely on documentation or programming conventions rather than having language features to enforce the contract.
I've now seen several of these articles that point out valid concerns with dynamic languages. What the static typing proponents are missing is one of the pillars of the dynamic typing crowd and that is unit testing. Peter, in you comments you state, "… it would only be caught if the unit codes executed the right code path." The dynamic crowd would say that there shouldn't be a code path that doesn't have a unit test. If you don't have a unit test for the code path it doesn't matter whether the program compiles, you can't prove that it's correct.
I'm with Ben on this… and Brian… By the time you go to production you should already have code that can't "randomly" pass objects that don't implement the right method. That's why I think Barney's argument is somewhat specious…
Brian writes: "The dynamic crowd would say that there shouldn't be a code path that doesn't have a unit test."
Doesn't that imply that you're able to write "perfect" unit tests; that is, that your unit tests provide 100% code coverage? If you can't write perfect code in the first place (no one can), what makes you think you can write perfect unit tests? That's why I'd rather rely on the compiler to automatically catch as many errors as it can before I run my unit tests.
I'm with Vince on this. I think it is a little unrealistic to assume 100% code coverage from unit tests. On an associated note, can anyone recommend a code coverage engine for CF? I know there are tools for Java and .NET, but I'm not sure I've come across a tool which identified percentage coverage of CF code based on a given set of unit tests. Without such a tool, I think the chances of getting 100% code coverage are pretty low on a complex project.
The compiler isn't a silver bullet. It's like a seatbelt in a car. It doesn't help if someone shoots you, but in a fender bender it may just save your life.
Also, Sean, you don't just "go to production". With ongoing maintenance and frequent builds, unless you have 100% coverage in your automated unit tests (I'm assuming everyone is using automated unit tests as part of their build process) the compiler just catches one more class of errors in advance. That isn't a bad thing.
Now, whether a compiler is worth what it costs in terms of additional complexity required just to support explicit typing – that's a whole other matter.
Also to clarify, this is a discussion (for me) between true explicit typing (e.g. Java) and true dynamic typing (e.g. CF). In my opinion the benefits of strong typing in CF are a little more modest because there is no compiler – just a different runtime error.
Vince,
I agree, no one can write perfect code. I put errors up on the production site all the time …. er, um… I mean… my co-workers put errors up on the live sites all the time….
But, these are usually minor, non-mission critial errors. I feel that things where type-checking is involved are errors that are BIG and should be caught with even the least amount of testing.
And, even if something important does slip through, I think it's a rare case where the "corruption" of data would be so huge that we couldn't fix it and debug. Of course that changes from project to project (I have never built anything for a bank with money issues and 4 jillion transactions a second).
So, not to ramble, I am sure there are "edge" cases where any issue on the live server can result in massive amounts of data corruption, BUT, I feel that in 99.9% of the cases, that's just not going to happen.
This reminds me of something my digital logic professor talked about once in engineering… He said that's the difference between "Computer Scientists" and "Computer Engineers": CS's are concerned with theory – what about that one case or the complexity of a problem when it appraoaches N and such nonsesnse. Then you have the CE's who don't care so much about theory, they care about numbers: sure a problem has exponential complexity, but I know that this system will never go above "X" and therefore, I don't care about the theory, I know it will always be fast.
Here endeth the rambling.
By your definition of "perfect" unit test, then yes, because the dynamic typing proponents say to use TDD or something like that, so you only write code that addresses a failing test. I'm not saying that you will have "perfect" tests that will handle all situations that can be encountered. The argument is that you can't know if your code correctly handles a situation unless you have a test for the situation regardless of whether the programming language is dynamically or statically typed. The compiler is just another unit test.
@Ben – All it takes for code to fail is for you to forget to add one cfargument to one method. Usually that'll be caught early on in testing, but if the method is a really obscure path, it may not be. Then you start getting phone calls from customers saying your site isn't working but you can't replicate the error.
In the end it turns out that if someones session times out WHILE they are checking out AND if they have not previously ordered from you before (which is where your initial testing fails as you didn't blow away your cookies before testing), it runs code to pull up their default data (and this is the only time this particular path is fully exercised – your general model call works fine), but your controller in this one case forgets to pass the appropriate property back from the method call and doesn't fully initialize your cart so they get a "cannot check out" error.
Now imagine hundreds of thousands of lines of code, large teams of programmers and daily builds with new features. Good news: you don't have to waste time with a compiler. Bad news: you have just been appointed head of QA and it is your ass if these bugs aren't fixed by 3pm!!!
A compiler is not a silver bullet. There are all kinds of problems that a compiler won't catch, but it will catch SOME of these problems so it does provide a real benefit for larger projects and teams. The question then is whether it is worth the cost of that benefit and your milage will vary. For my use case – no way!
Peter wrote: "In my opinion the benefits of strong typing in CF are a little more modest because there is no compiler – just a different runtime error."
I agree that the benefits of strong typing is CF are less that what you'd get in a compiled language, but I think the "just a different runtime error" argument falsely implies that all runtime errors are the same–they're not. The relevant question is: when does the error get caught?
If you specify a CFC type for a CFARGUMENT tag, then you're going to catch the error as soon as you try to pass a CFC as a function parameter that doesn't match the required type–probably during development. If you use duck typing then there may be a path in your code that you miss during testing that isn't caught until the application is put into production. In some environments, fixing post-production bugs isn't a big deal; in others it's a major disaster.
I understand that type checking helps me identify errors. But making everything fit into compatible types takes a lot of brain power. I'd rather devote that brain power directly to finding and fixing errors.
In my experience, simplicity is the best defense against all kinds of errors. The simpler the code is, the better I understand it. The more I understand it, the easier it is to discover and fix errors. Duck typing allows me to make the code simpler.