SlideShare una empresa de Scribd logo
1 de 20
Descargar para leer sin conexión
Eliminating Inheritance Via Smalltalk-Style Traits
Copyright 2009 by Curtis “Ovid” Poe under the GNU Free Documentation License1

A Brief History of Pain

Object-Oriented Programming (OOP) has been around for a long time. In fact, most of the
core features we associate with OOP -- inheritance, polymorphism, classes and so on --
were introduced in 1967 with the language SIMULA 67 2. In the case of inheritance, one
might believe that a concept hanging around for over four decades would have most of the
kinks ironed out but one might also believe in the tooth fairy.

Now if you ask people “what is essential to OOP”, youʼll get plenty of arguments over the
matter, but “inheritance” is one area that many, many people disagree about violently. In
fact, some languages such as Javascript, REBOL, Lua and Self avoid inheritance by
reusing code via cloning existing objects and adding the behavior they need -- though if
you squint, it often looks like single inheritance. But for languages that do implement
inheritance, things should be pretty clear-cut, right?

In inheritance, we can think of classes as types and subclasses as subtypes. So Dogs
and Cats might be subclasses of Mammal and if you adhere to the Liskov Substitution
Principle3 (LSP), you could drop any instance of a Dog or Cat into a place where we
expect a Mammal and the code should run just fine. Of course, the devil is in the details
and depending upon the languageʼs type system, violating the principle might be a
compile-time error, a runtime error, or no error at all. Various mechanisms such as
programming by contract, used by Eiffel4, or the unusual inheritance mechanism provided
by Beta5 can be used to enforce LSP. Other languages, such as Ruby, use “duck typing”
where you just sort of hope that the object in question has the methods you want (to be
fair, this seems to work remarkably well for Rubyists). Still others argue that LSP isnʼt all
itʼs cracked up to be and debate whether a 3D point should inherit from 2D point or vice
versa and argue that somehow Liskov js flawed.

Then there are discussions about using aspect-oriented programming to enforce strict
equivalence, C++ templates versus Java generics, interfaces versus mixins, separation of
implementation and interface and so on. Whatʼs important here is that these arenʼt
debates about a piece of code you implemented last week. These are serious debates
about the implementation of the languages themselves. While most people are generally
on board about things such as polymorphism and encapsulation, arguments about
appropriate use of inheritance have been going on for over four decades.




1   http://en.wikipedia.org/wiki/Text_of_the_GNU_Free_Documentation_License
2   http://heim.ifi.uio.no/~kristen/FORSKNINGSDOK_MAPPE/F_OO_start.html
3   http://www.objectmentor.com/resources/articles/lsp.pdf
4   http://www.eiffel.com/
5   http://www.daimi.au.dk/~beta/
If you want to dig further into this, youʼll discover that the inheritance mess is so serious
that many top-notch OOP developers recommend that you avoid inheritance as much as
possible and use delegation, mixins, or other tricks to implement code reuse.

This, my friends, is what we call a “code smell”. A code smell doesnʼt mean that thereʼs a
problem with code, but it does mean that it bears further investigation. When you have a
programming practice thatʼs been around for over four decades and people are still
arguing about the fundamentals of it, further investigation is definitely warranted. So letʼs
investigate.

A Deeper Look At Pain

First off, Iʼm going to go out on a limb and suppose that most readers of this are not
programming language designers. Language designers have a much harder problem than
language users. They shouldnʼt just hack something together and hope it works, they
have to research it. They have to think about it very carefully. They have to consider how
their language is likely to be actually used and they have to implement it to support that
use. Some languages, like Eiffel, are very carefully designed and others grow
organically 6, but most OOP languages that you know probably have had a bit of thought
applied to how they implement OOP. This raises a couple of interesting point.

First, if the language is even remotely popular, it probably means that the author(s) of the
language probably has a lot more on the ball than most of us. Second, they had a lot
more time to develop their OOP implementation. So better programmers with more time
on their hands will probably have an implementation of code will likely be better than our
implementation of code. Now if youʼre like me, youʼre probably an average programmer
working with a deadline and you need to translate your (possibly ill-conceived) specs into
the language designerʼs carefully thought-out language and thereʼs a good chance that
what you create wonʼt be perfect the first time around. Real world translation: most “OO”
code bases tend to be a mess. Iʼve worked with plenty of them in a variety of languages
and theyʼre often horrific and tough to maintain, but the key thing is that they work. They
work despite violating the Liskov Substitution Principle, despite ignoring strict equivalence,
despite not understanding cohesion. They have all sorts of nasty hacks to work around
the developerʼs lack of knowledge and this makes them a nightmare to maintain and
extend, but they work.7 This is because programmers generally arenʼt focused on theory.
Theyʼre focused on behavior. “I need this method to read a config file”, so they write the
code to do that. Sure, maybe they should be abstracting this into a ConfigManager
class, but when they think about their deadline, timeʼs a-wastinʼ.

Truth be told, I donʼt think this is a problem with developers. Yes, itʼs a problem that
developers need better training and experience, but at the end of the day, while some
developers are reading a paper on the original intention of the database relational models
in databases (sadly, far too few), other developers are out at the pub, sharing a pint with
friends, going on dates with their partners and spending time with their children. This isnʼt
a bad thing. Itʼs OK to have a life outside of programming. Whatʼs a bad thing is that to be
a great programmer, you usually find yourself forgoing a lot of that life. What we need are
programming tools that fit what developers really do rather than try to suddenly expect that


6   The popularity of PHP says a lot in favor of practice over theory.
7   Though I confess to still having nightmares about that C# developer who wrote a “StartHTML” class.
developers are going to “step up their game.” Inheritance is not one of those programming
tools.

To investigate some of this, Iʼm going to focus on Perl. Though an oft-maligned language,
its flexibility and power have a lot to recommend it. And, to be honest, though Iʼve
programmed in a variety of different languages, Perl is the one I now know best and that
means I can more easily focus on concepts and worry less about rabid readers attacking
me for missing a semi-colon.

First, letʼs take a look at a real-world case of inheritance:




Figure 1: The B Inheritance Hierarchy8


Without going into too much detail, thatʼs an inheritance hierarchy which simulates how
Perl variables work internally. For the end user this tends to be transparent, despite the
apparent complexity and that's part of what classes should do for you. Still, very few Perl
programmers ever need to touch this and fewer still know what it means. However, letʼs
take a closer look at one section of it.




8   Full size image: http://www.flickr.com/photos/publius_ovidius/3646752998/sizes/o/
Figure 2: A Closer Look At A Section of the B Inheritance Hierarchy


Hmm, looks like we might have some multiple inheritance here, so letʼs focus on just that:




Figure 3: Examining PVIV Inheritance
With a large amount of hand-waving, “SV” means “variable”. “PV” is a string value and
“IV” is an integer value9. A “PVIV” is a variable with both string and integer
representations. To those working with languages with strict type systems, this seems
absurd. However, in Perl, this means that you can do something like this:

      my $number = 3;
      $number   += 2;
      print "I have $number apples";

Example 1: Printing A String In Perl


And that final line prints “I have 5 apples”.

In Java, those lines might look like this:

      int number = 3;
      number    += 2;
      System.out.println("I have " + number + " apples");

Example 2: Printing A String In Java


Java doesnʼt allow operator overloading, but theyʼve made an exception for the String
class. Perl allows operator overloading but often doesnʼt need it because variables have
“slots” (again, lots of hand-waving) with string, integer and numeric (double) values and
each is used as appropriate given the context. Thatʼs why thereʼs this strange “PVIV”
class. Most of the time this “just works”, but what most people donʼt know is that while the
string, integer and numeric slots usually have complementary values (“5”, 5, 5.0), you can
assign different values to those slots if you really need to (“five”, 5, 5.0)10 and ensure that
the final line in the Perl code prints “I have five apples” even if the integer value is 5.

Looking further, both the B::PV and B::IV classes have an as_string method and if
you have a B::PVIV instantiation of a variable, printing its string value is trivial:

      print $variable->as_string;

Example 3: Printing A String In Perl


That prints the B::PV::as_string value because B::PVIV inherits from B::PV first.
Ignoring for the moment the fact that this contrived example is a usually a stupid thing to
do, what happens if you want the as_string value of the integer 5 and not the
as_string value of the string “five”? Because of how multiple inheritance works and
because of how these classes are designed internally, this becomes rather painful to get.

      print $variable->B::IV::as_string;
Example 4: Printing An Integer Value in Perl


