Cheap Advice

Clean Code

class MyClass
  def foo; 42; end
end
class MyOtherClass
  def bar; 43; end
end
a = MyClass.new
b = MyOtherClass.new
a.foo
b.bar

Add Logging

class MyClass
  def foo
    $stderr.puts "#{self}#foo => 42"
    42
  end
end
class MyOtherClass
  def bar
    $stderr.puts "#{self}#bar => 43"
    43
  end
end
a = MyClass.new
b = MyOtherClass.new
a.foo
b.bar

Add Security

class MyClass
  def foo
    raise "SecurityError" unless $roles.include?("MyClass#foo")
    $stderr.puts "#{self}#foo => 42"
    42
  end
end
class MyOtherClass
  def bar
    raise "SecurityError" unless $roles.include?("MyClass#bar")
    $stderr.puts "#{self}#bar => 43"
    43
  end
end
a = MyClass.new
b = MyOtherClass.new
$roles = [ "MyClass#foo" ]
a.foo
b.bar

YUCK!

class MyClass
  def foo
    # YUCK!: raise "SecurityError" unless $roles.include?("MyClass#foo")
    # YUCK!: $stderr.puts "#{self}#foo => 42"
    42
  end
end
class MyOtherClass
  def bar
    # YUCK!: raise "SecurityError" unless $roles.include?("MyClass#bar")
    # YUCK!: $stderr.puts "#{self}#bar => 43"
    43
  end
end
a = MyClass.new
b = MyOtherClass.new
$roles = [ "MyClass#foo" ] # ???
a.foo
b.bar

Dumb and Clean, Smart and Dirty

  • Real World is Smart Code.
  • Dumb Code is Clean, Smart Code is Dirty.
  • Get Dirt Out Of Code — (problem-domain vs. solution-domain)
  • Sweep It Somewhere Else — (modularize the smart dirt)
  • Don’t be Dirty All the Time — (dynamic, stateful dirt: logging, debugging, security, etc.)

Separation of Concerns

  • Logging
  • Security

… are not problem-domain issues.

Advice

  • Commonplace in the Lisp world, (esp. Emacs).
  • Advice is function that wraps another function: “before”, “after” or “around”.
  • Advice can be added or removed at run-time.

Advice != Aspects

  • Aspects are woven into code based on complex “codepoint” criteria at build-time (or load-time).
  • Advice is applied to a more well-known constructs: applicable objects: functions, methods, etc.
  • Advice are objects.
  • Advice can be added and removed at run-time.

Cheap Advice

  • … adds dynamic Advice to Ruby methods.
  • … is applied to methods.
  • … are stateful objects.
  • … can be added and removed at run-time.
  • … are configurable.

Logging Advice

# Advice 
trace_advice = CheapAdvice.new(:around) do | ar, body |
  ar.advice[:log] << 
    "#{Time.now.iso8601(6)} " <<
    "#{ar.rcvr.class} #{ar.meth} #{ar.rcvr.object_id}\n"
  body.call
  ar.advice[:log] << 
    "#{Time.now.iso8601(6)} " <<
    "#{ar.rcvr.class} #{ar.meth} #{ar.rcvr.object_id} " <<
    "=> #{ar.result.inspect}\n"    
end
# State attached to trace_advice.
trace_advice[:log] = File.open("trace.log", "a+")

Applying Advice

...
# Activate trace_advice:
trace_advice.advise!(MyClass, :foo)
trace_advice.advise!(MyOtherClass, :bar)
a.foo
b.bar

# Disable trace_advice:
trace_advice.disable!
a.foo
b.bar

Configuration

:advice:
  ~:
    :enabled: false
    :options:
      :trace:
        :logger:
          :name: :default

  'MyClass':
    :advice:  trace

  'MyClass#foo':
    :enabled: false

  'MyClass#bar':
    :enabled: false

Security Advice

You get the idea.

See example/ex01.rb

Questions?

Code!

  • http://github.com/kstephens/cheap_advice
  • gem install cheap_advice