quality of error messages

J

Joachim Wuttke

Hi Ruby developers,

after spending some time with Perl and Python, I am
currently exploring Ruby. While I am quite enthusiastic
about some of its features, I am increasingly preocuppied
about the difficulties to localize errors.

Example 1: missing "end" in <last-line-of-code>
-> I forget to close a "class" or a "do" or a "if" block -
but which one ? Shouldn't the system be able to name
possible locations of unclosed block openings ?

Example 2: NoMethodError: undefined method.
-> Wrong, I defined the method. I just forgot to
include it in the attr_reader list. The system
shouldn't mislead me.

Developers, do you share my opinion that this would
be worth working upon ? I would really like to hear that
error messages will improve in future versions of the
interpreter.

Once I open a Ruby/Tk session, error messages are
redirected to an annoying popup window. Can I disable
this behavior ? I would prefer to see errors in stdout,
even when I am running a GUI.

Regards, Joachim
 
B

Brian Candler

Example 1: missing "end" in <last-line-of-code>
-> I forget to close a "class" or a "do" or a "if" block -
but which one ? Shouldn't the system be able to name
possible locations of unclosed block openings ?

I agree with that one. I have often has to do a 'divide and conquer': create
a temporary file, copy in one class or method, run, paste in some more, run
again, until I locate the section of code which contains the error.

I think it's difficult for a parser to know where you might have missed an
'end'. It could show you where it thinks the most recent 'do'/'def' was, but
the error could be much, much higher up.

It seems that Ruby permits nested 'defs', which makes it almost impossible
to detect the error. But since nested defs are not common, perhaps there
could be a way to track them. What about if "ruby -W3" gave a warning for a
nested 'def'? That would allow you to isolate the error to a single method,
which would make life a lot easier.

class Foo
def bar(x)
if x == 3
puts "hello"
end

def baz(y) # <-- could error here?
end
end # <-- actually errors here

Example 2: NoMethodError: undefined method.
-> Wrong, I defined the method. I just forgot to
include it in the attr_reader list. The system
shouldn't mislead me.

That error is correct - perhaps you can give an example of code where you
think it is misleading.

Remember that
@foo = "hello"

sets an instance variable, but it does NOT create a method. You can do:

def foo
@foo
end
def foo=(x)
@foo=x
end

or you can do

attr_accessor :foo

to define corresponding methods automatically, but instance variables
purposely do not automatically gain accessor methods.

Regards,

Brian.
 
J

James Edward Gray II

Example 2: NoMethodError: undefined method.
-> Wrong, I defined the method. I just forgot to
include it in the attr_reader list. The system
shouldn't mislead me.

Could you please show a short example of code that triggers this
message and your solution for it? I don't think I understood the
description.

James Edward Gray II
 
B

Bill Atkins

Hi Ruby developers,

after spending some time with Perl and Python, I am
currently exploring Ruby. While I am quite enthusiastic
about some of its features, I am increasingly preocuppied
about the difficulties to localize errors.

Example 1: missing "end" in <last-line-of-code>
-> I forget to close a "class" or a "do" or a "if" block -
but which one ? Shouldn't the system be able to name
possible locations of unclosed block openings ?

From what I understand, it's pretty difficult for Ruby (or any other
compiler) to figure out where the improperly-ended block began. Using
ruby-mode or some other automatic indenting system can help you spot
these errors.

Consider the following C code:

