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.

09477c358c5897d44121a248326e16d7?s=128

Jim Gay

March 24, 2015
Tweet

Transcript

  1. The Missing System Or Preparing for Piecemeal Growth

  2. Jim Gay @saturnflyer ! clean-ruby.com clean-ruby.com/dsl

  3. Jim Gay @saturnflyer ! github.com/saturnflyer/surrounded

  4. None
  5. None
  6. None
  7. Habitability & Barriers

  8. Patterns of Software

  9. –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.
  10. –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.
  11. 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!
  12. ! ! ! ! ! ! ! ! ! !

    ! ! 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
  13. ! ! ! ! ! ! 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
  14. 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
  15. person.display_address(template)! ! address.display(template)! ! template.display(address)! ! STDOUT.puts “…”! Tell Don’t

    Ask
  16. –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.
  17. –Richard Gabriel ! ! 
 Bugs tell us that we

    are not capable of producing a master plan.
  18. –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.
  19. github.com/saturnflyer/surrounded

  20. Goals • Preserve existing object behavior / allow extension •

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

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

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

    user! end! attr_reader :admin, :user! private :admin, :user! ! module Admin! #…! end! end
  24. 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
  25. 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
  26. 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
  27. class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! !

    ! ! ! module Admin! #…! end! end
  28. 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
  29. 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
  30. 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
  31. 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
  32. class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! !

    ! ! ! module Admin! #…! end! end
  33. 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
  34. 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
  35. class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! def

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

    initialize(admin, user)! super! special_processing(…)! end! ! ! module Admin! #…! end! end
  37. 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
  38. 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
  39. class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! !

    ! ! ! ! ! module Admin! #…! end! end
  40. Goals • Preserve existing object behavior / allow extension •

    Localize use case behavior • Apply behavior only when necessary
  41. Coupling & Cohesion

  42. –Richard Gabriel The primary feature for easy maintenance is locality

    !
  43. –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.
  44. None
  45. None
  46. None
  47. None
  48. None
  49. class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! !

    ! ! ! ! ! module Admin! #…! end! end
  50. 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
  51. class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! !

    ! ! ! ! ! role :admin do! #…! end! end
  52. 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
  53. 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
  54. 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
  55. class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! !

    ! ! ! ! ! role :admin do! #…! end! end
  56. 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
  57. None
  58. Goals • Preserve existing object behavior / allow extension •

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

    do_something! # use objects and role behaviors! end! ! ! ! ! ! role :admin do! #…! end! end
  60. 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
  61. class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! trigger

    :do_something do! # use objects and role behaviors! end! ! ! ! ! ! role :admin do! #…! end! end
  62. 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]
  63. 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, …]
  64. None
  65. 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
  66. 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
  67. 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
  68. 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
  69. 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
  70. 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
  71. 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
  72. 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
  73. 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
  74. Control & 
 Plan Locality

  75. –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.
  76. – Jim “Cope” Coplien No approach or method can ever

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

    clean-ruby.com/dsl github.com/saturnflyer/surrounded
  78. What is missing?

  79. What is missing? Concurrency Contracts

  80. –Herb Sutter It is far, far easier to make a

    correct program fast than it is to make a fast program correct.
  81. What system is missing?