Day 7 - Slim down a large class

Sometimes, despite our best intentions, a few classes in our system get large and unwieldy.

Today’s exercise is to take a small step toward slimming down one of those classes.

First, use something like this to find your longest classes:

find ~/code/my_project -name "*.rb" | xargs wc -l | sort -rn | head

Then, pick one that looks like a good candidate and open it up.

Next, scan the file to look for opportunities to extract a new object.

When I do this, I’m looking for groups of methods that “clump together” in a related way.

Here are a few attributes that might identify “clumps” that may make sense to extract together:

  1. Several methods that take the same parameter.
  2. Several methods that access the same instance data.
  3. Several methods that include the same word in their name.

When you see several methods that possess some of the above attributes, try extracting them into a new class and see if it feels like a worthwhile improvement.

An important caveat: this refactoring might be tough to pull off in 20 minutes.

Since you’re working on a large class, you may find it has a lot of coupling that resists extraction.

Alternatively, you might not be able to find a good candidate for extraction.

In either case, here’s a fallback task: improve SOMETHING about the class, even if it’s tiny. Here are a few ideas:

  • Delete a stray comment.
  • Improve a name.
  • Make something private if it’s only called internally.
  • Improve the formatting/style of any ugly bits (got any trailing whitespace or inconsistent newlines?).
  • Slim down a long method.
  • Delete some unused code.

Finally, please don’t feel bad about any of the following:

  • How long your classes are.
  • The fact that you could only extract something small.
  • The fact that you couldn’t find something good to extract.
  • The fact that you couldn’t find a small thing to improve.

As always, this challenge is about showing up each day and taking a small step forward toward better code quality. Some days, you won’t improve the code itself, but your mind. Attempting each exercise will prime you to perform better on your next project or task.

Enjoy!

2 Likes

Ended up increasing the amount of line numbers by about 10%, but improved readability quite a bit. :slight_smile:

4 Likes

My longest .rb files are specs :smile:
I prefer to use the flog gem to identify long/complex classes/methods:

$ flog -g .

The complex ones are those on my ToDo I left for refactoring so I’m covered :wink:

Have a nice day.

cya

1 Like

In comparing a few different repos I have, I noticed a common theme that my test classes tend to be bigger (2x-4x) than the actual source files. I can definitely understand the benefit of extracting some of the feature/steps files out to be more descriptive. But for a model’s spec, what would be a recommended method of extraction? Should different context blocks being moved out to more descriptive file names?

Isn’t it enough that Netbeans always warns me about long functions and classes? Now you too @ben !? :wink:

I actually found a perfect one: 300 lines or 25% lines of code of my main js. There is even already a ticket from 8 month ago to fix this. A quick try horribly failed to decouple this mess. However I’m more than motivated to prevent such chaos in the future. This one won’t go away anytime soon.

Ironically for productive use I merge again all the files to reduce browser requests and save a significant amount of loading time.

1 Like

We definitely have some God classes lurking in our app. I don’t have the time to really dig into them today but I’ve ticketed it to make sure we tackle in the next few weeks.

1 Like

Repo I picked unfortunately didn’t have many large classes in it. I did find one long-ish one (about 600 lines), and was able to simplify a class attribute, remove an unused variable, and eliminate the class constructor (all it did was define some class-level attributes, so I moved those to the class rather than leave them in the __init__() method).

Small improvement, but still an improvement.

As an aside, anyone know of any good tools for finding large classes in Python? The command line given in the exercise is ok, but only if class size correlates with file size (so if you have multiple classes in a single file, that’s not likely to be the case)

1 Like

I’m working in C, so I picked a large file to work on. Split off 500 closely related lines from a 1400 line kitchen-sink file, and had almost no coupling to deal with.

3 Likes

Done, not too big but at least the code its cleaner. I could change my biggest method in 3 classes and convert it to smallest and clearest methods.

Love the results! Thanks!

1 Like

We have a large Org class (much like other’s massive User classes). It deals with billing, database sharding, permissions, and a handful of other stuff.

I cleaned up a bit, but there’s no way to really make a dent in it today.

The other file that clearly needs some help is our ApplicationController, a few hundred lines of dumping ground for stuff.

1 Like

Went straight to the god object in one of our apps. There are plenty of opportunities in this class, but I ended up going for a more modest change. I was able to remove a method by properly setting up a has_many :through relationship.

I appreciate the reminder at the end of the problem essentially saying we win for trying. I’ve been trying to stick to 20 minutes when working on these and keep feeling like I should be doing more.

2 Likes

Worked on a data import class that was very procedural. The class had 2 public methods and 1 private method. The public methods had 94 and 46 lines respectively.

I ended up with two classes. The first one with a 18-line public method, with 8 private methods (all under 30 lines). The second one has a 4-line public method with 5 private methods. I’m still not done, though and I’m thinking about extracting other objects & improving the method interfaces. Thankfully the original class had very good test coverage, so I could go straight to tweaking things :raised_hands:

1 Like

This exercise highlighted the reason for starting a project that I started last year: one of the longest files we have is our ability.rb from the CanCanCan gem. We started the transition to using Pundit which uses much smaller ‘policy’ classes. So although I’m not refactoring a single class, I am working on making it shorter! :nerd_face:

2 Likes

I need help with this task. Could someone help me, please?

I have one large class. It used to be 300+ lines, I’ve extracted some methods to modules, but it doesn’t solve the problem.

Now it is 232 lines. And I’m struggling to find abstraction to extract, will be happy to get some help. If you have any idea on how to make this code simpler, please share it.

Here is the code:
https://github.com/skyderby/skyderby/blob/dev/app/models/tracks/base_presenter.rb

Below is the page it is used for. Every number, every chart line has it’s own method.

Hi @skyksandr, I see that your class handle different things like altitude, speed, weather etc. Maybe you can extract any of them to a separate class if it makes sense to your code?

2 Likes

@skyksandr there’s also a lot of code that seems to be dealing with points - maybe points should be a new concept by itself and have a dedicated class?
Then there’s also some methods that seem like they could belong to the Track class (if it exists) like track_elevation, track_trajectory_distance and track_points.
And weather_data - it seems like the implementation details in that method should probably be encapsulated in the object that knows about the weather data (which seems to be the Track right now).
You may end up moving some stuff to the Track class initially and end up finding groups of methods that seem to stick together within the Track class. Those may spawn other domain concepts like “trajectory” or “weather data”.

Maybe a different class for each widget could also help think about new concepts.

One thing that helps finding new objects is trying to focus on the messages that the objects send each other, instead of on the objects themselves. So, for example, does it make sense for the BasePresenter to receive the min_gps_time message? Should this really be its responsibility? Who could receive this message instead?
Another example: what about the zerowind_points? Does this class really need to know the details of how to calculate the zero wind points? Could this class send a message asking for this to someone else? Who could receive such a message?

Etc etc. POODR is a good resource if you’ve never read it. Chapter 4 should help.

2 Likes

Playing catch-up after being away for a couple of days…
I’m working on a C project so no classes in this one. However, our main.c file is more than 16,000 lines of code… plenty of unrelated functions here that need to be moved to their own modules. Started moving all the GPS related functions that bugged me for a while :slight_smile: On the way I’m finding a lot of spaghetti code I need to untie in order to create the new modules… I love diving into this mess and coming up on the other side with cleaner code! :smile:

2 Likes

I’ve just finished to trim down our User class (our longest model) by extracting some 2 concerns to their own classes.
:tada: