Share
Embed
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
Transcript
81 slides
-
–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. -
–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. -
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! -
! ! ! ! ! ! ! ! ! !
! ! 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 -
! ! ! ! ! ! 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 -
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 -
–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. -
–Richard Gabriel ! ! Bugs tell us that we
are not capable of producing a master plan. -
–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. -
Goals • Preserve existing object behavior / allow extension •
Localize use case behavior • Apply behavior only when necessary -
Goals • Preserve existing object behavior / allow extension •
Localize use case behavior • Apply behavior only when necessary -
class UserActivation! def initialize(admin, user)! @admin = admin! @user =
user! end! attr_reader :admin, :user! private :admin, :user! 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 -
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 -
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 -
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 -
class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! !
! ! ! module Admin! #…! end! end -
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 -
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 -
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 -
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 -
class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! !
! ! ! module Admin! #…! end! end -
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 -
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 -
class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! def
initialize(admin, user)! super! special_processing(…)! end! ! ! module Admin! #…! end! end -
class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! def
initialize(admin, user)! super! special_processing(…)! end! ! ! module Admin! #…! end! end -
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 -
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 -
class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! !
! ! ! ! ! module Admin! #…! end! end -
Goals • Preserve existing object behavior / allow extension •
Localize use case behavior • Apply behavior only when necessary -
–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. -
class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! !
! ! ! ! ! module Admin! #…! end! end -
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 -
class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! !
! ! ! ! ! role :admin do! #…! end! end -
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 -
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 -
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 -
class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! !
! ! ! ! ! role :admin do! #…! end! end -
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 -
Goals • Preserve existing object behavior / allow extension •
Localize use case behavior • Apply behavior only when necessary -
class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! def
do_something! # use objects and role behaviors! end! ! ! ! ! ! role :admin do! #…! end! end -
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 -
class UserActivation! extend Surrounded::Context! ! initialize :admin, :user! ! trigger
:do_something do! # use objects and role behaviors! end! ! ! ! ! ! role :admin do! #…! end! end -
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] -
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, …] -
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 -
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 -
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 -
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 -
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! ! 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 -
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 -
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 -
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 -
–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. -
– Jim “Cope” Coplien No approach or method can ever
be perfect, because its designers cannot control the entire world. -
Thanks! Jim Gay @saturnflyer ! ! ! ! ! clean-ruby.com
clean-ruby.com/dsl github.com/saturnflyer/surrounded -
–Herb Sutter It is far, far easier to make a
correct program fast than it is to make a fast program correct.