Composition Of Responsibility vs Interface Implementation

This started out a comment in a Google+ stream, which is in response to the brujahahahahalols that have been going around concerning ActiveRecord, FubuMVC and Rails. I’m not defending any of these posts or perspectives. I have my own opinions on the problems and benefits of the various things mentioned… but I wanted to specifically talk about something that Chad mentions: bloated object interfaces, or too many methods on an object.

It seems to me that claims of object bloat and Separation of Concerns (SoC) / Single Responsibility Principle (SRP) violations for languages like Ruby are often based on the idea of interface-based method dispatch… the idea that an object must implement an interface in order to have the method available. Now I’m not saying that all claims of these violations are from this perspective, by any means. I don’t know if this was Chad’s perspective or not. I can only assume based on how he worded things. And obviously there are some horrible chunks of code out there, in any language, with any type system or method dispatch system, that really do violate these principles.

Still… I wonder how much of the bloat or perception of bloat is based on the wrong perspective…

SoC/SRP: 354 Methods On An Object!

ActiveRecord might truly be a horrible beast with far too many concerns in one given place. I haven’t dug into that source code very much. From what I remember of it, it’s huge and difficult for me to understand (but then, it does a metric-ton-squared of meta-programming, so I guess I’m not surprised that it’s hard for me to understand).

I have dug deep in to Mongoid, though (a MongoDB ODM for ruby), which sits on top of various pieces of ActiveRecord. I’ve submitted a handful of patches for Mongoid and have spent a fair amount of time studying it to learn how it works. On the surface, it also looks like a ton of bloat and SoC/SRP violations. Run “puts my_model.methods.sort”, and you’ll see 354 methods… it makes you wonder…

1.9.2p290 :001 > require 'rubygems'
1.9.2p290 :002 > require 'mongoid'

1.9.2p290 :003 > class Foo
1.9.2p290 :004?>   include Mongoid::Document
1.9.2p290 :005?> end

1.9.2p290 :006 > f = Foo.new
1.9.2p290 :007 > f.methods.count
 => 354 