int main() {
if(1) {
// do something

int var = 34;
while(var < 10) {
var--;
}
}

gcc gives a similar message ("syntax error at end of input").
Obviously, the bracket for the if statement was never closed, but it's
difficult for a compiler to tell where it was supposed to end. (For
example, is the bracket closing the while meant to close the if
statement?)

Example 2: NoMethodError: undefined method.
-> Wrong, I defined the method. I just forgot to
include it in the attr_reader list. The system
shouldn't mislead me.

Hmm, you shouldn't be listing methods you've defined in an attr list.
the attr_* methods define getter and/or setter methods for the names
you pass to them.

Maybe you mean that you haven't declared an attr for some instance
variable and running object.variable gives you a NoMethodError. In
that case, remember that Ruby has no way to know the object.variable
is an attr_reader as opposed to a regular, user-defined method. Just
keep in mind that all accessors are just methods and then the error
message will probably seem more informative.
 
J

Joachim Wuttke

Encouraged by your quick response, I try to explain better
what I meant in example 2:

class Foo
attr_reader ... # here I forgot :pos
def initialize
...
@pos = ...
end
end
...
blabla = someClass.someMethod.link_to_another_class.methods_pos.oof

=> system complains "no such method"

Formally, the system is right: I forgot to declare the read
access method for the variable pos.

But:
(1) the system should tell me which method in the above
chain causes the problem
(2) as a naive programmer, with a background in other languages,
I do not think of pos as method. I think of it as variable.
Once the system does not find a method, it could check whether
there is a local variable of same name, and then print out
an error message like
"no method 'pos', no read access to local variable 'pos'".

Joachim
 
G

Gavin Sinclair

Encouraged by your quick response, I try to explain better
what I meant in example 2:
class Foo
attr_reader ... # here I forgot :pos
def initialize
...
@pos = ...
end
end
...
blabla = someClass.someMethod.link_to_another_class.methods_pos.oof

=>> system complains "no such method"
Formally, the system is right: I forgot to declare the read
access method for the variable pos.

It tells you what method is missing from what object/class. This
should be enough for you to work out the problem.
But:
(1) the system should tell me which method in the above
chain causes the problem

See above. Is your error message different from mine? (See IRB
transcript below.)
(2) as a naive programmer, with a background in other languages,
I do not think of pos as method. I think of it as variable.

The system is helping you, then, by reminding you it's a method :)
There's no such thing as direct access to instance variables in Ruby.
Once the system does not find a method, it could check whether
there is a local variable of same name, and then print out
an error message like
"no method 'pos', no read access to local variable 'pos'".

Too much effort. Better to just learn Ruby properly. I'm not
suggesting Ruby couldn't be made more user-friendly, but there's no
need in this area, IMO.

Cheers,
Gavin

P.S. IRB transcript:

$ irb
irb(main):001:0> class A
irb(main):002:1> def foo
irb(main):003:2> b = B.new
irb(main):004:2> b.bar
irb(main):005:2> end
irb(main):006:1> end
=> nil
irb(main):007:0> class B
irb(main):008:1> end
=> nil
irb(main):009:0> A.new.foo
NoMethodError: undefined method `bar' for #<B:0x10272b28>
from (irb):4:in `foo'
from (irb):9
irb(main):010:0>
 
J

Joachim Wuttke

Sorry, there was a misprint in the crucial line of my last
mail. Here comes the hopefully correct version:
 
J

James Edward Gray II

Encouraged by your quick response, I try to explain better
what I meant in example 2:

class Foo
attr_reader ... # here I forgot :pos
def initialize
...
@pos = ...
end
end
...
blabla = someClass.someMethod.link_to_another_class.methods_pos.oof

=> system complains "no such method"

Formally, the system is right: I forgot to declare the read
access method for the variable pos.

But:
(1) the system should tell me which method in the above
chain causes the problem

I was sure Ruby does this:

james% ruby broken.rb
broken.rb:9: undefined method `value' for #<Broken:0x1ca954 @value=3>
(NoMethodError)
james% cat broken.rb
#!/usr/bin/env ruby

class Broken
def initialize( value )
@value = value
end
end

puts Broken.new(3).value

__END__

Looks right to me. Is that what you're seeing?
(2) as a naive programmer, with a background in other languages,
I do not think of pos as method. I think of it as variable.
Once the system does not find a method, it could check whether
there is a local variable of same name, and then print out
an error message like
"no method 'pos', no read access to local variable 'pos'".

In Ruby all instance variables are private, so there's really no need
for such distinctions. A class defines its interface through its
methods.

I think your misconceptions here are just a few Rubisms that haven't
quite sunk in yet. Hopefully, you'll find the messages making more
sense over time.

And until then, just yell if you need us...

James Edward Gray II
 
F

Florian Frank

Example 2: NoMethodError: undefined method.
-> Wrong, I defined the method. I just forgot to
include it in the attr_reader list. The system
shouldn't mislead me.

It doesn't. If you define the method with "def" yourself, you don't
have to call attr_reader at all.

attr_reader :foobar

generates an instance method like

def foobar
@foobar
end

that returns the instance variable @foobar.

You can actually define your own metaprogramming methods:

class Module
def attr_writer_positive(*ids)
ids.each do |id|
define_method("#{id}=") do |value|
raise "not positive" unless value > 0
instance_variable_set("@#{id}", value)
end
end
nil
end
end
# => nil

class Klass

attr_writer_positive :foo

end
# => nil

k = Klass.new
# => #<Klass:0x5de78>
k.foo = 5
# => 5
k.foo = -5
RuntimeError: not positive
from foo.rb:5:in `foo='
from foo.rb:4:in `foo='
from foo.rb:21

Florian Frank
 
A

Austin Ziegler

=> system complains "no such method"

Formally, the system is right: I forgot to declare the read
access method for the variable pos.

But:
(1) the system should tell me which method in the above
chain causes the problem

irb(main):004:0> e = Object.new
=> #<Object:0x28c6f50>
irb(main):005:0> e.pos
NoMethodError: undefined method `pos' for #<Object:0x28c6f50>
from (irb):5

Now, it's impossible to tell which object returned is causing a problem, really:

irb(main):012:0> e = OpenStruct.new
=> <OpenStruct>
irb(main):013:0> e.f = OpenStruct.new
=> <OpenStruct>
irb(main):014:0> e.f.g = OpenStruct.new
=> <OpenStruct>
irb(main):015:0> e.f.g.h = Object.new
=> #<Object:0x28db698>
irb(main):016:0> e.f.g.h.pos
NoMethodError: undefined method `pos' for #<Object:0x28db698>
from (irb):16

But as a smart programmer, you'll note that it's whatever object is
calling "pos" -- because it does tell you which method is missing, and
obviously, the object returned by "h" is the one that doesn't support
pos.
(2) as a naive programmer, with a background in other languages,
I do not think of pos as method. I think of it as variable.
Once the system does not find a method, it could check whether
there is a local variable of same name, and then print out
an error message like
"no method 'pos', no read access to local variable 'pos'".

Absolutely not. Consider:

class FooReader
attr_reader :pos
def initialize(stream)
@stream = stream
end
def open
@stream.open unless @stream.opened?
@pos = @stream.pos
end
def read(size = 1)
raise "Stream must be opened" if @pos.nil?
data = @stream.read(size)
@pos = @stream.pos
data
end
end

@pos is *never* initialized during the initializer; thus, you know the
stream hasn't even been opened until you call Foo#open.

There is no meaningful correspondence between an instance variable and
its accessor methods. The proper answer here is for you to retrain
your thinking.

-austin
 
F

Florian Frank

But:
(1) the system should tell me which method in the above
chain causes the problem

Actually it does.
(2) as a naive programmer, with a background in other languages,
I do not think of pos as method. I think of it as variable.
Once the system does not find a method, it could check whether
there is a local variable of same name, and then print out
an error message like
"no method 'pos', no read access to local variable 'pos'".

You should think of it as a method. That's called the Uniform Access
Principle. It's true that almost all of the so called OOP languages get
it wrong, but that doesn't mean that Ruby should join them. Consider
that you change the implementation of your class and "pos" isn't a
variable (field) anymore but computed instead. In some other languages
you now have to change all the client code that directly accesses the
variable. That's exactly the opposite of data hiding and encapsulation
of state.

Florian Frank
 
J

Joachim Wuttke

In my previous posting I complaint about an error message
which I quoted incorrectly from memory. Contrarily to what
I wrote, the system actually does tell me which of several
dot-linked messages it does not find. Sorry for that.

It would be much easier for me to debug my code if I could
scroll up present and past error messages. This is impossible
as soon as the program gets to the Tk mainloop.

Is there a possibility to re-redirect error message from the
Tk popup box to stderr ?

Thanks, Joachim
 
H

Hidetoshi NAGAI

Hi,

From: Joachim Wuttke <[email protected]>
Subject: quality of error messages
Date: Wed, 6 Oct 2004 22:48:49 +0900
Message-ID: said:
Once I open a Ruby/Tk session, error messages are
redirected to an annoying popup window. Can I disable
this behavior ? I would prefer to see errors in stdout,
even when I am running a GUI.

Please use TkBgError.set_handler.
For example,
------------------------------------------------------------------------
require 'tk'

TkButton.new:)text=>'raise error',
:command=>proc{raise 'baaaaar'}).pack:)fill=>:x)

TkButton.new:)text=>'set bgerror',
:command=>proc{
STDERR.print("call TkBgError.set_handler\n")
TkBgError.set_handler{|msg|
STDERR.puts(msg)
STDERR.puts(Tk.tk_call('set', '::errorInfo'))
}
}).pack:)fill=>:x)

Tk.mainloop
 
H

Hidetoshi NAGAI

Hi,

From: Joachim Wuttke <[email protected]>
Subject: Tk error message box
Date: Thu, 7 Oct 2004 00:41:07 +0900
Message-ID: said:
Is there a possibility to re-redirect error message from the
Tk popup box to stderr ?

I just posted. Please see [ruby-talk:115223].
 
G

Guillaume Marcais

Although it has never been a big problem for me to see the "no such
method" error, here is something you could do for debugging:

$ cat error.rb
#!/usr/bin/env ruby

class Broken
def method_missing(meth, *args)
msg = "No method #{meth}."
if instance_variables.include?("@" + meth.to_s)
msg <<= " Forgot to attr_reader :#{meth}?"
end
raise msg
end

def initialize( value )
@value = value
end
end

puts Broken.new(3).value
$ ruby error.rb
error.rb:9:in `method_missing': No method value. Forgot to attr_reader
:value? (RuntimeError)
from error.rb:17

Guillaume.


Le 6 oct. 04, à 11:04, James Edward Gray II a écrit :
 
E

Eivind Eklund

Hi Ruby developers,

after spending some time with Perl and Python, I am
currently exploring Ruby. While I am quite enthusiastic
about some of its features, I am increasingly preocuppied
about the difficulties to localize errors.

Example 1: missing "end" in <last-line-of-code>
-> I forget to close a "class" or a "do" or a "if" block -
but which one ? Shouldn't the system be able to name
possible locations of unclosed block openings ?

This is parser-technically hard to do; maybe it would be possible to
look at when indenting got wrong, but I suspect that would be hard to
add into the present lex/yacc style parser factoring.

On a separate note: I have very few problems with this; I also program
in Perl, and get a lot more problems of this kind in Perl. I can
think of a few of my practices that minimize the problem:
- I always write the opening and closing delimiters for a statement
at the same time and then fill in data between them
- I use vim with hot braces and parantheses, so I see if I have a
mismatch (however, this should tip the opposite direction for Ruby)
- I use
- I always review my diffs with -ub to find what I may have changed
wrong if I get a parse error. I also do very many very small commits
(and use cdiff - http://people.freebsd.org/~eivind/cdiff - to review
my diffs).
Example 2: NoMethodError: undefined method.
-> Wrong, I defined the method. I just forgot to
include it in the attr_reader list. The system
shouldn't mislead me.

As others have said: You really need to adjust your thinking to Ruby
here. Also, the code example you showed elsewhere in the thread had
two a slight design smells:
- Trying to get a variable smells of violating "Tell, don't ask"
- The method chaining you do violates the "Law of Demeter" - don't dot
the return value from another method.

Q: "What are the consequences of violating the Law of Demeter?"
A: "Joe Demeter sends his mates Largo Coupling and Minimo Cohering
round to beat the shit out of your system."
(Credit: Graham Perkins)

Note that "Law" is a very strong statement - this is a guideline.
Developers, do you share my opinion that this would
be worth working upon ? I would really like to hear that
error messages will improve in future versions of the
interpreter.

I think it is well worth making better error messages. But then
again, I always think that :) I hope the messages will be better in
later versions of Ruby, and I'm sure that matz will accept patches to
this effect (assuming they do not break anything else). ;-)

