Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The Missing System

The Missing System

Managing coupling and cohesion in our programs is a difficult and never-ending task. Often we struggle to know exactly where to put what code. Over the last few years working with and studying Ruby and DCI I've built up a toolset for managing cohesive set of behaviors for inter-related objects called Surrounded. It helps me maintain related code in the same place and gives me a discussion point for team members implementing features in our systems. We'll walk through the adaptable interface it provides and how it can help your better organize your feature set.

Jim Gay

March 24, 2015
Tweet

More Decks by Jim Gay

Other Decks in Programming

Transcript

  1. –Richard Gabriel Habitability is the characteristic of source code 


    that enables programmers,
 
 to understand its construction and intentions, and to change it comfortably and confidently.
  2. –Richard Gabriel Habitability is the characteristic of source code that

    enables programmers, coders, bug-fixers, and people coming to the code later in its life to understand its construction and intentions, and to change it comfortably and confidently.
  3. class Person! def display_address! "".tap do |string|! if !address.street.nil? ||

    !address.street.empty?! string << address.street! end! ! string << "\n" unless string.empty?! ! if !address.city.nil? address.city.empty? || address.city.nil?! string << address.city! end! ! if address.province || address.postal_code! string << ", "! end! ! if address.province || address.postal_code! if address.province! string << address.province! end! ! string << " "! ! if address.postal_code! string << address.postal_code!
  4. ! ! ! ! ! ! ! ! ! !

    ! ! class Template! def display(address)! region = [address.province, address.postal_code].compact.join(' ')! region = nil if region.empty?! ! city_region = [address.city, region].compact.join(', ')! city_region = nil if city_region.empty?! ! STDOUT.puts [address.street, city_region].compact.join("\n")! end! end
  5. ! ! ! ! ! ! class Address! def display(template)!

    template.display(self)! end! end! ! class Template! def display(address)! region = [address.province, address.postal_code].compact.join(' ')! region = nil if region.empty?! ! city_region = [address.city, region].compact.join(', ')! city_region = nil if city_region.empty?! ! STDOUT.puts [address.street, city_region].compact.join("\n")! end! end
  6. class Person! def display_address(template=Template.new)! address.display(template)! end! end! ! class Address!

    def display(template)! template.display(self)! end! end! ! class Template! def display(address)! region = [address.province, address.postal_code].compact.join(' ')! region = nil if region.empty?! ! city_region = [address.city, region].compact.join(', ')! city_region = nil if city_region.empty?! ! STDOUT.puts [address.street, city_region].compact.join("\n")! end! end
  7. –Christopher Alexander It is simply not possible to fix today

    what the environment should be like [in the future], and then to steer the piecemeal process of development toward that fixed, imaginary world.
  8. –Richard Gabriel ! ! 
 Bugs tell us that we

    are not capable of producing a master plan.
  9. –Richard Gabriel Many a bug is the result of not

    anticipating a particular event or use and is not the result of a mistake—bugs are not always errors. 
 Bugs tell us that we are not capable of producing a master plan.
  10. Goals • Preserve existing object behavior / allow extension •

    Localize use case behavior • Apply behavior only when necessary
  11. Goals • Preserve existing object behavior / allow extension •

    Localize use case behavior • Apply behavior only when necessary
  12. class UserActivation! def initialize(admin, user)! @admin = admin! @user =

    user! end! attr_reader :admin, :user! private :admin, :user! end
  13. class UserActivation! def initialize(admin, user)! @admin = admin.extend(Admin)! @user =

    user! end! attr_reader :admin, :user! private :admin, :user! ! module Admin! #…! end! end
  14. class UserActivation! def initialize(admin, user)! @admin = Admin.new(admin)! @user =

    user! end! attr_reader :admin, :user! private :admin, :user! ! class Admin! #…! end! end class UserActivation! def initialize(admin, user)! @admin = admin.extend(Admin)! @user = user! end! attr_reader :admin, :user! private :admin, :user! ! module Admin! #…! end! end
  15. class UserActivation! def initialize(admin, user)! @admin = admin! @user =

    user! map_roles([:admin, :user].zip([admin, user]))! end! attr_reader :admin, :user! private :admin, :user! ! module Admin! #…! end! end
  16. class UserActivation! def initialize(admin, user)! @admin = admin! @user =

    user! map_roles([[:admin, admin], [:user, user]])! end! attr_reader :admin, :user! private :admin, :user! ! module Admin! #…! end! end
  17. module Surrounded! module Context! module Initializing! def initialize(*setup_args)! attr_reader(*setup_args)! private(*setup_args)!

    mod = Module.new! line = __LINE__; mod.class_eval "! def initialize(#{setup_args.join(',')})! map_roles(#{setup_args.to_s}.zip(! ! ! ! ! [#{setup_args.join(‘,')}]! ! ! ! ))! end! ", __FILE__, line! const_set("ContextInitializer", mod)! include mod! end! end! end! end
  18. module Surrounded! module Context! module Initializing! def initialize(*setup_args)! attr_reader(*setup_args)! private(*setup_args)!

    mod = Module.new! line = __LINE__; mod.class_eval "! def initialize(#{setup_args.join(',')})! map_roles(#{setup_args.to_s}.zip(! ! ! ! ! [#{setup_args.join(‘,')}]! ! ! ! ))! end! ", __FILE__, line! const_set("ContextInitializer", mod)! include mod! end! end! end! end
  19. module Surrounded! module Context! module Initializing! def initialize(*setup_args)! attr_reader(*setup_args)! private(*setup_args)!

    mod = Module.new! line = __LINE__; mod.class_eval "! def initialize(#{setup_args.join(',')})! map_roles(#{setup_args.to_s}.zip(! ! ! ! ! [#{setup_args.join(‘,')}]! ! ! ! ))! end! ", __FILE__, line! const_set("ContextInitializer", mod)! include mod! end! end! end! end
  20. module Surrounded! module Context! module Initializing! def initialize(*setup_args)! attr_reader(*setup_args)! private(*setup_args)!

    mod = Module.new! line = __LINE__; mod.class_eval "! def initialize(#{setup_args.join(',')})! map_roles(#{setup_args.to_s}.zip(! ! ! ! ! [#{setup_args.join(‘,')}]! ! ! ! ))! end! ", __FILE__, line! const_set("ContextInitializer", mod)! include mod! end! end! end! end
  21. class UserActivation! def initialize(admin, user)! @admin = admin.extend(Admin)! @user =

    user! map_roles([[:admin,admin], [:user, user]])! end! attr_reader :admin, :user! private :admin, :user! ! module Admin! #…! end! end
  22. class UserActivation! def initialize(admin, user)! @admin = admin.extend(Admin)! @user =

    user! map_roles([[:admin,admin], [:user, user]])! special_processing(…)! end! attr_reader :admin, :user! private :admin, :user! ! module Admin! #…! end! end
  23. class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! def

    initialize(admin, user)! super! special_processing(…)! end! ! ! module Admin! #…! end! end
  24. class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! def

    initialize(admin, user)! super! special_processing(…)! end! ! ! module Admin! #…! end! end
  25. module Surrounded! module Context! module Initializing! def initialize(*setup_args)! attr_reader(*setup_args)! private(*setup_args)!

    mod = Module.new! line = __LINE__; mod.class_eval "! def initialize(#{setup_args.join(',')})! map_roles(#{setup_args.to_s}.zip(! ! ! ! ! [#{setup_args.join(‘,')}]! ! ! ! ))! end! ", __FILE__, line! const_set("ContextInitializer", mod)! include mod! end! end! end! end
  26. module Surrounded! module Context! module Initializing! def initialize(*setup_args)! attr_reader(*setup_args)! private(*setup_args)!

    mod = Module.new! line = __LINE__; mod.class_eval "! def initialize(#{setup_args.join(',')})! map_roles(#{setup_args.to_s}.zip(! ! ! ! ! [#{setup_args.join(‘,')}]! ! ! ! ))! end! ", __FILE__, line! const_set("ContextInitializer", mod)! include mod! end! end! end! end UserActivation ContextInitializer #initialize #initialize Object Kernel BasicObject
  27. Goals • Preserve existing object behavior / allow extension •

    Localize use case behavior • Apply behavior only when necessary
  28. –Richard Gabriel The primary feature for easy maintenance is locality:

    Locality is that characteristic of source code that enables a programmer to understand that source by looking at only a small portion of it.
  29. class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! !

    ! ! ! ! ! module Admin! #…! end! end class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! ! ! ! ! ! ! class Admin < SimpleDelegator! #…! end! end
  30. class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! !

    ! ! ! ! ! role :admin, :module do! #…! end! end class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! ! ! ! ! ! ! role :admin, :wrapper do! #…! end! end
  31. class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! !

    ! ! ! ! ! module Admin! #…! end! end class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! ! ! ! ! ! ! role :admin, :wrapper do! #…! end! end
  32. class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! !

    ! ! ! ! ! role :admin, :module do! #…! end! end class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! ! ! ! ! ! ! class Admin < ::MyWrapper! #…! end! end
  33. class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! !

    ! ! ! ! ! role :admin do! #…! end! end! ! UserActivation::Admin ! # => NameError: private constant UserActivation::Admin referenced class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! ! ! ! ! ! ! module Admin! #…! end! private_constant :Admin! end
  34. Goals • Preserve existing object behavior / allow extension •

    Localize use case behavior • Apply behavior only when necessary
  35. class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! def

    do_something! # use objects and role behaviors! end! ! ! ! ! ! role :admin do! #…! end! end
  36. class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! def

    do_something! apply_behaviors! # use objects and role behaviors! ensure ! remove_behaviors! end! ! ! role :admin do! #…! end! end
  37. class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! trigger

    :do_something do! # use objects and role behaviors! end! ! ! ! ! ! role :admin do! #…! end! end
  38. class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! trigger

    :do_something do! # use objects and role behaviors! end! ! ! ! ! ! role :admin do! #…! end! end! ! ! ! UserActivation.new(user1, user2).triggers! # => [:do_something]
  39. class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! trigger

    :do_something do! # use objects and role behaviors! end! ! def non_trigger! # do something, roles not applied! end! ! role :admin do! #…! end! end! ! ! ! UserActivation.new(user1, user2).triggers! # => [:do_something]! UserActivation.new(user1, user2).methods! # => [:do_something, :non_trigger, …]
  40. class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! trigger

    :do_something do! admin.perform_action(user)! end! ! ! ! ! ! role :admin do! def perform_action(user)! # …! end! end! end
  41. class UserActivation! extend Surrounded::Context! ! initialize :admin, :user, :notifier! !

    trigger :do_something do! admin.perform_action(user, notifier)! end! ! ! ! ! ! role :admin do! def perform_action(user, notifier)! # …! end! end! end
  42. class UserActivation! extend Surrounded::Context! ! initialize :admin, :user, :notifier, :listener!

    ! trigger :do_something do! admin.perform_action(user, notifier, listener)! end! ! ! ! ! ! role :admin do! def perform_action(user, notifier, listener)! # …! end! end! end
  43. class UserActivation! extend Surrounded::Context! ! initialize :admin, :user, :notifier, :listener!

    ! trigger :do_something do! admin.perform_action! end! ! ! ! ! ! role :admin do! def perform_action! # has access to:! user! notifier! listener! end! end! end
  44. module Surrounded! ! def method_missing(meth, *args, &block)! context.role?(meth){} || super!

    end! ! def respond_to_missing?(meth, include_private=false)! !!context.role?(meth){} || super! end! ! end
  45. module Surrounded! ! def method_missing(meth, *args, &block)! context.role?(meth){} || super!

    end! ! def respond_to_missing?(meth, include_private=false)! !!context.role?(meth){} || super! end! ! end! ! module Surrounded! module Context! module InstanceMethods! def role?(name, &block)! return false unless role_map.role?(name)! accessor = block.binding.eval('self')! role_map.role_player?(accessor) && ! role_map.assigned_player(name)! end! end! end! end
  46. module Surrounded! ! def method_missing(meth, *args, &block)! context.role?(meth){} || super!

    end! ! def respond_to_missing?(meth, include_private=false)! !!context.role?(meth){} || super! end! ! end! ! module Surrounded! module Context! module InstanceMethods! def role?(name, &block)! return false unless role_map.role?(name)! accessor = block.binding.eval('self')! role_map.role_player?(accessor) && ! role_map.assigned_player(name)! end! end! end! end
  47. module Surrounded! ! def method_missing(meth, *args, &block)! context.role?(meth){} || super!

    end! ! def respond_to_missing?(meth, include_private=false)! !!context.role?(meth){} || super! end! ! end! ! module Surrounded! module Context! module InstanceMethods! def role?(name, &block)! return false unless role_map.role?(name)! accessor = block.binding.eval('self')! role_map.role_player?(accessor) && ! role_map.assigned_player(name)! end! end! end! end
  48. module Surrounded! ! def method_missing(meth, *args, &block)! context.role?(meth){} || super!

    end! ! def respond_to_missing?(meth, include_private=false)! !!context.role?(meth){} || super! end! ! end! ! module Surrounded! module Context! module InstanceMethods! def role?(name, &block)! return false unless role_map.role?(name)! accessor = block.binding.eval('self')! role_map.role_player?(accessor) && ! role_map.assigned_player(name)! end! end! end! end
  49. –Richard Gabriel Piecemeal growth is a reality. What gets in

    its way and prevents software habitability is overdesign, overabstraction, and the beautiful, taut monument of software.
  50. – Jim “Cope” Coplien No approach or method can ever

    be perfect, because its designers cannot control the entire world.
  51. Thanks! Jim Gay @saturnflyer ! ! ! ! ! clean-ruby.com

    clean-ruby.com/dsl github.com/saturnflyer/surrounded
  52. –Herb Sutter It is far, far easier to make a

    correct program fast than it is to make a fast program correct.