1.9.2p290 :008 > f.methods.sort
 => [:!, :!=, :!~, :<=>, :==, :===, :=~, :[], :[]=, :__id__, :__send__, :_accessible_attributes, :_accessible_attributes=, :_accessible_attributes?, :_active_authorizer, :_active_authorizer=, :_active_authorizer?, :_children, :_collection, :_collection=, :_create_callbacks, :_create_callbacks=, :_create_callbacks?, :_destroy_callbacks, :_destroy_callbacks=, :_destroy_callbacks?, :_id, :_id=, :_id?, :_id_change, :_id_changed?, :_id_was, :_id_will_change!, :_index, :_index=, :_initialize_callbacks, :_initialize_callbacks=, :_initialize_callbacks?, :_parent, :_parent=, :_protected_attributes, :_protected_attributes=, :_protected_attributes?, :_root, :_save_callbacks, :_save_callbacks=, :_save_callbacks?, :_type, :_type=, :_type?, :_type_change, :_type_changed?, :_type_was, :_type_will_change!, :_update_callbacks, :_update_callbacks=, :_update_callbacks?, :_updates, :_vacant?, :_validate_callbacks, :_validate_callbacks=, :_validate_callbacks?, :_validation_callbacks, :_validation_callbacks=, :_validation_callbacks?, :_validators, :_validators=, :_validators?, :`, :acts_like?, :add_atomic_pull, :add_to_set, :apply_defaults, :as_document, :as_json, :assign_attributes, :associations, :atomic_delete_modifier, :atomic_insert_modifier, :atomic_path, :atomic_position, :atomic_pulls, :atomic_pushes, :atomic_selector, :atomic_sets, :atomic_unsets, :atomic_updates, :attribute_method?, :attribute_method_matchers, :attribute_method_matchers?, :attribute_present?, :attributes, :attributes=, :becomes, :begin_validate, :bit, :blank?, :breakpoint, :build, :cached, :cached=, :cached?, :capture, :cascade!, :cascades, :cascades=, :cascades?, :changed, :changed?, :changed_attributes, :changes, :class, :class_eval, :clone, :collection, :collection_name, :collection_name=, :create_relation, :cyclic, :cyclic=, :cyclic?, :db, :debugger, :default_scoping, :default_scoping=, :default_scoping?, :defaults, :define_singleton_method, :delayed_atomic_pulls, :delayed_atomic_sets, :delete, :deleted?, :destroy, :destroyed=, :destroyed?, :display, :do_or_do_not, :dup, :duplicable?, :embedded=, :embedded?, :embedded_many?, :embedded_one?, :enable_warnings, :enum_for, :eql?, :equal?, :errors, :exit_validate, :extend, :fields, :flagged_for_destroy=, :flagged_for_destroy?, :freeze, :from_json, :from_xml, :frozen?, :has_attribute?, :hash, :hereditary?, :html_safe?, :id, :id=, :identifier, :identify, :in?, :inc, :include_root_in_json, :include_root_in_json=, :include_root_in_json?, :index_options, :index_options=, :initialize_clone, :initialize_copy, :initialize_dup, :insert, :inspect, :instance_eval, :instance_exec, :instance_of?, :instance_values, :instance_variable_defined?, :instance_variable_get, :instance_variable_names, :instance_variable_set, :instance_variables, :invalid?, :is_a?, :ivar, :key_formatter, :key_formatter=, :kind_of?, :mass_assignment_authorizer, :matches?, :metadata, :metadata=, :method, :method_missing, :methods, :model_name, :move_changes, :nested_attributes, :nested_attributes=, :nested_attributes?, :new?, :new_record, :new_record=, :new_record?, :nil?, :object_id, :parentize, :pending_attribute?, :pending_nested, :pending_relations, :persisted?, :polymorphic, :polymorphic=, :polymorphic?, :pop, :presence, :present?, :previous_changes, :primary_key, :primary_key=, :private_methods, :process, :process_attribute, :process_nested, :process_pending, :process_relations, :protected_methods, :psych_to_yaml, :psych_y, :public_method, :public_methods, :public_send, :pull, :pull_all, :push, :push_all, :pushable?, :quietly, :raw_attributes, :read_attribute, :read_attribute_for_validation, :referenced_many?, :referenced_one?, :reflect_on_all_associations, :reflect_on_association, :relation_exists?, :relations, :relations=, :relations?, :reload, :reload_relations, :remove, :remove_attribute, :remove_change, :remove_child, :remove_inverse_keys, :remove_ivar, :rename, :require_library_or_gem, :require_library_or_gem_with_deprecation, :require_library_or_gem_without_deprecation, :reset__id!, :reset__type!, :reset_persisted_children, :respond_to?, :respond_to_missing?, :respond_to_without_attributes?, :run_callbacks, :run_validations!, :safely, :sanitize_for_mass_assignment, :save, :save!, :scopes, :scopes=, :scopes?, :send, :serializable_hash, :set, :set_relation, :settable?, :setters, :shard_key_fields, :shard_key_fields=, :shard_key_selector, :silence, :silence_stderr, :silence_stream, :silence_warnings, :singleton_class, :singleton_methods, :substitutable, :suppress, :syncable?, :synced, :synced?, :taint, :tainted?, :tap, :timeless, :timestamping?, :to_a, :to_enum, :to_json, :to_key, :to_model, :to_param, :to_query, :to_s, :to_xml, :to_yaml, :to_yaml_properties, :trust, :try, :typed_value_for, :unsafely, :unset, :untaint, :untrust, :untrusted?, :update, :update_attribute, :update_attributes, :update_attributes!, :update_inverse_keys, :updateable?, :upsert, :using_object_ids, :using_object_ids=, :using_object_ids?, :valid?, :validated?, :validates_acceptance_of, :validates_confirmation_of, :validates_exclusion_of, :validates_format_of, :validates_inclusion_of, :validates_length_of, :validates_numericality_of, :validates_presence_of, :validates_size_of, :validates_with, :validation_context, :validation_context=, :with_options, :with_warnings, :write_attribute, :write_attributes, :y, :you_must]

But when I look at the source for Mongoid, it’s one of the most beautiful sets of code that i’ve seen in Ruby. From my perspective, it has a very clean separation of concerns and follows many other good OO principles to the core (at least until it has to interact w/ activerecord).

The interface bloat that we see on an object like the one above comes as a result of a few different things: Ruby base objects, and applying the many different Mongoid mixins to a Ruby model, via `include Mongoid::Document`.

Perspective: Interface-based vs Message-based

The problem might not be ActiveRecord, or Mongoid, or any other “bloated framework that violates …” and how the objects that use these frameworks look when we list the interface of a model. The problem might really be that we are listing the interface to the model as if it were the truth of this object’s implementation. The problem might just be our perspective.

Does the object above really implement all of these methods? Or has it been composed from many different mixins, with many different hats to wear in different scenarios (Udi Dahan’s “role specific interfaces”, or dependency inversion in general)?

Watch Where You’re Pointing That Perspective!

If we look at a message-based method dispatch in the same light and perspective as we do a interface-based dispatch, things look bad. If we look at the message-based, first-class-mixin system as a series of responsibilities, though, with each responsibility having it’s own protocol definition and each responsibility and protocol captured into an object that can be composed into a larger piece (again, mixins), things look much better.

Yes, my “Foo” object in the above example still has 354 methods on it. But how many of these do you really care about for a given scenario? What role do you expect this object to play, when? Ruby, Rails, ActiveRecord, Mongoid… Python, JavaScript, and the rest of the dynamic ecosystem give us a lot of power and flexbility. (Insert abused “Uncle Ben” quote from Spiderman… with great power come blah blah blah blah…)


Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Derick Bailey

Derick Bailey is an entrepreneur, problem solver (and creator? :P ), software developer, screecaster, writer, blogger, speaker and technology leader in central Texas (north of Austin). He runs SignalLeaf.com - the amazingly awesome podcast audio hosting service that everyone should be using, and WatchMeCode.net where he throws down the JavaScript gauntlets to get you up to speed. He has been a professional software developer since the late 90's, and has been writing code since the late 80's. Find me on twitter: @derickbailey, @mutedsolutions, @backbonejsclass Find me on the web: SignalLeaf, WatchMeCode, Kendo UI blog, MarionetteJS, My Github profile, On Google+.
This entry was posted in .NET, C#, Mongoid, Philosophy of Software, Principles and Patterns, Rails, Ruby. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://twitter.com/colin_jack Colin Jack

    “Yes, my “Foo” object in the above example still has 354 methods on it.
    But how many of these do you really care about for a given scenario?”

    I get what your saying but when faced with an object with 354 methods don’t you end up being a bit lost?

    To me thats where the interface segregation principle has value, it makes it much easier (as a developer) to reason about a particular object. Mixins help with that to some extent, its not like you look at your type and see 354 methods, but the roles you mention are pretty implicit when your looking at a particular object (or argument).

    I am looking forward to giving Dart a proper run for its money because to me its optional typing could have real possibilities, for example in the case of your Foo maybe carving off some of that behavior into an interface so that when viewing the code one of the responsibilities is made more concrete.

    • http://mutedsolutions.com Derick Bailey

      354 methods would be horrible to sift through if i ever looked at that list with intellisense or via command line as shown above. i very rarely do this, though. i use vim and i never use the auto-complete (intellisense-like) feature. 

      the only time i ever list the methods on an object are when i think there should be a method with a specific name… then i’ll `.methods.grep(/name/)` to find it. other than that, i read documentation for each of the separate responsibilities, and I look at the code to find the responsibilities and the protocols (API) that they implement.

      as for ISP… i’m not sure ISP as it was originally defined applies to ruby very well. Jim Weirich discussed SOLID ruby back in 2009: http://confreaks.net/videos/185-rubyconf2009-solid-ruby note in the ISP section (at 40:10), he all but says that ISP doesn’t apply because a language like ruby requires a far deeper implementation of DIP – dependency inversion – via responsibilities and protocols (APIs). he specifically says “depend upon narrow protocols”, but in his description of that he talks about dependency inversion. 

      with such a deep need for dependency inversion, the ability to easy mix a dependency’s protocol into an object, and the nature of ruby being message-based dispatch instead of interface-based, I have a hard time seeing classic ISP techniques as a concern. ISP itself still applies… but i don’t think it’s a first-class concern in Ruby. i think it’s a side effect of other principles, like DIP, SRP, etc. 

      when looking at the code and documentation for my Foo object, I should see the API that directly relates to Foo and it should be simple and ISP conforming. When I list the methods for a Foo instance at runtime, though, that’s a different story. the ability to compose objects at run time means we can’t assume the same perspective on what these principles look like at runtime.

      • http://twitter.com/colin_jack Colin Jack

        “When I list the methods for a Foo instance at runtime, though, that’s a
        different story. the ability to compose objects at run time means we
        can’t assume the same perspective on what these principles look like at
        runtime.”

        Definitely, I maybe wasn’t clear. Lets say I’m coming to a new method and its taking in an argument. I just want a quick summary of what I can do with that argument. You’ve answered how to work it out, and I do the same, look at code or documentation (or tests).

        But just compare static to dynamic here. Static if I’ve used ISP I know exactly what I can do with the argument, the protocol is explicit. Dynamic I obviously can’t be 100% sure (metaprogramming) but even ignoring that its a bit more implicit. Thats good if I get to a type somehow (say Foo) but less good if Foo has lots of mixins, suddenly I have a lot of choices (lots of responsibilities in one place). I’ve got a bit of work to find out which of them applies in this context.

        This is not an argument against dynamic languages at all, but I do worry that if you have such a wide interface combined with a dynamic language then you could be making it hard for the maintainers of your code.

        Caveat is this hasn’t been a big problem for me, but then I haven’t actually worked on large Ruby codebases and I have found it to be a little bit of an issue when working with some open source Ruby code.

  • Anonymous

    I would personally like to hear more about:

     ”If we look at a message-based method dispatch in the same light and perspective as we do a interface-based dispatch, things look bad. If we look at the message-based, first-class-mixin system as a series of responsibilities, though, with each responsibility having it’s own protocol definition and each responsibility and protocol captured into an object that can be composed into a larger piece (again, mixins), things look much better.”

    How do you look at and understand highly mixed code, protocol definition at the ruby method / message level – etc.

    • http://mutedsolutions.com Derick Bailey

      In general: I read the code and the documentation for the code. 

      The problem with heavy use of mixins, from the perspective of what functions are for what responsibility, is generally at runtime. doing a list of methods like i did is a runtime call, for example. but the actual code for Mongoid, and the documentation for it, are not a giant blob like that. 

      If you look at Ruby’s Array class documentation (which is admittedly a poor example because of the number of methods on it) you can see that it includes the Enumerable module. 

      http://ruby-doc.org/core-1.9.3/Array.html

      Assuming I know what an Enumerable is, in Ruby, and have a fair idea of what responsibilities that module encompasses, I now know one of the responsibilities that is mixed in to an Array. 

      Knowing that an Enumerable exists allows me to write code to that API/Protocol/whatever you want to call it. When I’m writing my own code or reading documentation for other people’s code and I see someone say that a parameter should be an Enumerable, I have knowledge of what that is because the documentation has lead me to it.

  • Anonymous

    links would work too. :)

  • http://murrayon.net/ Mike Murray

    So I can understand the argument to focus on roles/responsibilities instead of 1-to-1 interfaces. But at what point are you not following SRP by having your objects take on too many roles?

    • http://mutedsolutions.com Derick Bailey

      Having an object take on multiple roles is not an SRP violation in itself. It’s the single Responsibility principles, not single Role principle.

      SRP says a class (or object in non-classy languages) should have one reason to change. We change a class/object by changing it’s code. If I define 5 or 10 fields for my Foo class in the above example, and use it in a simple forms over data app, there’s likely no violation of SRP happening because the responsibility of the class is moving data between the form/user input and the database.

      That doesn’t mean it’s never an SRP violation, though. SRP is very contextually sensitive. An SRP violation in one code base may be a perfectly fine chunk of code with no SRP violation in another context.

      If I’m building a highly complex business application with tendencies toward domain modeling and other domain-driven-design concepts, then including Mongoid::Document in a domain model is likely an SRP violation… not because there are too many methods on the runtime object, though. But because I have chosen to mix the responsibility of data persistence with the responsibility of the domain model.

      For more of my opinion on SRP, see my SOLID article here: http://www.code-magazine.com/article.aspx?quickid=1001061&page=4

      • http://murrayon.net/ Mike Murray

        Yeah, ok…not much I disagree with there…

        I guess I still don’t see the distinction between roles and responsibilities as you are seeing it. Can you elaborate on situations where an object would keep to a single responsibility but take on multiple roles?

        I’m also sensing you making a separation between the name of the principle and its definition? Are you saying that taking the name of the principle (“single responsibility”) too literally is an unnecessary restriction, and that following the definition of the principle (“one reason to change”) is sufficient? Isn’t having more than one role having more than one reason to change?

        Interested to hear your thoughts. Thanks!

        • http://mutedsolutions.com Derick Bailey

          names are good because they give us shared meaning. but it’s the meaning, and subtleties of the meaning that are really important.

          “responsibility” is very subjective and too open to the overly analytical left-brain developers that want to over-engineer everything. “I’m building a car? Ok, it needs an engine. So I need pistons, crank shaft, oil pan / pump, spark plugs, block, ….” vs “I’m building a car that accelerates and breaks? Ok. Here’s the car w/ an accelerate method, and a break method.”

          “one reason to change” forces us to ask questions about the reasons for change… what are the valid reasons in this case. what are the possible vectors of change that we know of in this application? Am I building a vehicle repair system that needs to track every single part? Or am I building a child’s entry-level video game with two buttons: accelerate and break?

          … fwiw: i keep waiting for someone to come along and show me where my opinions and ideas have gone off the deep end. these are my opinions and ideas after having dealt with this stuff for a bunch of years… but i still don’t think i’m an “expert” with this stuff by any means. i keep going back to the examples used in the “Agile principles, patterns and practices” book, and scratching my head at how they came up with some of those examples and great solutions. :P