ruby-style-guide icon indicating copy to clipboard operation
ruby-style-guide copied to clipboard

Recommend calling super in initialize

Open headius opened this issue 5 years ago • 11 comments

Many users forget to call super from initialize, which can lead to tricky bugs if the superclass needs to initialize its own state.

Perhaps this should be in the style guide?

I'm not sure if it should be a hard failure, since classes that extend Object or BasicObject or a few other core types don't technically need to invoke super, but the safest pattern would be to always do so.

headius avatar Mar 27 '20 20:03 headius

@headius Sounds good to me. Would you like to formulate the guideline yourself?

bbatsov avatar Mar 28 '20 16:03 bbatsov

Sure I'll see if I can put together a PR.

headius avatar Mar 29 '20 04:03 headius

:+1: Please extend this rule to self.inherited and similar callbacks.

marcandre avatar Jun 13 '20 23:06 marcandre

👍 Please extend this rule to self.inherited and similar callbacks.

And maybe a note that typically included, prepended and extended are not needed, because writing one doesn't interrupt the a potential non-trivial call chain.

marcandre avatar Aug 06 '20 20:08 marcandre

What about when you explicitly are not wanting to invoke the superclass's behavior?

jcoyne avatar Aug 10 '20 15:08 jcoyne

What about when you explicitly are not wanting to invoke the superclass's behavior?

Do you have a good example where that would be the case? As with all rules, some can be bent or broken, but as a general principle I would imagine this would be due to a weakness of the superclass...

marcandre avatar Aug 10 '20 15:08 marcandre

@marcandre I would agree that using inheritance improperly could be one such reason not to call the parent initializer. Brevity is also a reason. There is no sense in calling super on a subclass of ViewComponent::Base because it doesn't do anything: https://github.com/github/view_component/blob/master/lib/view_component/base.rb#L98

jcoyne avatar Aug 11 '20 02:08 jcoyne

@jcoyne Indeed, this method has no body. Yet. By not wanting to invoke super, you are preventing that class to refactor it's initializer without breaking compatibility. Moreover I'll note that there is an initialize method and it is there only to swallow any arguments. I'm not the author, but the only reason I can see the author defined this initialize is to allow subclasses to call super instead of super().

I still think it's a good rule of thumb (and especially in external dependencies like ViewComponent::Base), but maybe you will want to chime in when we get an actual proposal for the style guide about how to word this. I'd say something "You should", maybe @headius will propose "It's recommended" and maybe you can get it down to "Some feel it's a good idea to ... so don't be surprised if you see a call to super to a method that doesn't (yet) do anything" 😆

marcandre avatar Aug 11 '20 02:08 marcandre

there is a super common pattern that breaks this purpose:

# frozen_string_literal: true

class ApplicationService
  def self.call(*args, **kwargs, &block)
    new(*args, **kwargs, &block).__send__ :perform
  end
end
# frozen_string_literal: true

class SomeService < ApplicationService
    def initialize(a_param:)
      @a_param = a_param
    end

    private

    def perform
      puts "I'm an useless service that puts the param: #{@a_param}"
    end
  end
SomeService.call(a_param: 'something')

Why would I like to call super in SomeService initializer?

EDIT:

fortunately all my services objects are inside app/services so I added this to rubocop.yml

Lint/MissingSuper:
  Exclude:
    - 'app/services/**/*'

silva96 avatar Aug 12 '20 20:08 silva96

Why would I like to call super in SomeService initializer?

@silva96 In this particular example, it does nothing and perhaps the super call is not needed. The problem is that you don't know this is the case if the superclass is part of another library or maintained by someone else. The base class may add some initialization logic in the future that would not get run by your subclass.

It is worth pointing out that the Java language has required all constructors to call a superconstructor since the beginning for exactly this reason: not calling the superclass constructor breaks the guarantee that it will run when instances of that class or its subclasses are instantiated. Java code requires that the subclass invoke a superclass constructor directly, or else a call to the superclass's default (no-args) constructor will be inserted for you.

headius avatar Oct 28 '21 20:10 headius

Why would I like to call super in SomeService initializer?

@silva96 In this particular example, it does nothing and perhaps the super call is not needed. The problem is that you don't know this is the case if the superclass is part of another library or maintained by someone else. The base class may add some initialization logic in the future that would not get run by your subclass.

It is worth pointing out that the Java language has required all constructors to call a superconstructor since the beginning for exactly this reason: not calling the superclass constructor breaks the guarantee that it will run when instances of that class or its subclasses are instantiated. Java code requires that the subclass invoke a superclass constructor directly, or else a call to the superclass's default (no-args) constructor will be inserted for you.

Good Point

silva96 avatar Nov 03 '21 20:11 silva96