Eivind.
 
E

Edgardo Hames

This is parser-technically hard to do; maybe it would be possible to
look at when indenting got wrong, but I suspect that would be hard to
add into the present lex/yacc style parser factoring.

There are Error Correcting Parsers and generators that could be used
to correct the errors Joachim mentions and can log them. You can
google for it and learn how to build them. For a parser library in
Haskell, checkout

http://research.microsoft.com/~emeijer/Papers/Parsec.pdf

Regards,
Ed
 
B

Brian Schröder

Joachim said:
Hi Ruby developers,

after spending some time with Perl and Python, I am
currently exploring Ruby. While I am quite enthusiastic
about some of its features, I am increasingly preocuppied
about the difficulties to localize errors.

Example 1: missing "end" in <last-line-of-code>
-> I forget to close a "class" or a "do" or a "if" block -
but which one ? Shouldn't the system be able to name
possible locations of unclosed block openings ?

I highly recommend using an editor like xemacs that has proper automatic
indentation. Normally I see at which level I am, and if somethings going
wrong I just reindent the whole file and can see where somethings not
properly aligned.

Regards,

Brian
 
C

Charles Hixson

Gavin said:
=>> system complains "no such method"




It tells you what method is missing from what object/class. This
should be enough for you to work out the problem.




See above. Is your error message different from mine? (See IRB
transcript below.)




