The most difficult thing about making large changes to a code field and the processes around it is building consensus on your team.
We’d like to talk to you today about the process of making such a change in the context of implementing RuboCop on a fully un-linted codebase.
Thankfully there are some great blog resources available for how you might do this from a technical standpoint, one of which that we have found particularly helpful is: Evil Martians’ RuboCopping with Legacy Code. On top of that, the tools themselves have great functionality built in to allow for progressive changes, like RuboCop’s todo file and the ability to inherit configurations.
Given that, we’d like to focus on the building consensus part of the process based on experiences and lessons we’ve learned from implementing code linting at various clients.
And you may ask yourself, “Well, how did I get here?”
Wait, we don’t lint?
The paths to finding yourself in an un-linted code field are many.
What’s most important is not to dwell on your particular path but to set your sights on the end goal. It does, of course, help to understand the decisions and personalities that led to where you are today, but some of that will be unknowable, and some of that may also no longer be relevant. It is important to extend grace and empathy both to yourself, your current team, and the team that was a part of building what you now have.
Almost never do engineers make decisions that they hope will cause others pain and grief, and it always helps to reset your judgment about where you are now.
What are some of the reasons you can look to that might be useful in understanding your current and past team? Perhaps it was inexperience with the practice of linting and the benefits that standardized code bases have in team-based production systems. Perhaps it was inexperience with the language. Perhaps it was a dogmatic assertion by a member of the team. Perhaps it was a business directive that prevented such a change from being implemented.
If you know or can dig into that, it can help you frame some of the next steps. But it isn’t fully necessary. Don’t let the absence or unattainability of a full context prevent you from pushing forward. There are two times to start linting your code field. When you write your first line of code and now.
Dealing with aggressive pushback
You may have another engineer on your team who belittles you for wanting RuboCop as a babysitter for your code. They may accuse you of not being good enough to not need RuboCop. They may assert that it impairs the creative process. Who knows all the ways they may object?
In instances like this, it is first and foremost important to revisit your previous embrace of empathy and grace. They must have some reasons for their pushback. Perhaps you can tease out some root cause? Perhaps you can acknowledge it?
As consultants, it is somewhat easier to take that abusive pushback without fear of how it impacts our position within the team. Regardless, it is perfectly acceptable to close your PR. Withdraw one of your small incremental changes. Let them have a win. Give them some time and space. Not all is lost. The whole effort of making those small changes is so that you can iterate towards an improved end state without all your eggs being in one basket.
The two-way door
Deciding to bring RuboCop into a code base can be a one-way door decision or a two-way door decision. One-way doors are scary, involve committing to changes and disruption, and have high associated risks. Two-way door decisions allow you to make the decision and easily walk it back.
The one-way RuboCop door is installing it, setting it up, running it across the whole code field, autocorrecting 13,000 errors, making one big commit, and merging it. This is a no, no! In addition to introducing functional risk into the code base, especially depending on the state of tests, you are creating a whiplash for yourself, your team, and their managers. The most likely outcome of this will be to build resentment for you and for your tool. It forces all the decision-making to be made upfront, all at once and almost guarantees conflict and stress. Remember that thing about empathy for your team?
The alternative approach is to open a PR that brings in RuboCop, configured as simply as possible, with a giant todo file. You run rubocop -a
and get those green lights and all that without a single line of app code changing. This process allows you to not just introduce RuboCop but also remove it with almost no impact. This gives people, the team, and managers an opportunity to adjust, but it also gives them a chance to eject with almost no impact. The reversible decision makes it palatable, low-risk, and embraceable.
Building consensus through tangible incremental artifacts
But how do you even get to the point of merging a PR in? In contexts where there is strong opposition to particular changes, we like to work by introducing small tangible artifacts.
For example, open that PR as a draft! Opening it as a draft is less psychologically threatening and indicates: “Hey, this is something I’m thinking about.” If your team is operating from a place of fear or lack of trust, it may simply be that no one comments on it at all. That’s okay. But an engineer leaves a comment: “Oh, I’m glad you’re looking at this.” Or maybe they indicate one of the root causes you suspected earlier: “Oh, I’ve wanted to do this forever but wasn’t sure what the correct way to move forward was on this.”
This helps to bring the discussion out into the open, helping to identify people who may support or object to the change, and giving you something to work with as you navigate the changes.
Other tangible artifacts that might be nice to introduce include documentation attempts at style, links to blog posts about the benefits of linting, links to updates around RuboCop performance, or youtube talks about custom cops used to solve tricky code problems in large code bases. All of these help to create an opportunity for discussion and gradual acceptance of the idea as being worth either accepting or declining.
Small changes like this build momentum, creates a paper trail of decision-making and conversation, and help to permeate the culture of your team with an appetite and willingness for the change.
Leveraging personal relationships
One of the great outcomes of these incremental introductions is that you’ve helped yourself identify people who want change and who are open or who are hungry for some of the safety brought about by RuboCop.
Reaching out to them to get them engaged and excited on a new level helps move them from the casual supporter to an active proponent of the change. This could include soliciting comments from them via Slack DMs. An approach like this helps them become part of the change—and demonstrates to others that this isn’t just your wild idea but something that others are coalescing around.
Another way to involve your team is pairing with them on configuration. Remember, pairing doesn’t just have to be a tool for working through hairy coding problems. It is a great way to share knowledge, ideas, and incorporate feedback in real time. Let them run that rubocop -a
and see all the green dots for themselves, to help give them a sense of control. You might get valuable feedback from them in the form of “I don’t like how my hash formatting looks, though.” And you can show them ways they can work with the cop to find a compromise.
Room for doubt
Perhaps at this point, things have taken longer than you wanted. Perhaps you have had to endure the slings and arrows of derision and mockery and have lost all appetite for trying to improve this code base through linting. Perhaps, even you have wondered if this is really the place for you.
Give yourself room for that doubt and allow yourself to wonder if it really is important or not. It is too much to ask of anyone to expect them to be supremely confident in their ideas in the face of overwhelming opposition. Changing a culture is not an easy thing, and it does not happen overnight.
It’s also worth remembering that not every change you advocate for is going to be a game-changer or a home run. The core of the effort is pushing for positive change, and if you have doubts about it, that’s okay!
Consensus vs. unanimity
Once all the cards are on the table, the result won’t always be a unanimous decision. You’re not going to get everyone to agree. A general consensus is what you are after. The goal is to demonstrate a culture of positive change and a healthy way of introducing something new.
While it’s important to acknowledge and address negative feedback, opposition, and criticisms, it’s also important to make sure that is not your full focus. Whether it is the outlier developer or opposition from management, the crucial thing is to recognize the overall impact of your proposal. It can be really difficult to sift through the negative feedback and determine what is an actual blocker versus what is just reluctance. Being empathetic to everyone is a great trait to bring, but it can also block you from weighing the pros and cons accurately.
If you are concerned about a negative impact on particular individuals, this is where the two-way door can come back to really help you and help you navigate what the true long-term impact of your contribution is going to be and help you address your changes with that individual.
[Note: And if your team ends up deciding to instead get behind a defined set of RuboCop rules that most Rubyists can generally agree with … well, you should check out Standard Ruby from Test Double.]
Make your own luck
Of course, the thing is, sometimes, it does happen overnight. A new hire starts one Monday and asks incredulously, “Why aren’t we running RuboCop”? The member of your team who was aggressive in their resistance leaves the team or maybe the company. The engineering manager returns from a conference where they attended a presentation, in part because of the conversations that you had generated.
Leverage those sudden changes! Your PR that you closed or that was sitting in Draft can get whipped out at the drop of a hat. The relationships you have already built and supporters you have already identified can be rallied. You have worked to build that consensus, so once it is in sight, you can take advantage of the opportunity. Huzzah!
Unite on shared values
Change is hard. It’s also not easy. Small steps, low-risk decisions, involvement, solicitation, pairing, private and public discussions all have a part in helping to make that change happen in a way that not only doesn’t threaten your team but helps your team to unite around shared values.
While we’ve used the example of introducing RuboCop to a code base exclusively, we believe that a lot of these aspects of change are useful regardless of the change being made. You are just as likely to encounter these challenges when bringing in Sorbet, testing frameworks, Packwerk, or code ownership files as you are RuboCop - and we believe that the lessons are just as valuable. They represent critical components of our consulting practice that allow us to have influence without authority and help improve our client’s code fields as well as help the world write better software.