Mixins (modules in Ruby) are a way of re-using code across classes. I think that rather than just being a light-weight way to share code, they add complexity to software projects by undercutting some of the benefits that would ordinarily be provided by the tools in object-oriented programming. In this post I’ll discuss some of the disadvantages of modules, and suggest that Ruby programmers should see them as a method of last resort for code sharing only after carefully considering alternative approaches such as creating classes.
About Mixins
Before going into the disadvantages of Mixins as a method of code organization, it’s important to understand their intended use in Ruby and other languages. Mixins are intended to provide a mechanism for multiple inheritance without some of the complexity that this brings into a programming language. The notion of mixins isn’t new in Ruby, and the idea of using groups of related methods that are injected into a class was widely discussed before Matz starting making commits to the Ruby interpreter around 1993 [2].
Mixins, in Ruby and other languages, allow for code reuse across multiple classes without the complex semantics of multiple inheritance found in languages like C++. It’s important to note that mixins are a type of inheritance, which can be easily seen in irb:
module Programmer ; end class Person include Programmer end Person.new.is_a?(Programmer) # true
Disadvantages
With the flexibility of an ostensibly lightweight code-sharing scheme in Ruby come strong disadvantages. Inheritance is fundamentally at odds with encapsulation, the addition of capabilities to objects with a single line quickly muddle the responsibilities of classes, and mixins themselves aren’t even the best option for code reuse across classes. I’ll cover each of these separately.
Encapsulation
Object oriented programming is used in order to help us control complexity in large systems. Instead of having confusing globs of code in unorganized piles in our application, we create objects that communicate with one another using simple, well-defined interfaces. Although each one of those objects may do something complex, as a whole we understand the system readily by treating each object and its functionality as a black box. In theory we should only have to understand these objects and use their functionality by sending simple messages to their public interfaces. In short, encapsulation is our friend in complex systems.
We say that inheritance is fundamentally at odds with encapsulation because once you inherit from a class, there are certain things that you have to know about the parent class in order to successfully inherit from it. As a good programmer, you always call ‘super’ when you’re overriding one of the parent class’ methods, and you’re often required to read those methods in order to understand their functionality that you’re extending. The parent class may maintain state in instance variables, and you have to see what those are doing, too, in order to know how to properly extend the parent class.
In a sense, extending functionality through inheritance is opening a Pandora’s box of sorts by breaking the principles of encapsulation. Generally, you’d be better off by extending functionality through delegation to another class via a public API rather than going down this road.
Remember that mixins are a form of inheritance, and they’re breaking encapsulation in the same way as single inheritance. In fact, you could even be introducing more complexity in your application with mixins, since you’re technically implementing code that acts like a slightly simpler form of multiple inheritance.
As a simple example, take a look at the following, clear example of a violation of encapsulation:
module ProgrammingSkills
def practice_programming!
@skills = [“Java”]
end
end
class Person
include ProgrammingSkills
end
You wouldn’t want to include something that replaced all of your hard-earned, polyglot programming skills with only a knowledge of Java, would you?
Finally, it’s often said that composition is better than inheritance. Even though the keyword for bringing a module into your class, include seems synonymous with ‘composition,’ rest assured, mixins aren’t what people in the software engineering are suggesting.
Single Responsibility Principle
One of the most common refactorings that I apply to programs that I work on is Extract Method [7]. As Martin Fowler described this technique [8], you basically figure out where a class is doing too much work, and create a new class with a set of cohesive methods. You then delegate to it from your old class, which now has a simplified set of responsibilities.
In order to successfully apply this principle, one rule of thumb is that cited by Bob Martin in the “Single Responsibility Principle.” This says that a class should change for one, and only one, reason. If you think about the responsibilities of your class, and can determine another reason that is should change, you should probably extract this behavior out to another class.
Another way of figuring out if you should extract a class is if you find yourself using conjunctions when describing its functionality (e.g., it makes coffee and it washes dishes) [6]. In this scenario, you also should consider extracting a class. Now consider the case where you include a module – what are you doing but adding the necessity of using a conjunction when you import the methods contained in that module into your class?
class Dog include WalkingAbility # and… include FoodConsumer # and… include CarChaser # and end
Traits
I won’t go into a long description of Traits, as they’re clearly articulated in a bunch of papers and also implemented in some form in languages like Scala and Smalltalk. They’re also planned for Ruby 2.0, at least according to the Ruby creator, Matz, in his 2010 RubyConf keynote. Basically, they’re intended to be a more sophisticated method of code inclusion in modules, by providing default collision detection of methods and the ability to more precisely define the order in which things are included. It may be somewhat of a scary thought that once you include a module, it will, without warning, clobber existing methods of the same name.
Without going into a long discussion about traits, and how modules could be improved in Ruby, it’s worth noting in passing that modules aren’t an elegant method of re-using code across your codebase, but actually a pretty crude implementation which can, and probably should be improved within the Ruby language.
Cleaning it up
It’s not my intention to say bad things about mixins without providing an alternative. First, there are some cases where mixins may be useful. Specifically, if you really have a case where something like multiple inheritance is the only thing to cure your ills, then modules would be the logical choice. Needless to say, there are few times in my career when I’ve said, “I know, I need multiple inheritance to solve this problem!” Likewise, I seldom find that I need mixins.
Instead of mixins, I’d encourage you to think about the lightweight system that we already have in place for code re-use and managing complexity in a system. Instead of a module, take the next step and see how you can conceive of your problem in terms of a class which has a well-defined interface and proper encapsulation of a single responsibility. Chances are, you’ll see the benefits of your decision as you debug and extend your system.
More Reading
The resources below are great references on the topics I mentioned above – they cover topics like mixins and how they’re a form of inheritance, as well as things like Traits in programming languages.
- S. Klabnik, “Mixins – a refactoring anti-pattern” http://blog.steveklabnik.com/posts/2012-05-07-mixins–a-refactoring-anti-pattern
- G. Bracha and W. Cook, “Mixin-based inheritance” pp. 303-311
- N. Schärli, S. Ducasse, O. Nierstrasz, “Traits: Composable Units of Behavior” http://scg.unibe.ch/archive/papers/Scha02bTraits.pdf
- A. Snyder, “Encapsulation and Inheritance in Object-Oriented Programming Languages”, pp. 38-45
- Y. Matz, Rubyconf 2010 Keynote, http://www.slideshare.net/yukihiro_matz/rubyconf-2010-keynote-by-matz
- S. Freeman, “Growing Object-Oriented Software, Guided by Tests” location 1258
- M. Fowler, “Refactoring: Improving the Design of Existing Code” location 3320
- B. Martin, “SRP: The Single Responsibility Principle”, http://www.objectmentor.com/resources/articles/srp.pdf