Documenting a class interface when there are no types in the method signature
by Nicolai Czempin » Fri, 20 Aug 2004 02:26:59 GMT
I've recently started using Ruby, coming from a C++/Java background.
And while there where some idiosyncracies, by and large I was able to be
very productive very quickly.
The application has evolved from a simple script (in the sense that
everything "looks global") to something a bit more complex, with
refactorings towards using objects, a "Test-First-After-The-Fact" approach
:-), and to factoring out library-like elements.
Now, I'm aware that there's always a discussion about strong vs. weak,
static vs. dynamic. The last time I had an opinion about it I had no good
answer to the claim that unit-tests lessen the need for static type
checking.
Now that I have gulped down the basics of ruby, I'm starting writing unit
tests, from the point of view of "what would a user of this class expect".
And I'm running into what seems to be a fundamental question, and my
googling so far hasn't turned up any satisfactory answer:
When I define a method in a class, let's say initialize(categories, data)
for the sake of argument: In Java etc. I can see from the method definition
that categories is an ordered set, and data is a Map (and if that info is
not sufficient, javadoc can be used to explain in more detail what is
expected, but I'll leave that out for the moment, it's not really key to the
question).
Of course the first step would be to find a good name (at the very least
"data" is a bit too generic). But what next? How does someone who writes an
API communicate that it makes no sense to send "1" to the categories.
Apparently the unit tests and/or dbc encompass the specification, but does
rdoc or some tool extract any information from them?
Am I the first person to run in this problem? If not, what are other
people's solutions? Do you just look at the source code of any library you
use?
I'm asking this from both points of view:
Many times I have run into some library where I was simply asking myself
"what exactly do I have to put into these parameters to get the method to do
what I want?" More specifically, I had this problem in parts of the REXML
library.
And from the other perspective, what is the Ruby Way (tm) to document
methods to users of your API/framework (or simply your class)?
Re: Documenting a class interface when there are no types in the method signature
by Austin Ziegler » Fri, 20 Aug 2004 03:13:23 GMT
On Thu, 19 Aug 2004 02:26:59 +0900, Nicolai Czempin
Actually, it *is* key to the question, as it's (basically) the Ruby
answer without using external mechanisms[1] like StrongTyping,
csinterface, or other mechanisms. Look at Diff::LCS as an example. The
main method is Diff::LCS.diff(seq1, seq2, callback =
Diff::LCS::DiffCallback). If you know what 'diff' does, then you'll be
able to make a good guess that you'll get something back similar to
'diff' when you pass in seq1 or seq2. But what are seq1 and seq2,
precisely? According to my documentation, they are ordered sequences.
I should be a bit more accurate in the definition, as they do require
random access under the current implementation, but they must be
either String-like or Array-like (Array-like works best, because
Strings work on a per-character basis).
The more confusing part is the callback parameter. There's absolutely
no way that I could meaningfully define the callback on the parameter
list, as it's a truly duck-typing situation. Using the #sdiff, I could
come up with a MatchingCallback that would only return those lines
that are considered equivalent. The only sensible solution here is
documentation.
My first step for any library I use is to look over its documentation
and examples to see basic usage. Preferably example use. Most of the
folks who have written libraries for Ruby have been sensible in their
choices, and they "make sense." After that, I look at the source if
I'm still confused.
-austin
[1] External to Ruby itself. rdoc and ri are now part of the Ruby distribution.
--
Austin Ziegler * XXXX@XXXXX.COM
* Alternate: XXXX@XXXXX.COM
Re: Documenting a class interface when there are no types in the method signature
by Nicolai Czempin » Fri, 20 Aug 2004 03:15:12 GMT
Oh, great, hidden inside another thread... sorry. How do I get it out?
Copy-Pasting the text and posting again and then CANCELing? Sigh..
Documenting a class interface when there are no types in the method signature
by Nicolai Czempin » Fri, 20 Aug 2004 03:17:21 GMT
I've recently started using Ruby, coming from a C++/Java background.
And while there where some idiosyncracies, by and large I was able to be
very productive very quickly.
The application has evolved from a simple script (in the sense that
everything "looks global") to something a bit more complex, with
refactorings towards using objects, a "Test-First-After-The-Fact" approach
:-), and to factoring out library-like elements.
Now, I'm aware that there's always a discussion about strong vs. weak,
static vs. dynamic. The last time I had an opinion about it I had no good
answer to the claim that unit-tests lessen the need for static type
checking.
Now that I have gulped down the basics of ruby, I'm starting writing unit
tests, from the point of view of "what would a user of this class expect".
And I'm running into what seems to be a fundamental question, and my
googling so far hasn't turned up any satisfactory answer:
When I define a method in a class, let's say initialize(categories, data)
for the sake of argument: In Java etc. I can see from the method definition
that categories is an ordered set, and data is a Map (and if that info is
not sufficient, javadoc can be used to explain in more detail what is
expected, but I'll leave that out for the moment, it's not really key to the
question).
Of course the first step would be to find a good name (at the very least
"data" is a bit too generic). But what next? How does someone who writes an
API communicate that it makes no sense to send "1" to the categories.
Apparently the unit tests and/or dbc encompass the specification, but does
rdoc or some tool extract any information from them?
Am I the first person to run in this problem? If not, what are other
people's solutions? Do you just look at the source code of any library you
use?
I'm asking this from both points of view:
Many times I have run into some library where I was simply asking myself
"what exactly do I have to put into these parameters to get the method to do
what I want?" More specifically, I had this problem in parts of the REXML
library.
And from the other perspective, what is the Ruby Way (tm) to document
methods to users of your API/framework (or simply your class)?
Re: Documenting a class interface when there are no types in the method signature
by Florian Gross » Fri, 20 Aug 2004 03:58:15 GMT
This is a multi-part message in MIME format.
No, RDoc itself can't extract information from Unit Tests. I'm right now
commonly embedding some simple unit tests into my documentation which
also act as handy sample code. Like in this -- bad and made-up -- example:
# Returns the first of the two halves of an Object.
# This works with all Objects that respond to the #[] and #size methods.
#
# Example code:
# halve("hello world") # => "hello"
# halve([1, 2, 3, 4]) # => [1, 2]
# halve(Object.new) # raises NoMethodError
def halve(obj)
obj[0, obj.size / 2]
end
I have written a small tool that will extract the embedded sample code
and run it as unit tests. It's still not perfect, but I've attached it
to this mail -- maybe it is of some use for you.
Here's a real world example: (from the evil-ruby project)
So my answer to the question "How does a library user know what protocol
objects need to comply with when they are used as arguments for method
X?" is "It's specified in the documentation of method X. There's also
sample code in there which will yield failing unit test when the
protocol changes so the documentation won't be outdated."
I hope I could answer at least some of your questions.
Regards,
Florian Gross
Re: Documenting a class interface when there are no types in the method signature
by James Britt » Fri, 20 Aug 2004 05:40:52 GMT
Sometimes, though I'd prefer not to. Too lazy.
It helps when the arguments have meaningful names.
I don't know about The Ruby Way, but when I have a method that will
accept different sorts of objects for the same parameter, I simple note
that in the rdoc.
There are (at least) two types of object substitution. In one, the
method is basically expecting, say, a String, but really only cares that
it responds to one or two String methods.
In the other case, the method knows that it might receive objects of a
limited set. For example, a method for XML processing that knows what
to do if given either a String or a REXML Document.
In the first case, the parameter name might be enough to indicate what
to pass in. In the second case, some explicit docs are needed to make
clear that one can pass either something that acts like a String, or
something that acts like a REXML Document. (Well, you could use a
parameter named 'xml_as_either_string_or_REXML_doc'.)
I'm thinking, though, that this is not so much a dynamic typing issue,
but a problem for API design and documentation in general. For example,
simply knowing that some Java method requires a Collection object may
not be enough. A Collection of what? The method name, and the names of
the parameters, need to express the reason the method exists and why you
might be interested in using it.
Even when you know the exact type for a parameter, you still have to
know the range of acceptable or meaningful values. Sometimes the best
way for a developer to indicate that is to simply write it down.
James
Re: Documenting a class interface when there are no types in the method signature
by Gavin Sinclair » Fri, 20 Aug 2004 12:53:20 GMT
Nicolai wrote, in part:
Regarding your example of initialize, I tend to do this.
#
# Detailed usage example...
#
class Foo
# (String) blah blah blah
attr_reader :name
# (Hash: Symbol -> Set) blah blah blah
attr_reader :categories
# (IO-like: responds to ...) blah blah blah
attr_reader :output
def initialize(name, output)
...
end
def add_category(id, thing)
@categories[id] ||= Set.new
@categories[id] << thing
end
end
My point: if you provide a decent usage example, then people can read that
and see what they need to do to work with the class. Give some nitty
gritty on the attributes, because they are usually key to understanding
the class. Then the methods will often not need documenting at all.
Cheers,
Gavin
Similar Threads:
1.A mechanism for specifying static type signatures (NOT STATIC TYPING)
2.Implementing interface using classes ( Interfaces and semantics)
Let's say I want to implement interfaces using classes (so I can do
interface checking with the simple Object#kind_of?). There will be
components (which will also be classes). A component can implement one
or more interfaces. Method names from different interfaces might clash
(e.g. IFoo#quack vs IBar#quack) and we need to be able to choose which
interface we want to use from a component.
What's the best way to implement this? My current attempt:
--- BEGIN OF CODE ---
# components will derive from the Component class, to share
# some common code
class Component
# @implements will contain a list of interfaces that this component
# implements
def Component.implements; @implements end
def implements; self.class.implements end
def Component.implements?(itf); @implements.include? itf end
def implements?(itf); self.class.implements? itf end
# @implementors is a hash, containing the class that implements a
# certain interface.
def Component.implementors; @implementors end
def implementors; self.class.implementors end
# will return an instance of implementor class
def use_interface(itf, *args)
raise ArgumentError unless implements? itf
raise NotImplementedError unless implementors[itf]
implementors[itf].new(*args)
end
end
# IHasFeet is an interface
class IHasFeet
# s is speed
def walk(s); raise NotImplementedError end
# d is the name of a dance
def entertain(d); raise NotImplementedError end
end
# IHasMouth is an interface
class IHasMouth
# sp is speed, str is string
def talk(sp, str); raise NotImplementedError end
# s is the name of a song
def entertain(s); raise NotImplementedError end
end
# Human is a component. It implements IHasFeet and IHasMouth.
class Human < Component
@implements = [IHasFeet, IHasMouth]
class Human_IHasFeet < IHasFeet
def walk(s); puts "Walking..." end
def entertain(d); puts "Can't dance!" end
end
class Human_IHasMouth < IHasMouth
def talk(sp, str); puts str end
def entertain(s); puts "Singing #{s}..." end
end
@implementors = {IHasFeet => Human_IHasFeet,
IHasMouth => Human_IHasMouth}
end
# Bear is a component. It implements IHasFeet and IHasMouth.
class Bear < Component
@implements = [IHasFeet, IHasMouth]
class Bear_IHasFeet < IHasFeet
def walk(s); puts "Walking..." end
def entertain(d); puts "Dancing #{d}..." end
end
class Bear_IHasMouth < IHasMouth
def talk(sp, str); puts "R O A R!" end
def entertain(s); puts "Can't sing!" end
end
@implementors = {IHasFeet => Bear_IHasFeet,
IHasMouth => Bear_IHasMouth}
end
# test
p Bear.implements # -> [IHasFeet, IHasMouth]
p Bear.implements? IHasMouth # -> true
bear = Bear.new
p bear.implements # -> [IHasFeet, IHasMouth]
p bear.implements? IHasFeet # -> true
b = bear.use_interface(IHasFeet)
p b.kind_of? IHasFeet # -> true
b.walk(1) # -> Walking...
b.entertain("russian dance") # -> Dancing russian dance...
b = bear.use_interface(IHasMouth)
b.entertain("o canada") # -> Can't dance!
--- END OF CODE ---
The code feels very mechanic and contains a lot of plumbing, but it's
pretty much what I wanted (my requirements):
a) a component can implement multiple interfaces;
b) I can pick interfaces from components without fear of conflict;
c) my other code can later check whether something has a feet or a
mouth, without explicitly checking its component class (Human or Bear).
Any better suggestion?
3.I am confused with Value Type Objects
4.[SiSU] book/document generator, sample documents: summary page for document markup and output
5.Parsing Ruby to get info about method signatures?
I'm interested in parsing some Ruby code for the purpose of extracting
information about the method signatures (including argument names and
default argument values). For example, if I had a method like:
def foo(a, b=2, c=3)
...
end
I'd like to be able to programmatically determine that I have a method
named "foo", with arguments "a", "b" and "c", with defaults of 2 and 3
for the latter two arguments. (I will use that information about the
method signatures to generate some additional code.)
Does anyone have any experience with this? I looked at the XML
generator for RDoc, but its output doesn't appear to provide this kind
of information. Ryan's ParseTree library may do the trick, or worst
case, I could write some kind of custom parsing tool myself. Just
wanted to see if anyone has any thoughts on this.
6. [proto-rcr] Blocks: default arguments and method signatures
7. [RDOC] Documenting accessor methods as methods
8. Converting Word Documents (and other types of files) to PDF in a rails application