Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recommend calling super in initialize #809

Open
headius opened this issue Mar 27, 2020 · 11 comments
Open

Recommend calling super in initialize #809

headius opened this issue Mar 27, 2020 · 11 comments
Labels

Comments

@headius
Copy link

headius commented Mar 27, 2020

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.

@bbatsov
Copy link
Collaborator

bbatsov commented Mar 28, 2020

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

@headius
Copy link
Author

headius commented Mar 29, 2020

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

@marcandre
Copy link
Contributor

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

@marcandre
Copy link
Contributor

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

@jcoyne
Copy link

jcoyne commented Aug 10, 2020

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

@marcandre
Copy link
Contributor

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

@jcoyne
Copy link

jcoyne commented Aug 11, 2020

@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

@marcandre
Copy link
Contributor

@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" 😆

@silva96
Copy link

silva96 commented Aug 12, 2020

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/**/*'

rsanheim added a commit to simpledotorg/simple-server that referenced this issue Aug 24, 2020
This is required to make the latest version of standard happy

See rubocop/ruby-style-guide#809
and rubocop/rubocop#8376 for context
rsanheim added a commit to simpledotorg/simple-server that referenced this issue Aug 24, 2020
* Show less months in the dropdown

* A bit less top padding for charts

* tests for to_s

* Adjust month formatting to Mon-Year

* Update standard, apply fixes that can auto apply

* Ensure we call super so parent classes can initialize

This is required to make the latest version of standard happy

See rubocop/ruby-style-guide#809
and rubocop/rubocop#8376 for context

* More linting fixes

* Bump cache versions

Just incase the date formatting change happens to mess w/ our cache keys

* Fix the keys to match the new period formatting

* Updated overview card chart spacing

Co-authored-by: claudiovallejo <hola@claudiovallejo.mx>
peteryates added a commit to x-govuk/govuk-form-builder that referenced this issue Jan 13, 2021
@headius
Copy link
Author

headius commented Oct 28, 2021

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.

@silva96
Copy link

silva96 commented Nov 3, 2021

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

koppen added a commit to substancelab/felt that referenced this issue Jan 23, 2023
We need to slurp all remaining options in the constructor, so that we
can pass them on to the wrapper tag. However,
ViewComponent::Base#initialize is defined as

    def initialize(*); end

which messes up keyword argument slurping using the double splat
operator. Given the following component constructor

    def initialize(foo:, **rest)
      super
      p rest
    end

I'd expect the behavior to be

    ThatComponent.new(foo: 1)
    # {}

given that the foo keyword argument is explicitly defined. However the
actual behavior is

    ThatComponent.new(foo: 1)
    # {:foo => 1}

If we don't call super we get the expected behavior:

    def initialize(foo:, **rest)
      p rest
    end

    ThatComponent.new(foo: 1)
    # {}

However, not calling super is a bit of an anti-pattern (as discussed in
rubocop/ruby-style-guide#809), so it would be
beneficial to be able to call it still. We cannot  get by with slurping
unknown keyword arguments before calling super, though:

    def initialize(foo:, **rest)
      p rest
      super
      p rest
    end

somehow still messes with the local variable:

    ThatComponent.new(foo: 1)
    # {}
    # {:foo => 1}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants