Quality of error messages

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

   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.

···

On Wed, Oct 06, 2004 at 10:48:49PM +0900, Joachim Wuttke wrote:

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

···

On Oct 6, 2004, at 8:48 AM, Joachim Wuttke wrote:

   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.

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.

···

On Wed, 6 Oct 2004 22:48:49 +0900, Joachim Wuttke <joachim_wuttke@ph.tum.de> wrote:

   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

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

···

On 06.10.2004, at 15:48, Joachim Wuttke wrote:

   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.

Hi,

···

From: Joachim Wuttke <Joachim_Wuttke@ph.tum.de>
Subject: quality of error messages
Date: Wed, 6 Oct 2004 22:48:49 +0900
Message-ID: <Pine.OSF.3.95.1041006144909.11973B-100000@eva.e13.physik.tu-muenchen.de>

   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
------------------------------------------------------------------------
After pressing the second button, you'll get error messages in stderr.
--
                                  Hidetoshi NAGAI (nagai@ai.kyutech.ac.jp)

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?"
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 :slight_smile: 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). :wink:

Eivind.

···

On Wed, 6 Oct 2004 22:48:49 +0900, Joachim Wuttke <joachim_wuttke@ph.tum.de> wrote:
A: "Joe Demeter sends his mates Largo Coupling and Minimo Cohering

Joachim Wuttke wrote:

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

···

--
Brian Schröder
http://ruby.brian-schroeder.de/

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

I agree with almost everything you said. However, I must object to
one point:

...the code example you showed elsewhere in the thread had
two a slight design smells:
...
- The method chaining you do violates the "Law of Demeter" - don't dot
the return value from another method.

      This is, in my opinion, a misreading of the Law of Demeter, which
specifically allows sending messages to objects you create or "own".
There is really no correlation between method chaining and the Law of
Demeter since the intermediate objects in the chain may well be friends,
but in other circumstances they may not. It depends on the details of
the individual case.
   
     In many instances, _not_ chaining methods leads to worse smell, as
it creates lots of set once / used once temporary variables (or worse,
reused for multiple purposes placeholders).

    For example:

        bottom_6 = scores.sort.reverse.values_at[0..5]

smells much better than

        sorted_scores = scores.sort
        reverse_sorted_scores = sorted_scores.reverse
        bottom_6 = reverse_sorted_scores.values_at[0..5]

     -- Markus

···

On Wed, 2004-10-06 at 09:43, Eivind Eklund wrote:

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

Regards,
Ed

···

On Thu, 7 Oct 2004 01:43:52 +0900, Eivind Eklund <eeklund@gmail.com> wrote:

On Wed, 6 Oct 2004 22:48:49 +0900, Joachim Wuttke > <joachim_wuttke@ph.tum.de> wrote:
> Hi Ruby developers,
>
> 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.

--
" Don't relax! It's only your tension that's holding you together."

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 :slight_smile:
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>

···

On Thursday, October 7, 2004, 12:49:33 AM, Joachim wrote:

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

···

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.pos

=> 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

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

···

On Oct 6, 2004, at 9:49 AM, Joachim Wuttke wrote:

=> 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

···

On Wed, 6 Oct 2004 23:49:33 +0900, Joachim Wuttke <joachim_wuttke@ph.tum.de> wrote:
--
Austin Ziegler * halostatue@gmail.com
               * Alternate: austin@halostatue.ca
: as of this email, I have [ 6 ] Gmail invitations

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

···

On 06.10.2004, at 16:49, Joachim Wuttke wrote:

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

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 :

On Oct 6, 2004, at 9:49 AM, Joachim Wuttke wrote:

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

Gavin Sinclair wrote:

···

On Thursday, October 7, 2004, 12:49:33 AM, Joachim wrote:

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 :slight_smile:
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

...

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.

Hi,

···

From: Joachim Wuttke <Joachim_Wuttke@ph.tum.de>
Subject: Tk error message box
Date: Thu, 7 Oct 2004 00:41:07 +0900
Message-ID: <Pine.OSF.3.95.1041006165622.11973F-100000@eva.e13.physik.tu-muenchen.de>

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

I just posted. Please see [ruby-talk:115223].
--
                                  Hidetoshi NAGAI (nagai@ai.kyutech.ac.jp)