From: Mathieu Bouchard Date: 2001-08-12T08:58:06+09:00 Subject: [ruby-talk:19556] Re: My 1st look @ ruby: No prototypes and problem with String#gsub On Mon, 6 Aug 2001, Dave Thomas wrote: > Mathieu Bouchard writes: > > On Sun, 5 Aug 2001, Dave Thomas wrote: > > > If you want to do type testing, you should really use > > > #respond_to?, not #is_a?, as that way you'll make explicit the > > > operations that you're asking your object to have. > > No, you'll make explicit the names of the operations, which is quite > > close, but quite not. You can say this method only requires #[] but > > it's not specified whether it's Array#[] or Hash#[], or the > > VerySpecialThing#[] (whose semantics are not written down anywhere), > > then knowing you need #[] only tells you half of what you need to > > know. > True, but in a dynamic language with open base classes I'm not sure I > see a _viable_ alternative. A "_viable_ alternative" (?) may be to stop writing spaghetti frameworks and start documenting what you're doing. You shouldn't write spaghetti documentation either, and that's why you have to build the abstractions that will support your documentation, instead of pretending that the abstractions don't exist. If the language doesn't keep your back straight, it will teach you to do so, but if you don't believe in spines, it can't do much for you. So you have to realize you are using interfaces all the time, and that you could think explicitly about them, and that you could even write about them, and that it would help you keeping your back straight. But, as you taught me, if it's not executable, it's unreliable. What this means to me is that you need to make your documentation executable (or, in other words, you need to build the abstractions that will support your testing). The easy half of that is creating empty modules for your interfaces, and use them as interface markers, and thus help yourself checking your parameters properly. Whether a class that is said to support XYZ really does so, is a problem you do solve separately; it's the other half of the problem. Fortunately, it's only half of the problem, because you made interfaces explicit in the first place. From there, you do as usual; that is, you pick one or several kinds of testing: * by example: unit-tests * by rule: contracts * by use: write application code that uses it * by prevention: don't make it error-prone The difference between writing unit-tests for particular classes and writing unit-tests for interfaces, is mostly that you have to make your tests reusable, by turning setup-methods (and all other relevant object constructions) into placeholders. (It's what we did together on parts of Rubicon a few months ago.) > > > Again, though, I'd question the usefulness of this. What is the > > > benefit of having an application die with an assertion failure at the > > > top of a routine, when it will fail anyway with a NameError a few > > > lines down? > > Because a few lines down it may be too late. > I don't want to be contentious, and I'm certainly willing to be swayed > on this issue. Could you give a real-world example of this? it will be too late because the NameError won't directly tell you where the problem is. it will be too late because you will store the object in some structure and later get a NameError in an unrelated spot. it will be too late because you will have a database server that may not support transactions, but it's not worth checking for whether it supports them, because you know it doesn't, and you have to do without it. so you have to make sure the transaction will work before it begins; or you must implement a manual rollback by using alot of rescue blocks; or you must implement transactions yourself, by building a log of the inverses of your changes, and execute that log whenever there's an error. it will be too late because you don't have a database server and all the transactional stuff you need is between a set of Ruby arrays and hashes. (of course, if you don't think of trapping a NameError, you won't think of this one) it will be too late because some changes you do are not reversible, e.g. sending data through a socket to a program that you don't know or that can't rollback. it will be too late because the error will happen in the middle of a time-consuming operation, which will hinder your debugging. (etc.) matju