The system is helping you, then, by reminding you it's a method :)
There's no such thing as direct access to instance variables in Ruby.




Too much effort. Better to just learn Ruby properly. I'm not
suggesting Ruby couldn't be made more user-friendly, but there's no
need in this area, IMO.

Cheers,
Gavin

...
Perhaps not in that precise area, however when a block isn't close the
error message should indicate the starting line of the unclosed block,
instead of just mentioning a line one past the last line of the file.

I have grown quite paranoid about creating large chunks of code between
tests... And large here can mean only fixing more than five errors in
one pass.
 
B

Brian Candler

Perhaps not in that precise area, however when a block isn't close the
error message should indicate the starting line of the unclosed block,
instead of just mentioning a line one past the last line of the file.

I think that what you're asking for, is not what you actually want.
Consider:

class A
def m1
end
def m2
end
def m3(x)
if x == 3
puts "hello"
end
end
class B
def m1
end
def m2
end
end

The error is obvious to you, right? But because identation is not signficant
in Ruby, the parser will read it as:

class A
def m1
end
def m2
end
def m3(x)
if x == 3
puts "hello"
end
end
class B # nested class definition (A::B)
def m1
end
def m2
end
end
# missing 'end' error reported here

So where is the starting line of the unclosed block? As far as Ruby is
concerned, it is line 1 ('class A'). Unfortunately, that doesn't help you
locate the error at all.
I have grown quite paranoid about creating large chunks of code between
tests... And large here can mean only fixing more than five errors in
one pass.

Yeah, I understand that, hence my divide-and-conquer strategy for dealing
with broken files. An auto-indenter would help lots though; if one existed
as a standalone program (i.e. not part of emacs or whatever) I'm sure I'd
use it. Perhaps 1.9 with its userland parser will be able to do this.

Regards,

Brian.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top