9"SV": Scalar value. "PV": Pointer value ("S" was taken and a string in C is merely a 'p'ointer to an array of
chars. "IV": Integer value.

10This technique is known as a “Big Bucket of Stupid”, but some people prefer to call it a dualvar. See
"dualvar NUM, STRING" in http://search.cpan.org/~gbarr/Scalar-List-Utils-1.21/lib/Scalar/Util.pm Despite
my mockery, it does sometimes prove very useful.
In short, weʼre forced to encode knowledge of our class structure into the method call and
we have inheritance leading to a tremendous violation of encapsulation. Polymorphism,
C3 linearization11 and other OOP techniques quietly fail here. If you have an instance of a
class, you really shouldnʼt have to know about the structure of the class hierarchy to use it,
but there you go.

Real-World Pain At the BBC

At this point youʼre probably thinking that Iʼm smoking crack. This is a contrived example
and is so incredibly obscure -- though itʼs the sort of real-world uses the the B:: classes
were designed to support -- that it doesnʼt seem relevant to what you do. So hereʼs a real-
world (simplified) example of a problem we faced building a metadata system for the
BBC.12




11   http://en.wikipedia.org/wiki/C3_linearization, a.k.a. “Mopping the Titanic”
12   http://www.bbc.co.uk/blogs/bbcinternet/2009/02/what_is_pips.html
Figure 4: A BBC “Program” (which my British colleagues insist upon spelling as “programme”)


This was part of our inheritance hierarchy for a program that you might watch on the BBC.
Now OOP purists might shudder at this hierarchy, but look at this from the point of view of
your average programmer.

My::ResultSource is a single instance of an object we pull from our database object-
relational mapper DBIx::Class.13 Most objects need to be audited (“who changed
what?”), so an Audited object ISA My::ResultSource. All objects which need to be
tagged (for example, tags you see on blog posts) are audited, so Tagged ISA Audited.
Thus, any Program which needs to be tagged ISA Tagged. No, Iʼm not arguing that this
is an appropriate use of OOP. Iʼm arguing that for the average developer, it doesnʼt
necessarily seem that ridiculous.

If someone alters an instance of a Programme (switching to the British spelling to
distinguish from a computer “program”), the program automatically Does The Right Thing
and works its auditing magic. Part of the reason it does this is because we, at present
count, have over 30,000 tests for our system and partly because this inheritance hierachy,
while violating a lot of rules, just works. My colleagues are a bunch of talented developers
who Iʼd be happy to hire onto any company I work with (really, all of them -- Iʼm very lucky
about this), but they've created an inheritance hierarchy several levels deep and even
though itʼs only single inheritance in this example, we have started to run into problems
with the very flexible nature of Perlʼs OOP behavior.

For example, letʼs say that your Program class needs a from() method indicating where
we got the the programme from. Because we have a deep inheritance hierarchy, we have
to search through a number of classes to know if weʼre accidentally overriding something.
As it turns out, DBIx::Class::ResultSource implements a completely different
from() method and overriding that will break out code. Only because we have a good
test suite are we protected from that. So itʼs fair to say that deep single inheritance tree
can increase the cognitive load a programmer has to manage and thatʼs something we like
to avoid.

As a quick aside: Java programmers might very well stop here, point to their IDEs and
laugh, explaining that their IDE can automatically point out when they're overriding
something. With statically typed languages, such IDE support is common. However,
dynamically typed languages generally donʼt have strong static analysis tools available
and thus frequently canʼt take advantage of this.14 Tools for managing this sort of
complexity -- and large-scale software is all about complexity management -- should be
built into the language when possible and not rely on external software products.15

But moving along, I should point out that the BBC systems store more than just
programmes. For example, we also store what we call “reference data”. This is data

13   http://search.cpan.org/dist/DBIx-Class/
14Dynamic languages have other benefits and this limitation is hardly an indictment, so donʼt choose
languages based on a single pet peeve. Plus, the dynamic SmallTalk languageʼs class browser provides a
delightful counter-example.
15Anyone forced to use "vi" (not even "vim") while trying to create an emergency patch of broken code over
a slow telnet connection at 2:30 in the morning is going to get very irritated if your codebase is so complex
that you need a large IDE to comprehend it.
which, unlike programmes, changes very infrequently, if at all. In your online store
database, you might have a table listing US states. Those donʼt change very frequently
and we might have a different base class named My::ResultSource::Static which
might extend some of the behavior of My::ResultSource. Since the BBC has
programmes from all over the world, we might have static data for countries.




Figure 5: Our Inheritance Hierarchy Grows


Since the static (reference) data doesnʼt change much, we donʼt need to audit it and our
inheritance hierarchy, while becoming a bit more complex, is still single inheritance. But
then the unthinkable happens: the Berlin Wall falls, the Soviet Union collapses, new
Eastern European countries are popping up like popcorn. All of a sudden, weʼre changing
our “static” country data quite a bit. Though our Country class still might benefit from a
“static” base class, we now need to audit it because our editors are constantly updating it.
Then our real problem starts.

Country canʼt simply inherit from Audited because it will no longer inherit from
My::ResultSource::Static. However, My::ResultSource::Static canʼt inherit
from Audited because our other static data might not need auditing. The solution seems
to be falling back on multiple inheritance.16




16One reviewer claimed that he had trouble following this example and asked if I could provide a clearer
one. I was initially inclined to agree until I realize that his complaint supported the argument presented in this
paper. Specifically, this example is a simplification of a real-world problem and if the inheritance is
confusing, that's because this is what happens with inheritance.
Figure 6: Solving A Compositional Problem With Multiple Inheritance


The dangers of multiple inheritance are so well-known that I will not belabor them here, but
suffice it to say that since so many OOP languages disallow multiple inheritance,17 itʼs fair
to say that there might be a reason for avoiding it. And though the simple example in
Figure 6 might look manageable, the reality is, systems grow.




Figure 7: The “Moose” Inheritance Hierarchy (still relatively small)18


As mentioned back at Figure 4, those with a strong OOP background might shudder at the
Country/Program hierarchy and, in fact, theyʼd be right. The problem kicking its head up
is “separation of concerns”.19 If youʼre trying to avoid multiple inheritance, you might very
well call your team lead over, show her the problem, and have her give a long lecture
about how a Program ISA Audited is a terrible way of modeling the system and auditing
functions belong in a separate class hierarchy which a Program delegates to and while
youʼre at it, you shouldnʼt be inheriting from Tagged either.




17   C#, Objective-C, Object Pascal, Java, PHP, BETA and Smalltalk, to name a few.
18   Full size image: http://www.flickr.com/photos/publius_ovidius/3549146507/sizes/o/
19   http://en.wikipedia.org/wiki/Separation_of_concerns
Shamefacedly, you admit that sheʼs right and as she walks away, you quietly moan about
all of the extra work you have to do when all you wanted to do was having “Country” write
a single line to a log file.

And thereʼs the disconnect. Programmers just want to Get Stuff Done and quite often we
find that trying to model a large system “properly” (whatever that means) is hard. Most
programmers arenʼt OOP experts and even those that are will generally tell you that the
first iterations of OOP systems tend to have plenty of flaws in them. CRC cards,20 UML
diagrams,21 and similar techniques, while laudable, donʼt seem to be employed very
often.22

Taking Pain Killers

So whatʼs really the problem going on here? A large part of the problem lies in how we
use classes. Specifically, we use them for two different things.23

First, as agents of responsibility, a class needs to perform everything the class is
responsible for. While this sounds obvious, it does mean that we have an upward
pressure on class size. As systems grow and we need new features, we add them to our
classes.

Second, classes are used for code reuse. This is done primarily (but not exclusively) via
inheritance. This causes an interesting issue. In Perl, when you write a library itʼs
generally recommended that you not pollute the namespace of code which uses your
library, but instead allow the consumer to choose which of code they want exported into
their namespace. For example:

          use List::Util qw(reduce);
          my $product = reduce { $a * $b } 1 .. 10

Example 5: Mitigating Namespace Pollution in Perl


Though List::Util offers several list utilities, you only get the ones you really need.
Thatʼs great because then you have control over what youʼre getting and frankly, many
times youʼre using a library and you only want one function out of it, not all. The same
goes for classes, but if you inherit from them, you get all of the methods those classes
provide,24 so what you really want are smaller classes so that you donʼt pull in irrelevant
behavior.

So classes are used in two different ways which tends to give them competing
requirements of needing to be both both smaller and larger at the same time. This is


20   http://en.wikipedia.org/wiki/Class-Responsibility-Collaboration_card
21   http://en.wikipedia.org/wiki/UML_Diagram
22http://www.google.com/search?hl=en&q=uml+sucks and numerous books touching on these topics.
Additionally, your author has worked for a number of companies which list UML as a job requirement, but
never actually use UML.
23   http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf
24   Thatʼs not entirely fair. Some languages limit this, but you still may very well get methods you donʼt need.
actually the root of the problem with classes and the solution here is to decouple these two
uses. Enter “traits”.

Traits, in this sense, refers to SmallTalk-style traits. These were first described in “Traits:
Composable Units of Behaviour”25 in 2003. Though a relatively new concept in computer
science terms, traits present a coherent alternative to code reuse via inheritance. Far from
being a wild experiment, trait research was carried out at two separate universities under
grants from the National Science Foundation and the Swiss National Foundation26 and for
the masochistic, you can read a “A Typed Calculus of Traits”27

The basic idea of traits is simple. You identify a behavior you need to share across
classes and you push it into a trait instead of a parent class. The trait provides the
methods28 and lists other methods it requires.

For example, letʼs say that all of your objects can be serialized as YAML, but you need to
serialize them as XML. The trait might look like the following pseudo-code:

          trait XMLSerialization {
               import XML::Converter;
               requires 'Str as_yaml(void)';

                  Str as_xml(void) {
                       return XML::Converter.from_yaml(this.as_yaml());
                  }
          }

Example 6: Pseudo-code For A Trait


And your class might look like this:

          class Customer does XMLSerialization {
               Str as_yaml(void) {
                    ...
               }
          }

Example 7: Pseudo-code For Consuming a Trait


Later, someone else might write this:

          if ClassOf(customer).does(‘XMLSerialization’) {
               print customer.as_xml();
          }

Example 8: Pseudo-code Of Trait Introspection



25   http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf
26   http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf
27   http://people.cs.uchicago.edu/~jhr/papers/2004/fool-traits.pdf
28   Strictly speaking, these are argued to be pure methods, but in practice, thatʼs not always what happens.
So now we have a way of organizing shared behavior without worrying about inheritance,
but honestly, if this was all there was to traits, it wouldnʼt be that compelling. Fortunately,
this is not all there is to traits. Now weʼre going to switch to examples in Perl 5 because
aside from the fact that this is the language Iʼm now most comfortable working in, itʼs also
probably the language which has the most widespread adoption of traits 29 and thereby has
a solid implementation. In Perl, however, traits are called “roles” (due to the term “trait”
already in use with Perl 6), so weʼll start using that term now. Further, weʼre going to use
Moose30, a complete OO system for Perl with a built-in metaprotocol.31 It makes our OOP
code more legible and has Moose::Role included, allowing you to use roles.32

First, imagine that youʼre writing a text adventure and there's a room with a practical joke
in it which must serve as some sort of clue to the player. So you start writing a
PracticalJoke class and it needs a non-lethal explosion and a fuse. Fortunately, you
already have fuse() and explode() methods handy in Bomb and Spouse classes. So
hereʼs what you write:

           # In Perl 5, packages and classes are the same
           package PracticalJoke;
           use Moose;
           extends qw(Bomb Spouse); # i.e., "inherits from"

Example 9: Multiple Inheritance In Perl


Right off the bat, we have a problem. Both Bomb and Spouse provide fuse() and
explode() methods. Here are their properties:


                  Method                                 Description

 Bomb::fuse()                                 Timed

 Spouse::fuse()                               Non-deterministic

 Bomb::explode()                              Lethal

 Spouse::explode()                            Wish it was lethal

Table 1: Multiple Inheritance For The FAIL


As you can see, this wonʼt work. We need the timing from the Bomb::fuse() method,
but the non-lethal Spouse::explode() behavior. Obviously, multiple inheritance means




29   http://scg.unibe.ch/research/traits (see the “Perl” section)
30   http://search.cpan.org/dist/Moose/
31   http://en.wikipedia.org/wiki/Meta-object_protocol
32Please note that this is not a tutorial on roles. Theyʼre trivial to learn and the details will vary from
language to language (if your language supports them). Instead, I focus on the concepts at hand rather than
getting sidetracked with detail.
that when we call these methods, weʼll get it from one base class or another, whichever is
first in the inheritance hierarchy.33

A word about mixins is in order. Some people like to share behavior via mixins, but theyʼd
have the “last wins” problem and would be of no help here. Consider the following Ruby
code:




33Actually, the language Eiffel would detect this issue and require you to be specific about which methods
you wanted. Joe Bob says “check it out”.
module Bomb
          def explode
              puts "Bomb explode"
          end
          def fuse
              puts "Bomb fuse"
          end
      end

      module Spouse
          def explode
              puts "Spouse explode"
          end
          def fuse
              puts "Spouse fuse"
          end
      end

      class PracticalJoke
          include Spouse
          include Bomb
      end

      joke = PracticalJoke.new()
      joke.explode
      joke.fuse

Example 10: Mixin Conflicts In Ruby


That will print out "Bomb explode" and "Bomb fuse". In other words, the Bomb's methods
overwrite the Spouse methods. The methods of the last module mixed into your class will
silently overwrite the previous module's methods. To try and reuse one method from each
means that one or both of those need to be handled differently. Additionally, mixins as
currently implemented provide no introspective capability letting outside code know that
your class can "do" a given mixin. With roles, it's a matter of asking the object's meta
class if it performs a given role:

      if ( $object->meta->does_role($some_role) ) { ... }

Example 11: Role Introspection In Perl


We could use delegation to solve the "which method can we use" problem caused by
multiple inheritance or mixins, but now you have a new problem that you have to set up
whatever scaffolding your language requires for delegation. Plus, delegation often means
that you send a message to the receiving object and if it needs more information, it canʼt
always communicate with the original invocant.

With multiple inheritance, you can often ask if a class ISA different class which supports
the behavior you need but you get the troubles with inheritance. With both delegation and
mixins, you avoid some (not all) of the issues with inheritance by you often can't
programmatically determine whether or not a given instance supports the behavior you
need. When using roles, you can simply ask a class or instance if it performs that role and
take action accordingly. Delegation obscures this. Roles, however, make this issue trivial
as Example 11 shows.

Here's how you might write the PracticalJoke class in Perl, using roles:

      {
             package Bomb;
             use Moose::Role;
             sub fuse    { print "Bomb exploden" }
             sub explode { print "Bomb fusen" }
      }
      {
             package Spouse;
             use Moose::Role;
             sub fuse    { print "Spouse exploden" }
             sub explode { print "Spouse fusen" }
      }
      {
             package PracticalJoke;
             use Moose;
             with qw(Bomb Spouse);
      }
      my $joke = PracticalJoke->new();
      $joke->explode();
      $joke->fuse();

Example 12: The PracticalJoke Class In Perl, Using Roles

Except that code won't compile.34 It fails almost immediately with a stacktrace and the
error message "Due to a method name conflict in roles 'Bomb' and
'Spouse', the method 'fuse' must be implemented or excluded by
'PracticalJoke'".35

        package PracticalJoke;
        use Moose;
        with 'Bomb'   => { excludes => 'explode' },
             'Spouse' => { excludes => 'fuse' };

Example 13: Excluding Conflicting Role Methods In Perl


And the order of consuming those roles is irrelevant, unlike multiple inheritance or
mixins.36



34 I'm lying to you. It does compile, but fails at "composition" time. With typical usage of Moose, you
frequently won't notice the difference.

35 This is for Moose 0.81 and it should report both the fuse() and explode() methods as being in
conflict. Currently it reports one and when you resolve the conflict, it reports the other. A bug report has been
filed.

36
 That's also not entirely true. I'm apparently a pathological liar. There are things you can do which would
make the order of role consumption important, but you generally have to try to achieve this.
All of a sudden, things start to look a bit interesting for traits. The order in which you
compose roles is irrelevant and you have full control over those methods. In fact, you
have more control than what you see here. Still want to sometimes have the
Spouse::fuse() method available?

         package PracticalJoke;
         use Moose;
         with 'Bomb'   => { excludes => 'explode' },
              'Spouse' => { excludes => 'fuse',
                            alias    => { fuse => 'random_fuse' } };

Example 14: Aliasing Conflicting Methods, Rather Than Simply Excluding Them


And in your actual code:

           $joke->fuse(14);      # timed fuse
           # or
           $joke->random_fuse(); # who knows?

Example 15: Calling Both The Needed fuse() method and its aliased version.


Whatʼs even more interesting is that if you have conflicting methods, your code wonʼt even
compile 37, but will instead fail with a useful error message and stack trace.

So not only is it easy to use a role, it's easy to write one, too.

And what about the poor programmer who has the multiple inheritance mess? His
inheritance hierarchy now looks like this:




Figure 8: No More Multiple Inheritance!




37   Actually, it fails at composition time, but we wonʼt go there.
And now he can fall safely back to his old “cut-n-paste” coding and update “Country” by
merely pasting in the “DoesAuditing” role:

          package Country;
          use Moose;
          extends "My::ResultSource";
          with qw(DoesStatic DoesAuditing);

Example 16: Fixing the Country Multiple Inheritance Problem


If the Country class doesnʼt provide all of the methods that DoesAuditing requires, or if
there are method conflicts, he gets a compile-time failure. In practice, weʼve found at the
BBC that many times, the code simply “just works” by applying a new role. In fact, these
techniques are proving powerful enough that other teams in the BBC are starting to switch
to roles with similarly pleasing results.

A Real World Example

Now those are rather trivial examples, but hereʼs a real example from part of the metadata
project I work on. We have one subsystem which returns “resultsets”. These are sets of
objects from the database. The old class hierarchy looked like this (again, this is only a
small part of the system):




Figure 9: Old ResultSet Hierarchy 38


If you look closely, youʼll notice that we donʼt have multiple inheritance here -- though we
did in other parts of our system -- but we do have several levels of inheritance, making it
difficult at times to know where behavior was implemented. After converting this system to
roles, hereʼs our new inheritance hierarchy:




Figure 10: New ResultSet Hierarchy39


I realize that this is tough to see, but itʼs completely flat, with only a single abstract base
class. If you open up a particular class, you might see something like this:40




38   Full size image: http://www.flickr.com/photos/publius_ovidius/3426561336/sizes/o/
39   Full size image: http://www.flickr.com/photos/publius_ovidius/3426561340/sizes/o/
40Actually, that code is a lie. I've cleaned up some of the names to make them a tad more understandable
to people unfamiliar with our system. For something closer to the truth, here's an old diagram of the
ResultSet hierarchy with roles listed: http://www.flickr.com/photos/publius_ovidius/3425903699/sizes/o/
package BBC::Programme::Episode;
          use Moose;
          extends 'BBC::ResultSet';
          with qw(
               DoesSearch::Broadcasts
               DoesSearch::Tags
               DoesSearch::Titles
               DoesSearch::Promotions
               DoesIdentifier::Universal
          );

Example 17: What A Full List Of Consumed Roles Might Look Like


In the process of refactoring, we found that many of those roles were originally behaviors
shared across classes which should not implement them. We didnʼt see this at first
because we have a large system and these “hidden” behaviors were safely tucked away in
base classes. These were serious bugs waiting to happen, but by refactoring into
appropriately named roles, any programmer can open up a class and see at a glance
which behaviors it really implements and if the roles are well-named, errors become much
more apparent. Not only do we get composition safety, we gain comprehension.

Conclusion
The power of roles -- “traits” if you prefer -- is that they allow classes to resume a more
natural role (er, sorry about that) as agents of responsibility. Shared behavior is handled
via roles. By making this separation, the BBC has found that we have better compile time
safety, fewer “hidden” methods and classes whose behaviors are far more
comprehensible, even to newer programmers.

Of course, roles can only mitigate risk, not eliminate it. In Perl 5, for example, you donʼt
have method signatures, so there can be some ambiguity when a role lists the methods it
requires, but in practice, weʼve not even found this to be a problem 41, despite it being a
glaring shortcoming of Perl 5.42 We are now developing code faster and with greater
understanding. Though the concept of roles is only a few years old, itʼs rapidly proving to
be an excellent technology.




41   To be fair, our comprehensive test suite may account for a lot of this.
42Perl 6 has proper method signatures and theyʼre far more robust than most languages and Moose
extensions are adding them to Perl 5.
Addendum: A Quick Note About Dynamic Languages
The debates about static versus dynamic languages rage even stronger than the debates
about inheritance. In my opinion, much of the debate is rather silly as many people don't
really understand what type systems are all about.43 When I find myself programming in a
static language, I often find myself missing many of the features which make dynamic
languages so flexible. However, when I'm programming in dynamic languages, I miss
features which make static languages a bit "safer" to use. One example:


         print $customer->as_xml;

Example 18: Does The as_xml() method exist?


In many dynamic languages, methods like that can be generated at runtime and as a
result, you can't know at compile time if this is an error. Consider the Java equivalent:


         System.out.println(customer.as_xml());

Example 19: Java Compile-Time Safety

With a static language, that code won't even compile. You don't worry as much about
frantic 3:00 AM phone calls due to that method not existing. Roles can mitigate this by
specifying methods they require.

          package DoesSerialization::YAML;
          use Moose::Role;

          requires 'as_xml';

          sub as_yaml {
              my $self = shift;
              my $xml = $self->as_xml();
              # convert XML to YAML and return
          }

Example 20: Using Roles to Bring Some Static Language Safety to Dynamic Languages


If the class which consumes the DoesSerialization::YAML role fails to implement
as_xml() either directly or via another role which provides this method, you get a
composition-time failure44 and your code will not run. Thus, roles can help bring some
static safety to dynamic languages.




43   See http://www.pphsg.org/cdsmith/types.html for a nice introduction.
44 As with so many things about technology, there are techniques to make this a runtime failure if you really
like being woken up at 3:00 AM.
Reviewers

Iʼm grateful for the following individuals who contributed thoughts and advice on this paper.
Errors, of course, are still mine. They are listed below in no particular order.

• Zbigniew Lukasiak
• Jerry Sievert
• Anton Berezin
• Tim Brown
• James Laver

Más contenido relacionado

La actualidad más candente

Refactoring 2TheMax (con ReSharper)
Refactoring 2TheMax (con ReSharper)Refactoring 2TheMax (con ReSharper)
Refactoring 2TheMax (con ReSharper)DotNetMarche
 
How to write maintainable code
How to write maintainable codeHow to write maintainable code
How to write maintainable codePeter Hilton
 
How to write good comments
How to write good commentsHow to write good comments
How to write good commentsPeter Hilton
 
Writing clean and maintainable code
Writing clean and maintainable codeWriting clean and maintainable code
Writing clean and maintainable codeMarko Heijnen
 
If I Had a Hammer...
If I Had a Hammer...If I Had a Hammer...
If I Had a Hammer...Kevlin Henney
 

La actualidad más candente (7)

Refactoring 2TheMax (con ReSharper)
Refactoring 2TheMax (con ReSharper)Refactoring 2TheMax (con ReSharper)
Refactoring 2TheMax (con ReSharper)
 
How to write maintainable code
How to write maintainable codeHow to write maintainable code
How to write maintainable code
 
How to write good comments
How to write good commentsHow to write good comments
How to write good comments
 
C c#
C c#C c#
C c#
 
Python overview
Python overviewPython overview
Python overview
 
Writing clean and maintainable code
Writing clean and maintainable codeWriting clean and maintainable code
Writing clean and maintainable code
 
If I Had a Hammer...
If I Had a Hammer...If I Had a Hammer...
If I Had a Hammer...
 

Destacado

A/B Testing - What your mother didn't tell you
A/B Testing - What your mother didn't tell youA/B Testing - What your mother didn't tell you
A/B Testing - What your mother didn't tell youCurtis Poe
 
Test::Class::Moose
Test::Class::MooseTest::Class::Moose
Test::Class::MooseCurtis Poe
 
Disappearing Managers, YAPC::EU 2014, Bulgaria, Keynote
Disappearing Managers, YAPC::EU 2014, Bulgaria, KeynoteDisappearing Managers, YAPC::EU 2014, Bulgaria, Keynote
Disappearing Managers, YAPC::EU 2014, Bulgaria, KeynoteCurtis Poe
 
Inheritance Versus Roles
Inheritance Versus RolesInheritance Versus Roles
Inheritance Versus RolesCurtis Poe
 
How to Fake a Database Design
How to Fake a Database DesignHow to Fake a Database Design
How to Fake a Database DesignCurtis Poe
 
Dark Matter, Public Health, and Scientific Computing
Dark Matter, Public Health, and Scientific ComputingDark Matter, Public Health, and Scientific Computing
Dark Matter, Public Health, and Scientific ComputingGreg Wilson
 
How to Become Rich, Famous, and Popular While Using Your Programming Skills t...
How to Become Rich, Famous, and Popular While Using Your Programming Skills t...How to Become Rich, Famous, and Popular While Using Your Programming Skills t...
How to Become Rich, Famous, and Popular While Using Your Programming Skills t...Greg Wilson
 
What We've Learned From Building Basie
What We've Learned From Building BasieWhat We've Learned From Building Basie
What We've Learned From Building BasieGreg Wilson
 
Bits of Evidence
Bits of EvidenceBits of Evidence
Bits of EvidenceGreg Wilson
 
The Lies We Tell About Software Testing
The Lies We Tell About Software TestingThe Lies We Tell About Software Testing
The Lies We Tell About Software TestingCurtis Poe
 

Destacado (10)

A/B Testing - What your mother didn't tell you
A/B Testing - What your mother didn't tell youA/B Testing - What your mother didn't tell you
A/B Testing - What your mother didn't tell you
 
Test::Class::Moose
Test::Class::MooseTest::Class::Moose
Test::Class::Moose
 
Disappearing Managers, YAPC::EU 2014, Bulgaria, Keynote
Disappearing Managers, YAPC::EU 2014, Bulgaria, KeynoteDisappearing Managers, YAPC::EU 2014, Bulgaria, Keynote
Disappearing Managers, YAPC::EU 2014, Bulgaria, Keynote
 
Inheritance Versus Roles
Inheritance Versus RolesInheritance Versus Roles
Inheritance Versus Roles
 
How to Fake a Database Design
How to Fake a Database DesignHow to Fake a Database Design
How to Fake a Database Design
 
Dark Matter, Public Health, and Scientific Computing
Dark Matter, Public Health, and Scientific ComputingDark Matter, Public Health, and Scientific Computing
Dark Matter, Public Health, and Scientific Computing
 
How to Become Rich, Famous, and Popular While Using Your Programming Skills t...
How to Become Rich, Famous, and Popular While Using Your Programming Skills t...How to Become Rich, Famous, and Popular While Using Your Programming Skills t...
How to Become Rich, Famous, and Popular While Using Your Programming Skills t...
 
What We've Learned From Building Basie
What We've Learned From Building BasieWhat We've Learned From Building Basie
What We've Learned From Building Basie
 
Bits of Evidence
Bits of EvidenceBits of Evidence
Bits of Evidence
 
The Lies We Tell About Software Testing
The Lies We Tell About Software TestingThe Lies We Tell About Software Testing
The Lies We Tell About Software Testing
 

Similar a Inheritance Versus Roles - The In-Depth Version

Low maintenance perl notes
Low maintenance perl notesLow maintenance perl notes
Low maintenance perl notesPerrin Harkins
 
Languages used by web app development services remotestac x
Languages used by web app development services  remotestac xLanguages used by web app development services  remotestac x
Languages used by web app development services remotestac xRemote Stacx
 
Computer Science Is The Study Of Principals And How The...
Computer Science Is The Study Of Principals And How The...Computer Science Is The Study Of Principals And How The...
Computer Science Is The Study Of Principals And How The...Laura Martin
 
A Strong Object Recognition Using Lbp, Ltp And Rlbp
A Strong Object Recognition Using Lbp, Ltp And RlbpA Strong Object Recognition Using Lbp, Ltp And Rlbp
A Strong Object Recognition Using Lbp, Ltp And RlbpRikki Wright
 
Java And Community Support
Java And Community SupportJava And Community Support
Java And Community SupportWilliam Grosso
 
The Ring programming language version 1.9 book - Part 97 of 210
The Ring programming language version 1.9 book - Part 97 of 210The Ring programming language version 1.9 book - Part 97 of 210
The Ring programming language version 1.9 book - Part 97 of 210Mahmoud Samir Fayed
 
Culture And Aesthetic Revisited
Culture And Aesthetic RevisitedCulture And Aesthetic Revisited
Culture And Aesthetic RevisitedAdam Keys
 
The Ring programming language version 1.5.1 book - Part 173 of 180
The Ring programming language version 1.5.1 book - Part 173 of 180 The Ring programming language version 1.5.1 book - Part 173 of 180
The Ring programming language version 1.5.1 book - Part 173 of 180 Mahmoud Samir Fayed
 
Unit 4 Assignment 1 Comparative Study Of Programming...
Unit 4 Assignment 1 Comparative Study Of Programming...Unit 4 Assignment 1 Comparative Study Of Programming...
Unit 4 Assignment 1 Comparative Study Of Programming...Carmen Sanborn
 
C# and java comparing programming languages
C# and java  comparing programming languagesC# and java  comparing programming languages
C# and java comparing programming languagesShishir Roy
 
Envisioning the Future of Language Workbenches
Envisioning the Future of Language WorkbenchesEnvisioning the Future of Language Workbenches
Envisioning the Future of Language WorkbenchesMarkus Voelter
 
Putting the science in computer science
Putting the science in computer sciencePutting the science in computer science
Putting the science in computer scienceFelienne Hermans
 
Software_engineering.pptx
Software_engineering.pptxSoftware_engineering.pptx
Software_engineering.pptxjohn6938
 
Tech breakfast 18
Tech breakfast 18Tech breakfast 18
Tech breakfast 18James Leone
 
The Four Principles Of Object Oriented Programming
The Four Principles Of Object Oriented ProgrammingThe Four Principles Of Object Oriented Programming
The Four Principles Of Object Oriented ProgrammingDiane Allen
 

Similar a Inheritance Versus Roles - The In-Depth Version (20)

Low maintenance perl notes
Low maintenance perl notesLow maintenance perl notes
Low maintenance perl notes
 
OOP Java
OOP JavaOOP Java
OOP Java
 
Languages used by web app development services remotestac x
Languages used by web app development services  remotestac xLanguages used by web app development services  remotestac x
Languages used by web app development services remotestac x
 
Computer Science Is The Study Of Principals And How The...
Computer Science Is The Study Of Principals And How The...Computer Science Is The Study Of Principals And How The...
Computer Science Is The Study Of Principals And How The...
 
A Strong Object Recognition Using Lbp, Ltp And Rlbp
A Strong Object Recognition Using Lbp, Ltp And RlbpA Strong Object Recognition Using Lbp, Ltp And Rlbp
A Strong Object Recognition Using Lbp, Ltp And Rlbp
 
Java And Community Support
Java And Community SupportJava And Community Support
Java And Community Support
 
The Ring programming language version 1.9 book - Part 97 of 210
The Ring programming language version 1.9 book - Part 97 of 210The Ring programming language version 1.9 book - Part 97 of 210
The Ring programming language version 1.9 book - Part 97 of 210
 
Culture And Aesthetic Revisited
Culture And Aesthetic RevisitedCulture And Aesthetic Revisited
Culture And Aesthetic Revisited
 
The Ring programming language version 1.5.1 book - Part 173 of 180
The Ring programming language version 1.5.1 book - Part 173 of 180 The Ring programming language version 1.5.1 book - Part 173 of 180
The Ring programming language version 1.5.1 book - Part 173 of 180
 
Unit 4 Assignment 1 Comparative Study Of Programming...
Unit 4 Assignment 1 Comparative Study Of Programming...Unit 4 Assignment 1 Comparative Study Of Programming...
Unit 4 Assignment 1 Comparative Study Of Programming...
 
C# and java comparing programming languages
C# and java  comparing programming languagesC# and java  comparing programming languages
C# and java comparing programming languages
 
Envisioning the Future of Language Workbenches
Envisioning the Future of Language WorkbenchesEnvisioning the Future of Language Workbenches
Envisioning the Future of Language Workbenches
 
Putting the science in computer science
Putting the science in computer sciencePutting the science in computer science
Putting the science in computer science
 
Software_engineering.pptx
Software_engineering.pptxSoftware_engineering.pptx
Software_engineering.pptx
 
Tech breakfast 18
Tech breakfast 18Tech breakfast 18
Tech breakfast 18
 
December06Bulletin
December06BulletinDecember06Bulletin
December06Bulletin
 
December06Bulletin
December06BulletinDecember06Bulletin
December06Bulletin
 
Oop Article Jan 08
Oop Article Jan 08Oop Article Jan 08
Oop Article Jan 08
 
Groovy And Grails
Groovy And GrailsGroovy And Grails
Groovy And Grails
 
The Four Principles Of Object Oriented Programming
The Four Principles Of Object Oriented ProgrammingThe Four Principles Of Object Oriented Programming
The Four Principles Of Object Oriented Programming
 

Más de Curtis Poe

How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.Curtis Poe
 
life-off-earth.pptx
life-off-earth.pptxlife-off-earth.pptx
life-off-earth.pptxCurtis Poe
 
Corinna-2023.pptx
Corinna-2023.pptxCorinna-2023.pptx
Corinna-2023.pptxCurtis Poe
 
Corinna Status 2022.pptx
Corinna Status 2022.pptxCorinna Status 2022.pptx
Corinna Status 2022.pptxCurtis Poe
 
Rummaging in the clOOset
Rummaging in the clOOsetRummaging in the clOOset
Rummaging in the clOOsetCurtis Poe
 
Rescuing a-legacy-codebase
Rescuing a-legacy-codebaseRescuing a-legacy-codebase
Rescuing a-legacy-codebaseCurtis Poe
 
Perl 6 For Mere Mortals
Perl 6 For Mere MortalsPerl 6 For Mere Mortals
Perl 6 For Mere MortalsCurtis Poe
 
Are Managers An Endangered Species?
Are Managers An Endangered Species?Are Managers An Endangered Species?
Are Managers An Endangered Species?Curtis Poe
 
A Whirlwind Tour of Test::Class
A Whirlwind Tour of Test::ClassA Whirlwind Tour of Test::Class
A Whirlwind Tour of Test::ClassCurtis Poe
 
Agile Companies Go P.O.P.
Agile Companies Go P.O.P.Agile Companies Go P.O.P.
Agile Companies Go P.O.P.Curtis Poe
 
Testing With Test::Class
Testing With Test::ClassTesting With Test::Class
Testing With Test::ClassCurtis Poe
 
Logic Progamming in Perl
Logic Progamming in PerlLogic Progamming in Perl
Logic Progamming in PerlCurtis Poe
 
Turbo Charged Test Suites
Turbo Charged Test SuitesTurbo Charged Test Suites
Turbo Charged Test SuitesCurtis Poe
 

Más de Curtis Poe (14)

How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.
 
life-off-earth.pptx
life-off-earth.pptxlife-off-earth.pptx
life-off-earth.pptx
 
Corinna-2023.pptx
Corinna-2023.pptxCorinna-2023.pptx
Corinna-2023.pptx
 
Corinna Status 2022.pptx
Corinna Status 2022.pptxCorinna Status 2022.pptx
Corinna Status 2022.pptx
 
Rummaging in the clOOset
Rummaging in the clOOsetRummaging in the clOOset
Rummaging in the clOOset
 
Rescuing a-legacy-codebase
Rescuing a-legacy-codebaseRescuing a-legacy-codebase
Rescuing a-legacy-codebase
 
Perl 6 For Mere Mortals
Perl 6 For Mere MortalsPerl 6 For Mere Mortals
Perl 6 For Mere Mortals
 
Are Managers An Endangered Species?
Are Managers An Endangered Species?Are Managers An Endangered Species?
Are Managers An Endangered Species?
 
A Whirlwind Tour of Test::Class
A Whirlwind Tour of Test::ClassA Whirlwind Tour of Test::Class
A Whirlwind Tour of Test::Class
 
Agile Companies Go P.O.P.
Agile Companies Go P.O.P.Agile Companies Go P.O.P.
Agile Companies Go P.O.P.
 
Econ101
Econ101Econ101
Econ101
 
Testing With Test::Class
Testing With Test::ClassTesting With Test::Class
Testing With Test::Class
 
Logic Progamming in Perl
Logic Progamming in PerlLogic Progamming in Perl
Logic Progamming in Perl
 
Turbo Charged Test Suites
Turbo Charged Test SuitesTurbo Charged Test Suites
Turbo Charged Test Suites
 

Último

React JS; all concepts. Contains React Features, JSX, functional & Class comp...
React JS; all concepts. Contains React Features, JSX, functional & Class comp...React JS; all concepts. Contains React Features, JSX, functional & Class comp...
React JS; all concepts. Contains React Features, JSX, functional & Class comp...Karmanjay Verma
 
Abdul Kader Baba- Managing Cybersecurity Risks and Compliance Requirements i...
Abdul Kader Baba- Managing Cybersecurity Risks  and Compliance Requirements i...Abdul Kader Baba- Managing Cybersecurity Risks  and Compliance Requirements i...
Abdul Kader Baba- Managing Cybersecurity Risks and Compliance Requirements i...itnewsafrica
 
All These Sophisticated Attacks, Can We Really Detect Them - PDF
All These Sophisticated Attacks, Can We Really Detect Them - PDFAll These Sophisticated Attacks, Can We Really Detect Them - PDF
All These Sophisticated Attacks, Can We Really Detect Them - PDFMichael Gough
 
Potential of AI (Generative AI) in Business: Learnings and Insights
Potential of AI (Generative AI) in Business: Learnings and InsightsPotential of AI (Generative AI) in Business: Learnings and Insights
Potential of AI (Generative AI) in Business: Learnings and InsightsRavi Sanghani
 
Infrared simulation and processing on Nvidia platforms
Infrared simulation and processing on Nvidia platformsInfrared simulation and processing on Nvidia platforms
Infrared simulation and processing on Nvidia platformsYoss Cohen
 
Generative AI - Gitex v1Generative AI - Gitex v1.pptx
Generative AI - Gitex v1Generative AI - Gitex v1.pptxGenerative AI - Gitex v1Generative AI - Gitex v1.pptx
Generative AI - Gitex v1Generative AI - Gitex v1.pptxfnnc6jmgwh
 
Decarbonising Buildings: Making a net-zero built environment a reality
Decarbonising Buildings: Making a net-zero built environment a realityDecarbonising Buildings: Making a net-zero built environment a reality
Decarbonising Buildings: Making a net-zero built environment a realityIES VE
 
Modern Roaming for Notes and Nomad – Cheaper Faster Better Stronger
Modern Roaming for Notes and Nomad – Cheaper Faster Better StrongerModern Roaming for Notes and Nomad – Cheaper Faster Better Stronger
Modern Roaming for Notes and Nomad – Cheaper Faster Better Strongerpanagenda
 
The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...
The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...
The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...Wes McKinney
 
Microsoft 365 Copilot: How to boost your productivity with AI – Part two: Dat...
Microsoft 365 Copilot: How to boost your productivity with AI – Part two: Dat...Microsoft 365 Copilot: How to boost your productivity with AI – Part two: Dat...
Microsoft 365 Copilot: How to boost your productivity with AI – Part two: Dat...Nikki Chapple
 
JET Technology Labs White Paper for Virtualized Security and Encryption Techn...
JET Technology Labs White Paper for Virtualized Security and Encryption Techn...JET Technology Labs White Paper for Virtualized Security and Encryption Techn...
JET Technology Labs White Paper for Virtualized Security and Encryption Techn...amber724300
 
Varsha Sewlal- Cyber Attacks on Critical Critical Infrastructure
Varsha Sewlal- Cyber Attacks on Critical Critical InfrastructureVarsha Sewlal- Cyber Attacks on Critical Critical Infrastructure
Varsha Sewlal- Cyber Attacks on Critical Critical Infrastructureitnewsafrica
 
Irene Moetsana-Moeng: Stakeholders in Cybersecurity: Collaborative Defence fo...
Irene Moetsana-Moeng: Stakeholders in Cybersecurity: Collaborative Defence fo...Irene Moetsana-Moeng: Stakeholders in Cybersecurity: Collaborative Defence fo...
Irene Moetsana-Moeng: Stakeholders in Cybersecurity: Collaborative Defence fo...itnewsafrica
 
Glenn Lazarus- Why Your Observability Strategy Needs Security Observability
Glenn Lazarus- Why Your Observability Strategy Needs Security ObservabilityGlenn Lazarus- Why Your Observability Strategy Needs Security Observability
Glenn Lazarus- Why Your Observability Strategy Needs Security Observabilityitnewsafrica
 
Generative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfGenerative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfIngrid Airi González
 
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotes
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotesMuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotes
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotesManik S Magar
 
Landscape Catalogue 2024 Australia-1.pdf
Landscape Catalogue 2024 Australia-1.pdfLandscape Catalogue 2024 Australia-1.pdf
Landscape Catalogue 2024 Australia-1.pdfAarwolf Industries LLC
 
Top 10 Hubspot Development Companies in 2024
Top 10 Hubspot Development Companies in 2024Top 10 Hubspot Development Companies in 2024
Top 10 Hubspot Development Companies in 2024TopCSSGallery
 
Assure Ecommerce and Retail Operations Uptime with ThousandEyes
Assure Ecommerce and Retail Operations Uptime with ThousandEyesAssure Ecommerce and Retail Operations Uptime with ThousandEyes
Assure Ecommerce and Retail Operations Uptime with ThousandEyesThousandEyes
 
Long journey of Ruby standard library at RubyConf AU 2024
Long journey of Ruby standard library at RubyConf AU 2024Long journey of Ruby standard library at RubyConf AU 2024
Long journey of Ruby standard library at RubyConf AU 2024Hiroshi SHIBATA
 

Último (20)

React JS; all concepts. Contains React Features, JSX, functional & Class comp...
React JS; all concepts. Contains React Features, JSX, functional & Class comp...React JS; all concepts. Contains React Features, JSX, functional & Class comp...
React JS; all concepts. Contains React Features, JSX, functional & Class comp...
 
Abdul Kader Baba- Managing Cybersecurity Risks and Compliance Requirements i...
Abdul Kader Baba- Managing Cybersecurity Risks  and Compliance Requirements i...Abdul Kader Baba- Managing Cybersecurity Risks  and Compliance Requirements i...
Abdul Kader Baba- Managing Cybersecurity Risks and Compliance Requirements i...
 
All These Sophisticated Attacks, Can We Really Detect Them - PDF
All These Sophisticated Attacks, Can We Really Detect Them - PDFAll These Sophisticated Attacks, Can We Really Detect Them - PDF
All These Sophisticated Attacks, Can We Really Detect Them - PDF
 
Potential of AI (Generative AI) in Business: Learnings and Insights
Potential of AI (Generative AI) in Business: Learnings and InsightsPotential of AI (Generative AI) in Business: Learnings and Insights
Potential of AI (Generative AI) in Business: Learnings and Insights
 
Infrared simulation and processing on Nvidia platforms
Infrared simulation and processing on Nvidia platformsInfrared simulation and processing on Nvidia platforms
Infrared simulation and processing on Nvidia platforms
 
Generative AI - Gitex v1Generative AI - Gitex v1.pptx
Generative AI - Gitex v1Generative AI - Gitex v1.pptxGenerative AI - Gitex v1Generative AI - Gitex v1.pptx
Generative AI - Gitex v1Generative AI - Gitex v1.pptx
 
Decarbonising Buildings: Making a net-zero built environment a reality
Decarbonising Buildings: Making a net-zero built environment a realityDecarbonising Buildings: Making a net-zero built environment a reality
Decarbonising Buildings: Making a net-zero built environment a reality
 
Modern Roaming for Notes and Nomad – Cheaper Faster Better Stronger
Modern Roaming for Notes and Nomad – Cheaper Faster Better StrongerModern Roaming for Notes and Nomad – Cheaper Faster Better Stronger
Modern Roaming for Notes and Nomad – Cheaper Faster Better Stronger
 
The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...
The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...
The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...
 
Microsoft 365 Copilot: How to boost your productivity with AI – Part two: Dat...
Microsoft 365 Copilot: How to boost your productivity with AI – Part two: Dat...Microsoft 365 Copilot: How to boost your productivity with AI – Part two: Dat...
Microsoft 365 Copilot: How to boost your productivity with AI – Part two: Dat...
 
JET Technology Labs White Paper for Virtualized Security and Encryption Techn...
JET Technology Labs White Paper for Virtualized Security and Encryption Techn...JET Technology Labs White Paper for Virtualized Security and Encryption Techn...
JET Technology Labs White Paper for Virtualized Security and Encryption Techn...
 
Varsha Sewlal- Cyber Attacks on Critical Critical Infrastructure
Varsha Sewlal- Cyber Attacks on Critical Critical InfrastructureVarsha Sewlal- Cyber Attacks on Critical Critical Infrastructure
Varsha Sewlal- Cyber Attacks on Critical Critical Infrastructure
 
Irene Moetsana-Moeng: Stakeholders in Cybersecurity: Collaborative Defence fo...
Irene Moetsana-Moeng: Stakeholders in Cybersecurity: Collaborative Defence fo...Irene Moetsana-Moeng: Stakeholders in Cybersecurity: Collaborative Defence fo...
Irene Moetsana-Moeng: Stakeholders in Cybersecurity: Collaborative Defence fo...
 
Glenn Lazarus- Why Your Observability Strategy Needs Security Observability
Glenn Lazarus- Why Your Observability Strategy Needs Security ObservabilityGlenn Lazarus- Why Your Observability Strategy Needs Security Observability
Glenn Lazarus- Why Your Observability Strategy Needs Security Observability
 
Generative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfGenerative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdf
 
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotes
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotesMuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotes
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotes
 
Landscape Catalogue 2024 Australia-1.pdf
Landscape Catalogue 2024 Australia-1.pdfLandscape Catalogue 2024 Australia-1.pdf
Landscape Catalogue 2024 Australia-1.pdf
 
Top 10 Hubspot Development Companies in 2024
Top 10 Hubspot Development Companies in 2024Top 10 Hubspot Development Companies in 2024
Top 10 Hubspot Development Companies in 2024
 
Assure Ecommerce and Retail Operations Uptime with ThousandEyes
Assure Ecommerce and Retail Operations Uptime with ThousandEyesAssure Ecommerce and Retail Operations Uptime with ThousandEyes
Assure Ecommerce and Retail Operations Uptime with ThousandEyes
 
Long journey of Ruby standard library at RubyConf AU 2024
Long journey of Ruby standard library at RubyConf AU 2024Long journey of Ruby standard library at RubyConf AU 2024
Long journey of Ruby standard library at RubyConf AU 2024
 

Inheritance Versus Roles - The In-Depth Version

  • 1. Eliminating Inheritance Via Smalltalk-Style Traits Copyright 2009 by Curtis “Ovid” Poe under the GNU Free Documentation License1 A Brief History of Pain Object-Oriented Programming (OOP) has been around for a long time. In fact, most of the core features we associate with OOP -- inheritance, polymorphism, classes and so on -- were introduced in 1967 with the language SIMULA 67 2. In the case of inheritance, one might believe that a concept hanging around for over four decades would have most of the kinks ironed out but one might also believe in the tooth fairy. Now if you ask people “what is essential to OOP”, youʼll get plenty of arguments over the matter, but “inheritance” is one area that many, many people disagree about violently. In fact, some languages such as Javascript, REBOL, Lua and Self avoid inheritance by reusing code via cloning existing objects and adding the behavior they need -- though if you squint, it often looks like single inheritance. But for languages that do implement inheritance, things should be pretty clear-cut, right? In inheritance, we can think of classes as types and subclasses as subtypes. So Dogs and Cats might be subclasses of Mammal and if you adhere to the Liskov Substitution Principle3 (LSP), you could drop any instance of a Dog or Cat into a place where we expect a Mammal and the code should run just fine. Of course, the devil is in the details and depending upon the languageʼs type system, violating the principle might be a compile-time error, a runtime error, or no error at all. Various mechanisms such as programming by contract, used by Eiffel4, or the unusual inheritance mechanism provided by Beta5 can be used to enforce LSP. Other languages, such as Ruby, use “duck typing” where you just sort of hope that the object in question has the methods you want (to be fair, this seems to work remarkably well for Rubyists). Still others argue that LSP isnʼt all itʼs cracked up to be and debate whether a 3D point should inherit from 2D point or vice versa and argue that somehow Liskov js flawed. Then there are discussions about using aspect-oriented programming to enforce strict equivalence, C++ templates versus Java generics, interfaces versus mixins, separation of implementation and interface and so on. Whatʼs important here is that these arenʼt debates about a piece of code you implemented last week. These are serious debates about the implementation of the languages themselves. While most people are generally on board about things such as polymorphism and encapsulation, arguments about appropriate use of inheritance have been going on for over four decades. 1 http://en.wikipedia.org/wiki/Text_of_the_GNU_Free_Documentation_License 2 http://heim.ifi.uio.no/~kristen/FORSKNINGSDOK_MAPPE/F_OO_start.html 3 http://www.objectmentor.com/resources/articles/lsp.pdf 4 http://www.eiffel.com/ 5 http://www.daimi.au.dk/~beta/
  • 2. If you want to dig further into this, youʼll discover that the inheritance mess is so serious that many top-notch OOP developers recommend that you avoid inheritance as much as possible and use delegation, mixins, or other tricks to implement code reuse. This, my friends, is what we call a “code smell”. A code smell doesnʼt mean that thereʼs a problem with code, but it does mean that it bears further investigation. When you have a programming practice thatʼs been around for over four decades and people are still arguing about the fundamentals of it, further investigation is definitely warranted. So letʼs investigate. A Deeper Look At Pain First off, Iʼm going to go out on a limb and suppose that most readers of this are not programming language designers. Language designers have a much harder problem than language users. They shouldnʼt just hack something together and hope it works, they have to research it. They have to think about it very carefully. They have to consider how their language is likely to be actually used and they have to implement it to support that use. Some languages, like Eiffel, are very carefully designed and others grow organically 6, but most OOP languages that you know probably have had a bit of thought applied to how they implement OOP. This raises a couple of interesting point. First, if the language is even remotely popular, it probably means that the author(s) of the language probably has a lot more on the ball than most of us. Second, they had a lot more time to develop their OOP implementation. So better programmers with more time on their hands will probably have an implementation of code will likely be better than our implementation of code. Now if youʼre like me, youʼre probably an average programmer working with a deadline and you need to translate your (possibly ill-conceived) specs into the language designerʼs carefully thought-out language and thereʼs a good chance that what you create wonʼt be perfect the first time around. Real world translation: most “OO” code bases tend to be a mess. Iʼve worked with plenty of them in a variety of languages and theyʼre often horrific and tough to maintain, but the key thing is that they work. They work despite violating the Liskov Substitution Principle, despite ignoring strict equivalence, despite not understanding cohesion. They have all sorts of nasty hacks to work around the developerʼs lack of knowledge and this makes them a nightmare to maintain and extend, but they work.7 This is because programmers generally arenʼt focused on theory. Theyʼre focused on behavior. “I need this method to read a config file”, so they write the code to do that. Sure, maybe they should be abstracting this into a ConfigManager class, but when they think about their deadline, timeʼs a-wastinʼ. Truth be told, I donʼt think this is a problem with developers. Yes, itʼs a problem that developers need better training and experience, but at the end of the day, while some developers are reading a paper on the original intention of the database relational models in databases (sadly, far too few), other developers are out at the pub, sharing a pint with friends, going on dates with their partners and spending time with their children. This isnʼt a bad thing. Itʼs OK to have a life outside of programming. Whatʼs a bad thing is that to be a great programmer, you usually find yourself forgoing a lot of that life. What we need are programming tools that fit what developers really do rather than try to suddenly expect that 6 The popularity of PHP says a lot in favor of practice over theory. 7 Though I confess to still having nightmares about that C# developer who wrote a “StartHTML” class.
  • 3. developers are going to “step up their game.” Inheritance is not one of those programming tools. To investigate some of this, Iʼm going to focus on Perl. Though an oft-maligned language, its flexibility and power have a lot to recommend it. And, to be honest, though Iʼve programmed in a variety of different languages, Perl is the one I now know best and that means I can more easily focus on concepts and worry less about rabid readers attacking me for missing a semi-colon. First, letʼs take a look at a real-world case of inheritance: Figure 1: The B Inheritance Hierarchy8 Without going into too much detail, thatʼs an inheritance hierarchy which simulates how Perl variables work internally. For the end user this tends to be transparent, despite the apparent complexity and that's part of what classes should do for you. Still, very few Perl programmers ever need to touch this and fewer still know what it means. However, letʼs take a closer look at one section of it. 8 Full size image: http://www.flickr.com/photos/publius_ovidius/3646752998/sizes/o/
  • 4. Figure 2: A Closer Look At A Section of the B Inheritance Hierarchy Hmm, looks like we might have some multiple inheritance here, so letʼs focus on just that: Figure 3: Examining PVIV Inheritance
  • 5. With a large amount of hand-waving, “SV” means “variable”. “PV” is a string value and “IV” is an integer value9. A “PVIV” is a variable with both string and integer representations. To those working with languages with strict type systems, this seems absurd. However, in Perl, this means that you can do something like this: my $number = 3; $number += 2; print "I have $number apples"; Example 1: Printing A String In Perl And that final line prints “I have 5 apples”. In Java, those lines might look like this: int number = 3; number += 2; System.out.println("I have " + number + " apples"); Example 2: Printing A String In Java Java doesnʼt allow operator overloading, but theyʼve made an exception for the String class. Perl allows operator overloading but often doesnʼt need it because variables have “slots” (again, lots of hand-waving) with string, integer and numeric (double) values and each is used as appropriate given the context. Thatʼs why thereʼs this strange “PVIV” class. Most of the time this “just works”, but what most people donʼt know is that while the string, integer and numeric slots usually have complementary values (“5”, 5, 5.0), you can assign different values to those slots if you really need to (“five”, 5, 5.0)10 and ensure that the final line in the Perl code prints “I have five apples” even if the integer value is 5. Looking further, both the B::PV and B::IV classes have an as_string method and if you have a B::PVIV instantiation of a variable, printing its string value is trivial: print $variable->as_string; Example 3: Printing A String In Perl That prints the B::PV::as_string value because B::PVIV inherits from B::PV first. Ignoring for the moment the fact that this contrived example is a usually a stupid thing to do, what happens if you want the as_string value of the integer 5 and not the as_string value of the string “five”? Because of how multiple inheritance works and because of how these classes are designed internally, this becomes rather painful to get. print $variable->B::IV::as_string; Example 4: Printing An Integer Value in Perl 9"SV": Scalar value. "PV": Pointer value ("S" was taken and a string in C is merely a 'p'ointer to an array of chars. "IV": Integer value. 10This technique is known as a “Big Bucket of Stupid”, but some people prefer to call it a dualvar. See "dualvar NUM, STRING" in http://search.cpan.org/~gbarr/Scalar-List-Utils-1.21/lib/Scalar/Util.pm Despite my mockery, it does sometimes prove very useful.
  • 6. In short, weʼre forced to encode knowledge of our class structure into the method call and we have inheritance leading to a tremendous violation of encapsulation. Polymorphism, C3 linearization11 and other OOP techniques quietly fail here. If you have an instance of a class, you really shouldnʼt have to know about the structure of the class hierarchy to use it, but there you go. Real-World Pain At the BBC At this point youʼre probably thinking that Iʼm smoking crack. This is a contrived example and is so incredibly obscure -- though itʼs the sort of real-world uses the the B:: classes were designed to support -- that it doesnʼt seem relevant to what you do. So hereʼs a real- world (simplified) example of a problem we faced building a metadata system for the BBC.12 11 http://en.wikipedia.org/wiki/C3_linearization, a.k.a. “Mopping the Titanic” 12 http://www.bbc.co.uk/blogs/bbcinternet/2009/02/what_is_pips.html
  • 7. Figure 4: A BBC “Program” (which my British colleagues insist upon spelling as “programme”) This was part of our inheritance hierarchy for a program that you might watch on the BBC. Now OOP purists might shudder at this hierarchy, but look at this from the point of view of your average programmer. My::ResultSource is a single instance of an object we pull from our database object- relational mapper DBIx::Class.13 Most objects need to be audited (“who changed what?”), so an Audited object ISA My::ResultSource. All objects which need to be tagged (for example, tags you see on blog posts) are audited, so Tagged ISA Audited. Thus, any Program which needs to be tagged ISA Tagged. No, Iʼm not arguing that this is an appropriate use of OOP. Iʼm arguing that for the average developer, it doesnʼt necessarily seem that ridiculous. If someone alters an instance of a Programme (switching to the British spelling to distinguish from a computer “program”), the program automatically Does The Right Thing and works its auditing magic. Part of the reason it does this is because we, at present count, have over 30,000 tests for our system and partly because this inheritance hierachy, while violating a lot of rules, just works. My colleagues are a bunch of talented developers who Iʼd be happy to hire onto any company I work with (really, all of them -- Iʼm very lucky about this), but they've created an inheritance hierarchy several levels deep and even though itʼs only single inheritance in this example, we have started to run into problems with the very flexible nature of Perlʼs OOP behavior. For example, letʼs say that your Program class needs a from() method indicating where we got the the programme from. Because we have a deep inheritance hierarchy, we have to search through a number of classes to know if weʼre accidentally overriding something. As it turns out, DBIx::Class::ResultSource implements a completely different from() method and overriding that will break out code. Only because we have a good test suite are we protected from that. So itʼs fair to say that deep single inheritance tree can increase the cognitive load a programmer has to manage and thatʼs something we like to avoid. As a quick aside: Java programmers might very well stop here, point to their IDEs and laugh, explaining that their IDE can automatically point out when they're overriding something. With statically typed languages, such IDE support is common. However, dynamically typed languages generally donʼt have strong static analysis tools available and thus frequently canʼt take advantage of this.14 Tools for managing this sort of complexity -- and large-scale software is all about complexity management -- should be built into the language when possible and not rely on external software products.15 But moving along, I should point out that the BBC systems store more than just programmes. For example, we also store what we call “reference data”. This is data 13 http://search.cpan.org/dist/DBIx-Class/ 14Dynamic languages have other benefits and this limitation is hardly an indictment, so donʼt choose languages based on a single pet peeve. Plus, the dynamic SmallTalk languageʼs class browser provides a delightful counter-example. 15Anyone forced to use "vi" (not even "vim") while trying to create an emergency patch of broken code over a slow telnet connection at 2:30 in the morning is going to get very irritated if your codebase is so complex that you need a large IDE to comprehend it.
  • 8. which, unlike programmes, changes very infrequently, if at all. In your online store database, you might have a table listing US states. Those donʼt change very frequently and we might have a different base class named My::ResultSource::Static which might extend some of the behavior of My::ResultSource. Since the BBC has programmes from all over the world, we might have static data for countries. Figure 5: Our Inheritance Hierarchy Grows Since the static (reference) data doesnʼt change much, we donʼt need to audit it and our inheritance hierarchy, while becoming a bit more complex, is still single inheritance. But then the unthinkable happens: the Berlin Wall falls, the Soviet Union collapses, new Eastern European countries are popping up like popcorn. All of a sudden, weʼre changing our “static” country data quite a bit. Though our Country class still might benefit from a “static” base class, we now need to audit it because our editors are constantly updating it. Then our real problem starts. Country canʼt simply inherit from Audited because it will no longer inherit from My::ResultSource::Static. However, My::ResultSource::Static canʼt inherit from Audited because our other static data might not need auditing. The solution seems to be falling back on multiple inheritance.16 16One reviewer claimed that he had trouble following this example and asked if I could provide a clearer one. I was initially inclined to agree until I realize that his complaint supported the argument presented in this paper. Specifically, this example is a simplification of a real-world problem and if the inheritance is confusing, that's because this is what happens with inheritance.
  • 9. Figure 6: Solving A Compositional Problem With Multiple Inheritance The dangers of multiple inheritance are so well-known that I will not belabor them here, but suffice it to say that since so many OOP languages disallow multiple inheritance,17 itʼs fair to say that there might be a reason for avoiding it. And though the simple example in Figure 6 might look manageable, the reality is, systems grow. Figure 7: The “Moose” Inheritance Hierarchy (still relatively small)18 As mentioned back at Figure 4, those with a strong OOP background might shudder at the Country/Program hierarchy and, in fact, theyʼd be right. The problem kicking its head up is “separation of concerns”.19 If youʼre trying to avoid multiple inheritance, you might very well call your team lead over, show her the problem, and have her give a long lecture about how a Program ISA Audited is a terrible way of modeling the system and auditing functions belong in a separate class hierarchy which a Program delegates to and while youʼre at it, you shouldnʼt be inheriting from Tagged either. 17 C#, Objective-C, Object Pascal, Java, PHP, BETA and Smalltalk, to name a few. 18 Full size image: http://www.flickr.com/photos/publius_ovidius/3549146507/sizes/o/ 19 http://en.wikipedia.org/wiki/Separation_of_concerns
  • 10. Shamefacedly, you admit that sheʼs right and as she walks away, you quietly moan about all of the extra work you have to do when all you wanted to do was having “Country” write a single line to a log file. And thereʼs the disconnect. Programmers just want to Get Stuff Done and quite often we find that trying to model a large system “properly” (whatever that means) is hard. Most programmers arenʼt OOP experts and even those that are will generally tell you that the first iterations of OOP systems tend to have plenty of flaws in them. CRC cards,20 UML diagrams,21 and similar techniques, while laudable, donʼt seem to be employed very often.22 Taking Pain Killers So whatʼs really the problem going on here? A large part of the problem lies in how we use classes. Specifically, we use them for two different things.23 First, as agents of responsibility, a class needs to perform everything the class is responsible for. While this sounds obvious, it does mean that we have an upward pressure on class size. As systems grow and we need new features, we add them to our classes. Second, classes are used for code reuse. This is done primarily (but not exclusively) via inheritance. This causes an interesting issue. In Perl, when you write a library itʼs generally recommended that you not pollute the namespace of code which uses your library, but instead allow the consumer to choose which of code they want exported into their namespace. For example: use List::Util qw(reduce); my $product = reduce { $a * $b } 1 .. 10 Example 5: Mitigating Namespace Pollution in Perl Though List::Util offers several list utilities, you only get the ones you really need. Thatʼs great because then you have control over what youʼre getting and frankly, many times youʼre using a library and you only want one function out of it, not all. The same goes for classes, but if you inherit from them, you get all of the methods those classes provide,24 so what you really want are smaller classes so that you donʼt pull in irrelevant behavior. So classes are used in two different ways which tends to give them competing requirements of needing to be both both smaller and larger at the same time. This is 20 http://en.wikipedia.org/wiki/Class-Responsibility-Collaboration_card 21 http://en.wikipedia.org/wiki/UML_Diagram 22http://www.google.com/search?hl=en&q=uml+sucks and numerous books touching on these topics. Additionally, your author has worked for a number of companies which list UML as a job requirement, but never actually use UML. 23 http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf 24 Thatʼs not entirely fair. Some languages limit this, but you still may very well get methods you donʼt need.
  • 11. actually the root of the problem with classes and the solution here is to decouple these two uses. Enter “traits”. Traits, in this sense, refers to SmallTalk-style traits. These were first described in “Traits: Composable Units of Behaviour”25 in 2003. Though a relatively new concept in computer science terms, traits present a coherent alternative to code reuse via inheritance. Far from being a wild experiment, trait research was carried out at two separate universities under grants from the National Science Foundation and the Swiss National Foundation26 and for the masochistic, you can read a “A Typed Calculus of Traits”27 The basic idea of traits is simple. You identify a behavior you need to share across classes and you push it into a trait instead of a parent class. The trait provides the methods28 and lists other methods it requires. For example, letʼs say that all of your objects can be serialized as YAML, but you need to serialize them as XML. The trait might look like the following pseudo-code: trait XMLSerialization { import XML::Converter; requires 'Str as_yaml(void)'; Str as_xml(void) { return XML::Converter.from_yaml(this.as_yaml()); } } Example 6: Pseudo-code For A Trait And your class might look like this: class Customer does XMLSerialization { Str as_yaml(void) { ... } } Example 7: Pseudo-code For Consuming a Trait Later, someone else might write this: if ClassOf(customer).does(‘XMLSerialization’) { print customer.as_xml(); } Example 8: Pseudo-code Of Trait Introspection 25 http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf 26 http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf 27 http://people.cs.uchicago.edu/~jhr/papers/2004/fool-traits.pdf 28 Strictly speaking, these are argued to be pure methods, but in practice, thatʼs not always what happens.
  • 12. So now we have a way of organizing shared behavior without worrying about inheritance, but honestly, if this was all there was to traits, it wouldnʼt be that compelling. Fortunately, this is not all there is to traits. Now weʼre going to switch to examples in Perl 5 because aside from the fact that this is the language Iʼm now most comfortable working in, itʼs also probably the language which has the most widespread adoption of traits 29 and thereby has a solid implementation. In Perl, however, traits are called “roles” (due to the term “trait” already in use with Perl 6), so weʼll start using that term now. Further, weʼre going to use Moose30, a complete OO system for Perl with a built-in metaprotocol.31 It makes our OOP code more legible and has Moose::Role included, allowing you to use roles.32 First, imagine that youʼre writing a text adventure and there's a room with a practical joke in it which must serve as some sort of clue to the player. So you start writing a PracticalJoke class and it needs a non-lethal explosion and a fuse. Fortunately, you already have fuse() and explode() methods handy in Bomb and Spouse classes. So hereʼs what you write: # In Perl 5, packages and classes are the same package PracticalJoke; use Moose; extends qw(Bomb Spouse); # i.e., "inherits from" Example 9: Multiple Inheritance In Perl Right off the bat, we have a problem. Both Bomb and Spouse provide fuse() and explode() methods. Here are their properties: Method Description Bomb::fuse() Timed Spouse::fuse() Non-deterministic Bomb::explode() Lethal Spouse::explode() Wish it was lethal Table 1: Multiple Inheritance For The FAIL As you can see, this wonʼt work. We need the timing from the Bomb::fuse() method, but the non-lethal Spouse::explode() behavior. Obviously, multiple inheritance means 29 http://scg.unibe.ch/research/traits (see the “Perl” section) 30 http://search.cpan.org/dist/Moose/ 31 http://en.wikipedia.org/wiki/Meta-object_protocol 32Please note that this is not a tutorial on roles. Theyʼre trivial to learn and the details will vary from language to language (if your language supports them). Instead, I focus on the concepts at hand rather than getting sidetracked with detail.
  • 13. that when we call these methods, weʼll get it from one base class or another, whichever is first in the inheritance hierarchy.33 A word about mixins is in order. Some people like to share behavior via mixins, but theyʼd have the “last wins” problem and would be of no help here. Consider the following Ruby code: 33Actually, the language Eiffel would detect this issue and require you to be specific about which methods you wanted. Joe Bob says “check it out”.
  • 14. module Bomb def explode puts "Bomb explode" end def fuse puts "Bomb fuse" end end module Spouse def explode puts "Spouse explode" end def fuse puts "Spouse fuse" end end class PracticalJoke include Spouse include Bomb end joke = PracticalJoke.new() joke.explode joke.fuse Example 10: Mixin Conflicts In Ruby That will print out "Bomb explode" and "Bomb fuse". In other words, the Bomb's methods overwrite the Spouse methods. The methods of the last module mixed into your class will silently overwrite the previous module's methods. To try and reuse one method from each means that one or both of those need to be handled differently. Additionally, mixins as currently implemented provide no introspective capability letting outside code know that your class can "do" a given mixin. With roles, it's a matter of asking the object's meta class if it performs a given role: if ( $object->meta->does_role($some_role) ) { ... } Example 11: Role Introspection In Perl We could use delegation to solve the "which method can we use" problem caused by multiple inheritance or mixins, but now you have a new problem that you have to set up whatever scaffolding your language requires for delegation. Plus, delegation often means that you send a message to the receiving object and if it needs more information, it canʼt always communicate with the original invocant. With multiple inheritance, you can often ask if a class ISA different class which supports the behavior you need but you get the troubles with inheritance. With both delegation and mixins, you avoid some (not all) of the issues with inheritance by you often can't programmatically determine whether or not a given instance supports the behavior you need. When using roles, you can simply ask a class or instance if it performs that role and
  • 15. take action accordingly. Delegation obscures this. Roles, however, make this issue trivial as Example 11 shows. Here's how you might write the PracticalJoke class in Perl, using roles: { package Bomb; use Moose::Role; sub fuse { print "Bomb exploden" } sub explode { print "Bomb fusen" } } { package Spouse; use Moose::Role; sub fuse { print "Spouse exploden" } sub explode { print "Spouse fusen" } } { package PracticalJoke; use Moose; with qw(Bomb Spouse); } my $joke = PracticalJoke->new(); $joke->explode(); $joke->fuse(); Example 12: The PracticalJoke Class In Perl, Using Roles Except that code won't compile.34 It fails almost immediately with a stacktrace and the error message "Due to a method name conflict in roles 'Bomb' and 'Spouse', the method 'fuse' must be implemented or excluded by 'PracticalJoke'".35 package PracticalJoke; use Moose; with 'Bomb' => { excludes => 'explode' }, 'Spouse' => { excludes => 'fuse' }; Example 13: Excluding Conflicting Role Methods In Perl And the order of consuming those roles is irrelevant, unlike multiple inheritance or mixins.36 34 I'm lying to you. It does compile, but fails at "composition" time. With typical usage of Moose, you frequently won't notice the difference. 35 This is for Moose 0.81 and it should report both the fuse() and explode() methods as being in conflict. Currently it reports one and when you resolve the conflict, it reports the other. A bug report has been filed. 36 That's also not entirely true. I'm apparently a pathological liar. There are things you can do which would make the order of role consumption important, but you generally have to try to achieve this.
  • 16. All of a sudden, things start to look a bit interesting for traits. The order in which you compose roles is irrelevant and you have full control over those methods. In fact, you have more control than what you see here. Still want to sometimes have the Spouse::fuse() method available? package PracticalJoke; use Moose; with 'Bomb' => { excludes => 'explode' }, 'Spouse' => { excludes => 'fuse', alias => { fuse => 'random_fuse' } }; Example 14: Aliasing Conflicting Methods, Rather Than Simply Excluding Them And in your actual code: $joke->fuse(14); # timed fuse # or $joke->random_fuse(); # who knows? Example 15: Calling Both The Needed fuse() method and its aliased version. Whatʼs even more interesting is that if you have conflicting methods, your code wonʼt even compile 37, but will instead fail with a useful error message and stack trace. So not only is it easy to use a role, it's easy to write one, too. And what about the poor programmer who has the multiple inheritance mess? His inheritance hierarchy now looks like this: Figure 8: No More Multiple Inheritance! 37 Actually, it fails at composition time, but we wonʼt go there.
  • 17. And now he can fall safely back to his old “cut-n-paste” coding and update “Country” by merely pasting in the “DoesAuditing” role: package Country; use Moose; extends "My::ResultSource"; with qw(DoesStatic DoesAuditing); Example 16: Fixing the Country Multiple Inheritance Problem If the Country class doesnʼt provide all of the methods that DoesAuditing requires, or if there are method conflicts, he gets a compile-time failure. In practice, weʼve found at the BBC that many times, the code simply “just works” by applying a new role. In fact, these techniques are proving powerful enough that other teams in the BBC are starting to switch to roles with similarly pleasing results. A Real World Example Now those are rather trivial examples, but hereʼs a real example from part of the metadata project I work on. We have one subsystem which returns “resultsets”. These are sets of objects from the database. The old class hierarchy looked like this (again, this is only a small part of the system): Figure 9: Old ResultSet Hierarchy 38 If you look closely, youʼll notice that we donʼt have multiple inheritance here -- though we did in other parts of our system -- but we do have several levels of inheritance, making it difficult at times to know where behavior was implemented. After converting this system to roles, hereʼs our new inheritance hierarchy: Figure 10: New ResultSet Hierarchy39 I realize that this is tough to see, but itʼs completely flat, with only a single abstract base class. If you open up a particular class, you might see something like this:40 38 Full size image: http://www.flickr.com/photos/publius_ovidius/3426561336/sizes/o/ 39 Full size image: http://www.flickr.com/photos/publius_ovidius/3426561340/sizes/o/ 40Actually, that code is a lie. I've cleaned up some of the names to make them a tad more understandable to people unfamiliar with our system. For something closer to the truth, here's an old diagram of the ResultSet hierarchy with roles listed: http://www.flickr.com/photos/publius_ovidius/3425903699/sizes/o/
  • 18. package BBC::Programme::Episode; use Moose; extends 'BBC::ResultSet'; with qw( DoesSearch::Broadcasts DoesSearch::Tags DoesSearch::Titles DoesSearch::Promotions DoesIdentifier::Universal ); Example 17: What A Full List Of Consumed Roles Might Look Like In the process of refactoring, we found that many of those roles were originally behaviors shared across classes which should not implement them. We didnʼt see this at first because we have a large system and these “hidden” behaviors were safely tucked away in base classes. These were serious bugs waiting to happen, but by refactoring into appropriately named roles, any programmer can open up a class and see at a glance which behaviors it really implements and if the roles are well-named, errors become much more apparent. Not only do we get composition safety, we gain comprehension. Conclusion The power of roles -- “traits” if you prefer -- is that they allow classes to resume a more natural role (er, sorry about that) as agents of responsibility. Shared behavior is handled via roles. By making this separation, the BBC has found that we have better compile time safety, fewer “hidden” methods and classes whose behaviors are far more comprehensible, even to newer programmers. Of course, roles can only mitigate risk, not eliminate it. In Perl 5, for example, you donʼt have method signatures, so there can be some ambiguity when a role lists the methods it requires, but in practice, weʼve not even found this to be a problem 41, despite it being a glaring shortcoming of Perl 5.42 We are now developing code faster and with greater understanding. Though the concept of roles is only a few years old, itʼs rapidly proving to be an excellent technology. 41 To be fair, our comprehensive test suite may account for a lot of this. 42Perl 6 has proper method signatures and theyʼre far more robust than most languages and Moose extensions are adding them to Perl 5.
  • 19. Addendum: A Quick Note About Dynamic Languages The debates about static versus dynamic languages rage even stronger than the debates about inheritance. In my opinion, much of the debate is rather silly as many people don't really understand what type systems are all about.43 When I find myself programming in a static language, I often find myself missing many of the features which make dynamic languages so flexible. However, when I'm programming in dynamic languages, I miss features which make static languages a bit "safer" to use. One example: print $customer->as_xml; Example 18: Does The as_xml() method exist? In many dynamic languages, methods like that can be generated at runtime and as a result, you can't know at compile time if this is an error. Consider the Java equivalent: System.out.println(customer.as_xml()); Example 19: Java Compile-Time Safety With a static language, that code won't even compile. You don't worry as much about frantic 3:00 AM phone calls due to that method not existing. Roles can mitigate this by specifying methods they require. package DoesSerialization::YAML; use Moose::Role; requires 'as_xml'; sub as_yaml { my $self = shift; my $xml = $self->as_xml(); # convert XML to YAML and return } Example 20: Using Roles to Bring Some Static Language Safety to Dynamic Languages If the class which consumes the DoesSerialization::YAML role fails to implement as_xml() either directly or via another role which provides this method, you get a composition-time failure44 and your code will not run. Thus, roles can help bring some static safety to dynamic languages. 43 See http://www.pphsg.org/cdsmith/types.html for a nice introduction. 44 As with so many things about technology, there are techniques to make this a runtime failure if you really like being woken up at 3:00 AM.
  • 20. Reviewers Iʼm grateful for the following individuals who contributed thoughts and advice on this paper. Errors, of course, are still mine. They are listed below in no particular order. • Zbigniew Lukasiak • Jerry Sievert • Anton Berezin • Tim Brown • James Laver