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
Disable recently problematic rules #125
Conversation
This makes sense to me - I agree with both of the overrides and the attached explanations. Following the discussion in #124, I'm still concerned about a couple of Cops that we're leaving enabled:
|
Thanks @benthorner for taking a look and for your help with this 👍 It's good that we're down to only a couple of divisive points in the world of linting. For both the rules you referenced I don't have particularly strong opinions for or against them myself (like many rules can see the occasional benefits and then times of annoyance). I therefore find myself leaning slightly more towards allowing them and seeing if they cause problems as the divergence of disabling rules is something we carry for a long time.
From @theseanything's stats it seems like this isn't too widespread a problem - mostly one we've introduced relatively recently into Content Publisher and Email Alert API. I can certainly see why it'd be annoying for those. However since it's so low widespread there is a part of me that wonders: are we actually coding in a strange way to hit this problem? I guess what we're doing that is odd is using the subclass as the place to define an interface that isn't on the superclass - this seems a violation of Liskov substitution principle but then this isn't the most Rubyist of principles - similar things exist in Sidekiq workers for instance with the perform interface. Perhaps though, we've used direct inheritance when a mixin is more appropriate for the type of behaviour that is shared?
Sure. I can't quite see why that'd cause a Rails error (could you explain?) however I can see that this causes an annoyance when considered with You also mentioned in your comment:
Yup - this is a tricky problem. I see two aspects to it - the initial dropping of this change to see if any of the rules are really miserable and then the long tail of getting it everywhere. I'm wondering whether it might be worth doing a pre-release of this gem with the new rules and applying them to a bunch of our key repos first (with a little call for volunteers to help) to iron out the first step. Second step would probably involve needing a few volunteers again, I somewhat imagine that since I'm involved in the release I'll have a drive to make it happen everywhere. |
Hmm...I'm in still in two minds with
This gave me some pause for thought. I think patterns like this (for anyone else reading) are OK, though:
I believe the pattern we have is the most minimal to achieve the desired, class-level interface, which I take as a sign that it's the "best" or "right" one for the job. So I'd only expect its usage to grow. Unless we can think of an advantage over testing, then it seems in our interest to disable the rule. Ruby isn't meant to be a boilerplate-y language, after all.
Ahh, I misremembered this, sorry. It was actually that I think it's important we promote a good experience with this repo. Even one instance of this conflict would be quite a poor experience for the developer trying to figure out how to resolve it. On that basis, I think we should scope That was long. Here's a quick summary of my thoughts so far:
I also like your idea of a pre-release, which nicely captures the potential compatibility issues that may crop up when applying all the changes. Perhaps @theseanything you'd like to comment here. What's your take on these Cops? |
Ah sure, it's the L in SOLID. It's a principle applied to Ruby frequently so I may have mis-represented it as saying it's not the most Rubyists of rules, something to aspire too but not dogmatic.
Sure, I'd have thought it could be similarly minimal as a mixin? I guess the two parts that make me question what we've done here is 1) it seems to be a violation only affecting new(ish) code rather than a wider GOV.UK precedent? and 2) the question asked about this rule is (paraphrased) "what is a good reason to not call super?" and the only answer I've got for this example is that it's not really a class we're inheriting from it's more a behavioural pattern, as we'd not expected the class to actually do anything. Happy to be led by others input into this - I know this has become a very specific discussion 😄
Ok great about the errors. I kind of feel if we wanted to have an exception it'd be better to disable I'm worried though that this talk of disabling is almost entirely driven by Whitehall where the code affecting this is already pretty whacky - where it'd seem reasonable to do an inline ignore on the offending controllers. Do we know where the other cases of this are?
Great, thank you - I'll try plug some other people to take a look at this tomorrow too. |
Yes, after writing this I realised I was biased towards Whitehall. Looking at it that way, I agree the inheritance structure in that repo is unusual and not something we'd want to replicate elsewhere. So it makes sense to do something specifically in Whitehall, as I'm not aware we have filtered, inherited controller actions anywhere else. Both of the rules are quite niche, but the lexical one has the benefit of being more directly related to the code, so easier to explain disabling it. So I'd agree with you there, well argued!
(Facetious, but the wider GOV.UK precedent here appears to be "a mess", so I'm not particularly swayed by this 😸.)
I think it would be slightly more verbose. The actual problem seems to be with I think this really comes down to a decision: do we want to put |
+1 on the idea about pre-releases. It'd be a good way to gauge the real impact of changes. As for
I think this type of rule would help us think about our code design, i.e. we are calling super, but we don't actually need it - is inheritance actually appropriate here? |
@theseanything this is what discussion with @kevindew. What's difficult here is that this change affects quite a lot of files, which will need to be manually fixed. Apart from blindly adding
The use of
And this is what I'm against: My conclusion so far is that applying this Cop will be a net deterioration: genuine omissions of Can you see the tradeoff here? |
@benthorner If I understand right you're theorising that with a mixin you'd still need to avoid initialize, however that wouldn't be the case (I actually cobbled together a rough implementation when chatting about this the other day here: alphagov/content-publisher@7b117d7). Whereas the With a mixin - you tend to be adding an interface to a class rather defining the class' lifecycle. For example, using I forgot the other day to shout to get other eyes on this, I'm thinking I could do that tomorrow but it might not be the most inviting of a conversations to get involved in 😆 . It may be worth trying to summarise the concerns to see if they can be tested? |
Sure, thanks for digging into it a bit more @kevindew. Here's a summary for anyone reading:
Latest thoughts from me on
That last one tips the balance for me, especially since it's clearly distinct from the current base classes So, I'm for approving this PR, if we're comfortable with the new approach to avoid |
Great thanks for that summary @benthorner. So a quick proposal on next step actions:
Does this sound ok? and are we happy for this to be the long foretold 4.0.0 release? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes 👍. Thanks for all your effort here @kevindew 🏅.
This is a new rule that was introduced in rubocop-rspec 1.43.0 [1] which sets a limit on the amount of `let` definitions you can have within an RSpec block (defaulting to 5). This has been disabled as per our preference to not have cops that are based on arbitrary limits. [1]: https://github.com/rubocop-hq/rubocop-rspec/blob/master/CHANGELOG.md#1430-2020-08-17
This rule enforces whether numbers are prefixed with underscores in variable names, for example normalcase is var1 and snake_case is var_1. Applying this rule to GOV.UK projects has been challenging and has presented concerns about reduced readability [1] In Rubocop 1.2.0 this cop was extended to enforce method names and symbols. This looked to present further readability concerns for GOV.UK and a large amount of code that would need fixing, as GOV.UK has tended to favour snake_case for these by precedence. It would make sense to adopt snake_case, if we were forced to choose a convention, over normalcase given the existing precedence in GOV.UK. However as we have previously used the normalcase rule switching to this would create further churn to be fixed. The simplest approach therefore seems to be disabling this rule and accepting both normalcase and snake_case as valid approaches in our naming. [1]: alphagov/smart-answers@54c1295
6fd0d6d
to
7437126
Compare
This creates a new mixin, Callable, which can be used to provide a class with the boilerplate so that it can have a public class method of `.call` which calls `#initialize` with the arguments and then `#call`. This mixin replaces the inheritance approach used in services with an ApplicationService class. These classes now all utilise this mixin instead. The reason for doing this was prompted by a linting violation with one of rubocop's newer rules: `Lint/MissingSuper`, which is an offence when a class inherits but does not call `super` in it's initialize method. This violation seemed to only affect some of GOV.UK's newer code, such as this service pattern, which led to some debate [1] about what the most appropriate means to share this behaviour is, whether our inheritance approach was actually idiomatic and how using super in every `initialize` would be unnecessary boilerplate. The Rubocop thread [2] regarding this rule raised an interesting point "I'm curious of examples where there are good reasons to explicitly not call super.", which led to me considering why we didn't think `super` would be appropriate in these classes. My conclusion to this was that we weren't actually intending to create a subtype with inheritance, we actually just wanted standardisation with the interface, and that the inherited classes have very little behavioural consistency, breaking Liskov substitution principle [3]. When looking at the Ruby standard library a module seems the most appropriate way to implement it, considering similarities with modules such as Singleton and Enumerable. Taking inspiration from the Ruby standard library there is an in [1]: alphagov/rubocop-govuk#125 [2]: rubocop/rubocop#8506 [3]: https://thoughtbot.com/blog/back-to-basics-solid#liskov-substitution-principle
This creates a new mixin, Callable, which can be used to provide a class with the boilerplate so that it can have a public class method of `.call` which calls `#initialize` with the arguments and then `#call`. This mixin replaces the inheritance approach used in services with an ApplicationService class. These classes now all utilise this mixin instead. The change was prompted by a linting violation with one of rubocop's newer rules: `Lint/MissingSuper`, which is an offence when a class inherits but does not call `super` in it's initialize method. This violation seemed to only affect some of GOV.UK's newer code, such as this service pattern, which led to some debate [1] about what the most appropriate means to share this behaviour is, whether our inheritance approach was actually idiomatic and how using super in every `initialize` would be unnecessary boilerplate. The Rubocop thread [2] regarding this rule raised an interesting point "I'm curious of examples where there are good reasons to explicitly not call super.", which led to me considering why we didn't think `super` would be appropriate in these classes. My conclusion to this was that we weren't actually intending to create a subtype with inheritance, we actually just wanted standardisation with the interface, and that the inherited classes have very little behavioural consistency, breaking Liskov substitution principle [3]. When looking at the Ruby standard library a module seems the most appropriate way to implement it, considering similarities with Singleton and Enumerable. [1]: alphagov/rubocop-govuk#125 [2]: rubocop/rubocop#8506 [3]: https://thoughtbot.com/blog/back-to-basics-solid#liskov-substitution-principle
This creates a new mixin, Callable, which can be used to provide a class with the boilerplate so that it can have a public class method of `.call` which calls `#initialize` with the arguments and then `#call`. The creation of this class is a port of the same approach applied in Content Publisher [1] (and most of this commit message is a port too in case you're feeling a touch of déjà vu). This mixin replaces the inheritance approach used in builders, presenters and services. The change was prompted by a linting violation with one of rubocop's newer rules: `Lint/MissingSuper`, which is an offence when a class inherits but does not call `super` in it's initialize method. This violation seemed to only affect some of GOV.UK's newer code, such as this service pattern, which led to some debate [2] about what the most appropriate means to share this behaviour is, whether our inheritance approach was actually idiomatic and how using super in every `initialize` would be unnecessary boilerplate. The Rubocop thread [3] regarding this rule raised an interesting point "I'm curious of examples where there are good reasons to explicitly not call super.", which led to me considering why we didn't think `super` would be appropriate in these classes. My conclusion to this was that we weren't actually intending to create a subtype with inheritance, we actually just wanted standardisation with the interface, and that the inherited classes have very little behavioural consistency, breaking Liskov substitution principle [4]. When looking at the Ruby standard library a module seems the most appropriate way to implement it, considering similarities with Singleton and Enumerable. [1]: alphagov/content-publisher@5933f87 [2]: alphagov/rubocop-govuk#125 [3]: rubocop/rubocop#8506 [4]: https://thoughtbot.com/blog/back-to-basics-solid#liskov-substitution-principle
This creates a new mixin, Callable, which can be used to provide a class with the boilerplate so that it can have a public class method of `.call` which calls `#initialize` with the arguments and then `#call`. The creation of this class is a port of the same approach applied in Content Publisher [1] (and most of this commit message is a port too in case you're feeling a touch of déjà vu). This mixin replaces the inheritance approach used in builders, presenters and services. The change was prompted by a linting violation with one of rubocop's newer rules: `Lint/MissingSuper`, which is an offence when a class inherits but does not call `super` in it's initialize method. This violation seemed to only affect some of GOV.UK's newer code, such as this service pattern, which led to some debate [2] about what the most appropriate means to share this behaviour is, whether our inheritance approach was actually idiomatic and how using super in every `initialize` would be unnecessary boilerplate. The Rubocop thread [3] regarding this rule raised an interesting point "I'm curious of examples where there are good reasons to explicitly not call super.", which led to me considering why we didn't think `super` would be appropriate in these classes. My conclusion to this was that we weren't actually intending to create a subtype with inheritance, we actually just wanted standardisation with the interface, and that the inherited classes have very little behavioural consistency, breaking Liskov substitution principle [4]. When looking at the Ruby standard library a module seems the most appropriate way to implement it, considering similarities with Singleton and Enumerable. [1]: alphagov/content-publisher@5933f87 [2]: alphagov/rubocop-govuk#125 [3]: rubocop/rubocop#8506 [4]: https://thoughtbot.com/blog/back-to-basics-solid#liskov-substitution-principle
This creates a new mixin, Callable, which can be used to provide a class with the boilerplate so that it can have a public class method of `.call` which calls `#initialize` with the arguments and then `#call`. The creation of this class is a port of the same approach applied in Content Publisher [1] (and most of this commit message is a port too in case you're feeling a touch of déjà vu). This mixin replaces the inheritance approach used in builders, presenters and services. The change was prompted by a linting violation with one of rubocop's newer rules: `Lint/MissingSuper`, which is an offence when a class inherits but does not call `super` in it's initialize method. This violation seemed to only affect some of GOV.UK's newer code, such as this service pattern, which led to some debate [2] about what the most appropriate means to share this behaviour is, whether our inheritance approach was actually idiomatic and how using super in every `initialize` would be unnecessary boilerplate. The Rubocop thread [3] regarding this rule raised an interesting point "I'm curious of examples where there are good reasons to explicitly not call super.", which led to me considering why we didn't think `super` would be appropriate in these classes. My conclusion to this was that we weren't actually intending to create a subtype with inheritance, we actually just wanted standardisation with the interface, and that the inherited classes have very little behavioural consistency, breaking Liskov substitution principle [4]. When looking at the Ruby standard library a module seems the most appropriate way to implement it, considering similarities with Singleton and Enumerable. [1]: alphagov/content-publisher@5933f87 [2]: alphagov/rubocop-govuk#125 [3]: rubocop/rubocop#8506 [4]: https://thoughtbot.com/blog/back-to-basics-solid#liskov-substitution-principle
This is to follow #124 it disables two rules that prove problematic as a result of the dependency updates. This build will fail until #124 is merged and this is rebased onto it. Further details in the commits.