Ruby NotImplementedError — Don’t use it!

Oleg Potapov
3 min readNov 27, 2020

--

To be accurate, the title should be — don’t use NotImplementedError unless you write platform-specific low-level code.

Have you ever seen something similar to the following code?

class SomeAbstractClass
def some_method
raise NotImplementedError.new(“method should be implemented in concrete class”)
end
end

This code looks good and it even works most of the time, but using NotImplementedError here may be harmful.

The name of the exception can be so confusing that developers often misuse it and recommend this misinterpreted use to others online:

https://stackoverflow.com/questions/13668068/how-to-signal-not-implemented-yet

https://github.com/rubocop-hq/ruby-style-guide/issues/458

According to the Ruby documentation, a NotImplementedError is:

Raised when a feature is not implemented on the current platform. For example, methods depending on the fsync or fork system calls may raise this exception if the underlying operating system or Ruby runtime does not support them.

Note that if fork raises a NotImplementedError, then respond_to?(:fork) returns false.

It clearly says that the purpose of this exception is completely different, it’s supposed to be used in operating-system-dependent methods to indicate that the current runtime platform doesn’t support some specific functionality. Here’s an example of proper usage of this exception from ruby source code:

def gen_random_urandom(n)
ret = Random.urandom(n)
unless ret
raise NotImplementedError, “No random device”
end
unless ret.length == n
raise NotImplementedError, “Unexpected partial read from random device: only #{ret.length} for #{n} bytes”
end
ret
end

This code will raise an exception if the runtime platform doesn’t provide a proper source of entropy (for example: /dev/urandom for Unix systems)

But who reads the documentation, right?

Ok, we get the point — the usage of NotImplementedError in abstract methods or as a TODO replacement is semantically incorrect. But what if I just like its name and think that it makes my code more readable? I could even raise a ZeroDivisionError if I want to!

Surprisingly, that’s not true. Let’s look into the Ruby exception hierarchy.

We can see that NotImplementedError’s ancestor is ScriptError, not the more common StandardError. Let’s return to the documentation:

ScriptError is the superclass for errors raised when a script can not be executed because of a LoadError, NotImplementedError or a SyntaxError. Note these type of ScriptErrors are not StandardError and will not be rescued unless it is specified explicitly (or its ancestor Exception).

What does it mean? It means that rescuing the error may not work as expected. For example:

# it will not work
begin
raise NotImplementedError
rescue => e
puts “rescue works”
end
# it will work
begin
raise NotImplementedError
rescue Exception => e
puts “rescue works”
end

It may be harmful, for example when you use some kind of error-tracking software, like Sentry or Rollbar. Your errors won’t show up there.

What should you use instead?

There doesn’t seem to be any kind of universal consensus in the Ruby community, but I can suggest three possible options:

  1. Use NoMethodError. Out of existing Ruby exceptions, it makes the most sense for abstract methods.
  2. Since situations where you get this kind of error should be quite rare, you may not need a specific error class at all, e.g. ‘raise “method should be implemented in concrete class” ‘ . This will raise a RuntimeError with the specified message.
  3. You can define your own exception class and use it across the whole project, e.g. class AbstractMethodError < StandardError

All three options are much better than using NotImplementedError incorrectly just because it sounds good.

Links

https://ruby-doc.org/core-2.5.0/NotImplementedError.html

https://ruby-doc.org/core-2.5.0/ScriptError.html

https://www.geeksforgeeks.org/ruby-exceptions/

--

--

Oleg Potapov
Oleg Potapov

Written by Oleg Potapov

Backend developer, interested in Ruby, Elixir, Postgres, Domain-Driven Design and Distributed Systems

Responses (2)