I’m not sure what it is about the feature envy code smell that I like. It’s probably the name and the images it invokes in my mind. One class jealous of another.
Hyperbole aside, feature envy is a useful code smell to understand. It can often be redressed by simply REMOVING code. All things being equal, less code is better. That simple fact is one of the reasons that AngularJS (way back in 2010 ish) got so popular. Compared to the alternatives, the same functionality in Angular back then required 80 to 90 percent less code. It was very compelling.
Feature envy is a code smell where one class “envies” another class. The class under question wants to be the other class so bad, it uses its methods excessively.
Code often needs collaborators. We create multiple classes so that any given class doesn’t get too large and complex. We extract functionality into new classes that encapsulate that functionality. This lets us work towards the goal of building complex systems from simple pieces. But one of the mistakes that may get made is creating a class that excessively uses the methods of another class.
Let’s start by looking at a simple example of this:
In the above class, we have a straightforward auth class that deals with tracking the loggedIn state on the client, and notifying the server to changes to that state.
Now let’s look at another class that depends on our Auth class:
This Login class has some unique functionality, but much of its functionality is just repeating the interface of the Auth class. Simply re-exposing that functionality. This is feature envy. Our Login class really wants to have all the functionality of the Auth class.
This creates high levels of coupling between two classes, which makes our systems brittle. It also implies that we may have unnecessary ceremonious code. Like re-wrapping methods with no additional functionality.
So what do we do?
Well, that is a complicated question. Remember code smells do NOT indicate that something is definitely wrong. They instead give us a reason to reconsider what we’ve done, and see if we CAN do something better.
In this case, maybe whoever consumes the Login class can also consume the Auth class. Then they can use the methods of the Auth class directly and we can remove those re-exposed methods from the Login class. Or perhaps the functionality of the Login class should be moved into the Auth class and the Login class deleted.
But maybe there’s a reason for this. Maybe we really need this facade over the Auth class. There’s many reasons to keep the current structure. But recognizing this code smell lets us look closer. As we learn to see this code smell on simple examples, we can begin to recognize them in more complex ones.
Let’s look at a more complex example. First, imagine an OrderFilter class that maintains four properties of a filter of orders: the filter string, the sort order, the # of days of data to include, and finally any tags that are part of the filter. There may be a lot of functionality needed to make sure that we don’t set an improper sort order, or set the # of days to a negative value, or managing what tags are selected. So we encapsulate that functionality into a class that controls how that data is set.
Now let’s look at a potential consumer of that class, the OrderSelection class:
Look closely at the filterStatus method. See how much it uses the accessor methods of our filter class? This class envies the Filter class. It wants to have the same scope as the Filter class. It wants to see the internal data of the Filter class.
So how do we solve this? We may decide that this string format is pretty common, so we can move the filterStatus method into the filter class. Maybe even overriding the toString method. We could implement a new method in the filter class that returns a new object that contains all the data. This lets consumers have easy access to the current filter elements without being able to modify the data inappropriately. There are other alternatives that you could come up with yourself. And finally, maybe we don’t do anything. We may consider alternatives and just accept the current situation as the best we can do given current constraints.
One thing is for sure, we can’t improve less effective code if we don’t learn to find it in our applications. So learning to see code smells like this is always a valuable skill, regardless of how, or even if, you address those smells.
Finally, for some great learning, check out our Gatsby course. Sam Julien really does a great job teaching Gatsby using hands-on coding. This course will teach you Gatsby faster than anything else out there.
Enjoy this discussion? Sign up for our newsletter here.