Wednesday, May 31, 2006
Use class methods for stubs
Since I believe in unit testing one concrete class at a time I frequently use stubs in my tests. These stubs always return the same value and are unimportant to the current test.
For example, assume you have an Employee class that contains check_email behavior.
For example, assume you have an Employee class that contains check_email behavior.
class EmployeeIn the above example Employee depends on cell_phone and laptop. When testing check_email you should have (at least) two tests, one for the case where @laptop.working? is true and one where it returns false. The second scenario (where @laptop.working? is false) is perfect for stub usage.
def initialize(cell_phone, laptop)
@cell_phone = cell_phone
@laptop = laptop
end
def check_email(user, password)
if @laptop.working?
@laptop.check_email(user, password)
else
@cell_phone.check_email(user, password)
end
end
end
class EmployeeTest < Test::Unit::TestCaseThe above test is correct; however, it can be slightly changed. Because of duck typing and the fact that stubs should always return a constant value it is possible to use class methods instead of instance methods. This relieves you from needing a new instance for each stub.
class StubLaptop
def working?
false
end
end
...
def test_when_laptop_is_not_working_cell_phone_is_used
# code for setting up a mock for cell_phone
employee = Employee.new(mock_cell_phone, StubLaptop.new)
employee.check_email('jay', 'password')
# code to verify mock
end
end
class EmployeeTest < Test::Unit::TestCase
class StubLaptop
def self.working?
false
end
end
...
def test_when_laptop_is_not_working_cell_phone_is_used
# code for setting up a mock for cell_phone
employee = Employee.new(mock_cell_phone, StubLaptop)
employee.check_email('jay', 'password')
# code to verify mock
end
end
Friday, May 26, 2006
Ruby Stunt Double
In the DSL focused system I'm currently working on we provide what I previously detailed as level 2 syntax checking. The DSL that we've developed is an internal DSL and requires the business user to call methods on objects (not that they have any idea what that means). The system depends on syntax similar to:
The solution involves executing your DSL code in the syntax checking context and having key methods return Stunt Double instances. In the above code
Of course, the majority of the magic is in the StuntDouble class.
open.a.table.today.at 2The users of the system requested some documentation to help them in building their business rules. Obviously we could have written or generated some documentation, but I thought displaying valid options within the system was a better choice.
The solution involves executing your DSL code in the syntax checking context and having key methods return Stunt Double instances. In the above code
open is clearly a DSL keyword. In the syntax checking context open would be defined as:def open
OpenStuntDouble.new(@messenger)
end
OpenStuntDouble is a class that inherits from StuntDouble and responds to all public instance methods of Open.class OpenStuntDoubleAny method that is executed on OpenStuntDouble will return a new instance of AStuntDouble. However, any method call that results in method_missing being executed will be reported as an invalid method call. Each invalid method call will also report the full list of available methods. Using this error you can provide your users with feedback on valid syntax.
def initialize(block)
super("open", &block)
end
stand_in_for(ThoughtWorks::Open) { AStuntDouble.new(@messenger) }
end
Of course, the majority of the magic is in the StuntDouble class.
class StuntDoubleOf course, no code is complete without tests.
alias __methods__ methods
alias __class__ class
def initialize(name, &block)
raise "a messenger proc is required for stunt double" unless block_given?
@messenger = block
@name = name
end
def self.stand_in(*array)
block = block_given? ? Proc.new : Proc.new {}
array.each do |element|
define_method(element.to_sym, &block)
end
end
def self.stand_in_for(mod)
block = block_given? ? Proc.new : Proc.new {}
stand_in(*mod.public_instance_methods, &block)
end
def method_missing(sym, *args)
@notify_block.call "#{does_not_support(sym)} #{does_support}"
end
private
attr_reader :name
def does_support
return "Supported: #{valid_methods}." if valid_methods != ''
"Nothing is supported."
end
def does_not_support(sym)
"#{name} does not support #{sym.to_s}."
end
def valid_methods
result = self.__class__.public_instance_methods.sort.select do |operation|
!@@excluded.include? operation
end
result.join(', ')
end
@@excluded = public_instance_methods.select { |method| method =~ /^__.+/ }
@@excluded += %w(to_s inspect method_missing instance_eval)
instance_methods.each { |m| undef_method m unless @@excluded.include? m }
end
require File.expand_path(File.dirname(__FILE__) + "/../test_helper")The end result is user friendly errors that can be displayed directly to your users.
class StuntDoubleTest < Test::Unit::TestCase
class Inner < BlankSlate
def included_from_elsewhere
end
end
class FooStuntDouble < StuntDouble
end
class BarStuntDouble < StuntDouble
end
def setup_foo
FooStuntDouble.stand_in(:bar) { }
FooStuntDouble.stand_in_for(Inner)
end
def test_name_is_returned_from_message
setup_foo
stunt = FooStuntDouble.new("foo") do |error|
assert_equal 'foo does not support zeb. Supported: bar, included_from_elsewhere.', error
return
end
stunt.zeb
fail 'assertion skipped'
end
def test_name_is_returned_from_message_but_nothing_supported
stunt = BarStuntDouble.new("bar") do |error|
assert_equal 'bar does not support zeb. Nothing is supported.', error
return
end
stunt.zeb
fail 'assertion skipped'
end
def test_methods_are_added_from_array
setup_foo
stunt = FooStuntDouble.new("foo") do |error|
fail "error"
end
stunt.bar
end
def test_methods_are_added_from_class
setup_foo
stunt = FooStuntDouble.new("foo") do |error|
fail "error"
end
stunt.included_from_elsewhere
end
end
open does not support the. Supported: a.An advantage to this form of syntax help is that as the system evolves so will the help without any additional effort.
Thursday, May 25, 2006
Practical Ruby
Pair Programming Anti-Patterns
Pair programming is hard. Previous to working at ThoughtWorks I had a manager who requested that the team do pair programming. The idea failed miserably, but it wasn't pair programming that was flawed, it was our approach. Below is a short list of the mistakes I've witnessed, but it's by no means a complete list.
- People who don't care. This is the biggest pair programming killer. If your employees are only there for the paycheck they will make pair programming fail. Getting two employees on the same schedule is hard. Once two employees get on the same schedule one will daydream while the other works. This is very boring, but to people who don't care, it's better than working.
- Strong code ownership. It's often very hard to motivate people to care about code they aren't responsible for. For more info on code ownership see Martin's bliki entry.
- Inconsistent workstation setup. People like to work in their own environment. Unfortunately, when pairing you are unlikely to find two people who agree on what an environment should be. The solution is to provide pairing stations with standard setups. Instead of trying to learn multiple environments, the team can work on one shared setup.
- Small work area. People like to be comfortable. Putting two chairs in one cube where one person's view is obstructed is very unlikely to produce good results. On the ThoughtWorks projects I've been staffed on we always have a project room that has enough room to sit side by side, two monitors that mirror the desktop, two mice, and two keyboards. This allows either person to take control when necessary.
Labels: pair programming
Wednesday, May 24, 2006
Ruby extend and include
Module.include(module, ..) is commonly used to mix modules into classes and other modules. When a module is included the constants, methods, and module variables of the module are added to the including module or class instances.
Similarly, Object.extend(module, ..) adds the instance methods from each module given as a parameter. However,
For another example of correct
module ActionsThe
def left(steps)
Movement.new(self, steps)
end
end
Actions module can be included to allow a class to generate movements.class CarAfter
include Actions
end
Actions is included left can be executed by any instance of Car.car = Car.new
movement = car.left
Similarly, Object.extend(module, ..) adds the instance methods from each module given as a parameter. However,
extend adds the methods to one instance, not to all instances.car = Car.newThe above code gives car the same behavior as include would, except only the instance that called extend would have this new behavior. Therefore, the above code is valid, but this code would result in an error:
car.extend Actions
movement = car.left
car = Car.new
car.extend Actions
car2 = Car.new
movement = car2.left #calling 'left' here is invalid because car2 does not extend Actions
extend is commonly used to mix instance methods of a module into a class.module AttributeInitializerMany DSL style class methods are added by extending a module instead of including it.
def attr_init(name, klass)
eval "define_method(name) { @#{name} ||= klass.new }"
end
end
class Statement
extend AttributeInitializer
attr_init :charges, Array
end
For another example of correct
extend usage see Forwardable from the standard library.
Tuesday, May 23, 2006
Law of Demeter and Forwardable
While developing software I often see Law of Demeter violations. These days I seem to see more of this, probably because Ruby on Rails allows you to easily navigate between objects based on table relationships. For example, if you are working on a partial that displays a charge from a credit card statement it wouldn't be surprising to see:
Luckily, Forwardable is included in the standard library. Forwardable allows you delegate method calls to an object, on a method by method basis. Using Forwardable the above code becomes:
charge.statement.customer.nameThe above code could be used to display the customer's name in the partial. Unfortunately, it assumes
statement will have a customer and customer will have a name. This is easily fixed by changing charge to only talk to it's friends.charge.customer_nameThe simple fix to support this is to define
customer_name in charge, and customer_name in statementclass ChargeThis change is simple enough; however, as the list of methods that require delegation grows your class can become littered with delegation code.
def customer_name
statement.customer_name
end
end
class Statement
def customer_name
customer.name
end
end
Luckily, Forwardable is included in the standard library. Forwardable allows you delegate method calls to an object, on a method by method basis. Using Forwardable the above code becomes:
class ChargeForwardable becomes even more valuable when you need to delegate several methods
extend Forwardable
def_delegators :statement, :customer_name
end
class Statement
extend Forwardable
def_delegator :customer, :name, :customer_name
end
def_delegators :amount_info, :units, :fractions, :currencyFor more info on Law of Demeter and it's advantages check out the definition on Wikipedia. For more info on Forwardable check out the documentation on Ruby-Doc.
Friday, May 19, 2006
Syntax Checking an internal DSL
"What about syntax checking?" is the most common question I receive concerning Domain Specific Languages. In my experience it depends on what level of freedom you want to give your users.
The simplest form of syntax checking I usually see is ruby syntax verification:
Verifying method calls and parameters is the next common level of syntax checking. Verification at this level can be achieved by evaluating the script in a syntax checking context.
In my previous example of evaluating a script in various contexts I used a script that represented some of the business rules of a poker room. Building from that example I have a similar script that clearly contains errors.
The next common level of syntax checking I have encountered is using regular expressions to validate the structure of the script. This is the most extreme in my opinion because it either requires very complex regular expressions, or it limits the user from taking advantage of the underlying language (Ruby in my examples). I won't go into much detail on this subject because I haven't had the need for it personally. However, it is an option to be aware of.
The simplest form of syntax checking I usually see is ruby syntax verification:
def valid_ruby?(text)In the above code ReceiveAny relies on method missing magic to allow any method call to work.
begin
ReceiveAny.new.instance_eval(text)
true
rescue SyntaxError => syn_err
yield "error on line #{extract_line_number(syn_err.message)}" if block_given?
false
end
end
Verifying method calls and parameters is the next common level of syntax checking. Verification at this level can be achieved by evaluating the script in a syntax checking context.
In my previous example of evaluating a script in various contexts I used a script that represented some of the business rules of a poker room. Building from that example I have a similar script that clearly contains errors.
if the '$1-$2 No Limit' list is more than a then notify the floor to openWhen syntax checking you could validate every method call; however, (especially if you are using bubble methods) you will more likely only need to validate key methods. For example you would want to validate that the
if the '$1-$2 No Limit' list is more than 1 then notify the floor to opeeen
if the '$1-$2 No Limit' list is more than z then ntify the floor to open
if the '$1-$2 No Limit' list is more than 1 then notify the floor to open
more method was given a numeric value.def more(number)After validating methods and parameters, the last method to execute should return all reported errors.
@errors << "more than should be followed by a number" unless number.kind_of?(Numeric)
end
def notify(arg)However, the last method executed could also be an invalid method (see line 2 of the sample invalid script). Because of this,
@errors.join(', ')
end
method_missing should also return the errors.def method_missing(sym, *args)In the example each line is executed individually. Because of this the returned errors will be specific to only one line.
if sym.to_s =~ /^dollar\d+dashdollar\d+space(Nospace)*Limit/
return true
end
@errors << "#{sym.to_s} is an invalid keyword"
@errors.join(', ')
end
rules.each_with_index do |rule, index|A benefit to executing each line individually is that errors can be reported with their associated line numbers.
result = self.new.instance_eval(rule)
yield result, index if block_given? && result.any?
end
Syntax Check:Sample code for the SyntaxCheckingContext is available here.
errors on line 0, (a is an invalid keyword, more than should be followed by a number)
errors on line 1, (opeeen is an invalid keyword)
errors on line 2, (z is an invalid keyword, more than should be followed by a number, ntify is an invalid keyword)
The next common level of syntax checking I have encountered is using regular expressions to validate the structure of the script. This is the most extreme in my opinion because it either requires very complex regular expressions, or it limits the user from taking advantage of the underlying language (Ruby in my examples). I won't go into much detail on this subject because I haven't had the need for it personally. However, it is an option to be aware of.
Hiding the Parenthesize Warning in Ruby
If you execute the sample code for the Executing an internal DSL in multiple contexts entry you will see results similar to this:
The code required to silence warnings can be found in the Rails framework (activesupport/lib/active_support/core_ext/kernel/reporting).
The results are much quieter after silencing the warnings
Context One:The code was just sample code so I wasn't very concerned with hiding the warnings; however, since several people asked how to do it I thought I'd do a quick follow up entry.
(eval):1: warning: parenthesize argument(s) for future version
(eval):1: warning: parenthesize argument(s) for future version
(eval):1: warning: parenthesize argument(s) for future version
(eval):1: warning: parenthesize argument(s) for future version
(eval):1: warning: parenthesize argument(s) for future version
(eval):1: warning: parenthesize argument(s) for future version
(eval):1: warning: parenthesize argument(s) for future version
(eval):1: warning: parenthesize argument(s) for future version
$5-$10 Limit
action open
position floor
...
The code required to silence warnings can be found in the Rails framework (activesupport/lib/active_support/core_ext/kernel/reporting).
module KernelAfter adding the above code to your codebase (or, if you are writing a Rails app it's already included) you can execute your code as a block to silence_warnings and no warnings will be shown.
def silence_warnings
old_verbose, $VERBOSE = $VERBOSE, nil
yield
ensure
$VERBOSE = old_verbose
end
end
silence_warnings do
puts "\nContext One:"
ContextOne.execute(script) do |notification|
Broadcast.notify(*notification)
end
puts "\nContext Two:"
ContextTwo.execute(script) do |stakes|
puts ContextTwo.sym_to_stakes(stakes)
end
puts "\nContext Three"
ContextThree.execute(script) do |positions|
puts positions
end
end
The results are much quieter after silencing the warnings
Context One:Updated Sample Code
$5-$10 Limit
action open
position floor
$1-$2 No Limit
action open
position floor
$5-$10 Limit
action announce
position brush
$1-$2 No Limit
action announce
position brush
Context Two:
$5-$10 Limit
$1-$2 No Limit
$5-$10 Limit
$1-$2 No Limit
Context Three
floor
floor
brush
brush
Monday, May 15, 2006
Apple in Detail
I recently bought a MacBook Pro and I'm quite happy with the purchase. Apple's attention to detail is amazing.
If you are holding out or still deciding, take the jump, you won't regret it.
If you are holding out or still deciding, take the jump, you won't regret it.
Developing Ruby in TextMate
I recently made the switch from IntelliJ to TextMate. So far I'm happy despite losing some features such as local history and Subversion integration. I haven't really found anything in TextMate that I couldn't do in IntelliJ, but I find I'm happier in TextMate so far. I think fighting with IntelliJ to work with Ruby wore on me. I also found IntelliJ was slightly annoyingly slow.
If you haven't already, I suggest giving TextMate a chance.
If you haven't already, I suggest giving TextMate a chance.
Tuesday, May 09, 2006
DRY code, DAMP DSLs
Everyone knows code should be DRY, but does the same rule apply to Domain Specific Languages? Based on my experience designing a DSL based system for a major bank, I believe the answer is no.
One reason for using a Domain Specific Language is to separate the business rules from the complexities of designing the system. When done correctly, the business rules can be maintained by business users who are the most familiar with the problem space. To a business user a DSL should be no different than a group of phrases that describe the rules for running the business correctly.
A well designed Domain Specific Language will appear as Descriptive And Meaningful Phrases.
In my previous post I described a DSL designed for running a poker room. I defined the syntax of the DSL as:
As businesses change, their software requires changes also. By providing a meaningful syntax such as
A sure sign that there is room for improvement is when a DSL requires training to understand. An ideal DSL contains phrases that are descriptive and meaningful enough that they require no explanation at all.
One reason for using a Domain Specific Language is to separate the business rules from the complexities of designing the system. When done correctly, the business rules can be maintained by business users who are the most familiar with the problem space. To a business user a DSL should be no different than a group of phrases that describe the rules for running the business correctly.
A well designed Domain Specific Language will appear as Descriptive And Meaningful Phrases.
In my previous post I described a DSL designed for running a poker room. I defined the syntax of the DSL as:
if the '$5-$10 Limit' list is more than 12 then notify the floor to openIn the example I defined a
bubble method that created methods for each member of the list passed to bubble. Each method that was defined by bubble took one value and returned the same exact value. This allowed the DSL to be verbose enough that not every word was required to have meaning. In fact 'the', 'list', 'is', 'than', and 'to' are all bubble methods. Without bubble methods the DSL can be DRYed out to read:notify floor open if '$5-$10 Limit' more 12However, the meaning of the business rule has been lost at the expense of removing the duplication.
As businesses change, their software requires changes also. By providing a meaningful syntax such as
"if the '$5-$10 Limit' list is more than 12 then notify the floor to open" the software can be altered by poker room managers, casino executives, or anyone well educated on poker room management. However, with every word that is removed the message becomes less descriptive and thus less maintainable. A sure sign that there is room for improvement is when a DSL requires training to understand. An ideal DSL contains phrases that are descriptive and meaningful enough that they require no explanation at all.
Saturday, May 06, 2006
Executing an internal DSL in multiple contexts
Creating an internal DSL (or embedded DSL) is challenging, but it does provide many advantages. One advantage to expressing your business rules in an internal DSL is the ability to execute them in various contexts. By executing the DSL in various contexts you can generate multiple behaviors from the same business rule. When the rule changes over time, all parts of the system that reference the rule will also be changed.
For example assume you work for a casino and you have been tasked with designing a system that will notify the poker room employees when a new table needs to be opened or when you are looking to open a new table. The rules for opening a table vary based on the stakes of the table and the length of the waiting list. For example, you need more people waiting for a no limit game because people go broke more quickly and you don't want the table to be short-handed shortly after you open it. The rules would be expressed in your DSL like this:
Based on this same script you could execute a second context that returns a list of the different games that are currently being spread.
A note on implementation: In my experience it is much easier to create a class per context and execute in the scope of that object. An alternative is to execute the script in the scope of one object that creates a generic object graph. The problem with this approach is finding an object graph that is generic enough to be useful in several situations. Clearly, I prefer the first approach.
Sample Code: http://www.jayfields.com/src/dslcontext.txt
For example assume you work for a casino and you have been tasked with designing a system that will notify the poker room employees when a new table needs to be opened or when you are looking to open a new table. The rules for opening a table vary based on the stakes of the table and the length of the waiting list. For example, you need more people waiting for a no limit game because people go broke more quickly and you don't want the table to be short-handed shortly after you open it. The rules would be expressed in your DSL like this:
if the '$5-$10 Limit' list is more than 12 then notify the floor to openThe first context in which I will execute the DSL is the context that notifies the employees.
if the '$1-$2 No Limit' list is more than 15 then notify the floor to open
if the '$5-$10 Limit' list is more than 8 then notify the brush to announce
if the '$1-$2 No Limit' list is more than 10 then notify the brush to announce
class ContextOne < DslContextContextOne uses the DSL to check the List for the size per stakes and sends notifications when necessary. This is of course sample code and my List object is just a stub to verify that everything works correctly. I'll add a link to the sample code at the end of the post.
bubble :than, :is, :list, :the, :to
def more(value)
'> ' + value.to_s
end
def method_missing(sym, *args)
@stakes = sym
eval "List.size_for(sym) #{args.first}"
end
def floor(value)
__position(value, :floor)
end
def brush(value)
__position(value, :brush)
end
def open
__action(:open)
end
def announce
__action(:announce)
end
def __action(to)
{ :action => to }
end
def __position(value, title)
value[:position] = title
value
end
def notify(value)
[@stakes, value]
end
end
Based on this same script you could execute a second context that returns a list of the different games that are currently being spread.
class ContextTwo < DslContextAs you can see, adding additional contexts is very easy. Another could be added to display all positions that are set up to receive notices.
bubble :than, :is, :list, :the, :to, :more, :notify, :floor, :open, :brush
def announce
@stakes
end
alias open announce
def method_missing(sym, *args)
@stakes = sym
end
end
class ContextThree < DslContextExecuting a DSL script in multiple contexts begins to blur the line between code and data. The script 'code' can also be executed to do things such as generate reports (i.e. A report of which employees are contacted by the system). The script could also be executed in a context that will show how long before a table will be opened (i.e. the rule states that 15 are needed, the system knows 10 are on the list so it displays the message '5 more people needed before the game can start').
bubble :than, :is, :list, :the, :to, :more, :notify, :announce, :open, :open
def announce; end
def open; end
def brush(value)
:brush
end
def floor(value)
:floor
end
def method_missing(sym, *args)
true
end
end
A note on implementation: In my experience it is much easier to create a class per context and execute in the scope of that object. An alternative is to execute the script in the scope of one object that creates a generic object graph. The problem with this approach is finding an object graph that is generic enough to be useful in several situations. Clearly, I prefer the first approach.
Sample Code: http://www.jayfields.com/src/dslcontext.txt
Friday, May 05, 2006
IntelliJ Ruby Syntax Highlighting Plugin
I'm very happy to see that the previous hack I detailed for IntelliJ is no longer necessary.
Following my post (a few days later) I got this comment:
My initial impression was not very good. Then I realized that few options in Colors & Fonts (CTRL + ALT + S, F) were set by default. I made a few quick changes to the color scheme that I prefer and was quite pleased.
Also, I made a few changes in the config to add some extra support:
In simplesyntax_ruby.config I added:
This change supports highlighting symbols.
I also modified the tags\keywords.rb file change my keyword array to be:
Overall, super job Daniel.
Following my post (a few days later) I got this comment:
Quick follow-up: I spent quite a few evenings working on a generic highlighter (with basic Ruby highlighting as the example config). You may want to have a look here.
Cheers, Daniel
My initial impression was not very good. Then I realized that few options in Colors & Fonts (CTRL + ALT + S, F) were set by default. I made a few quick changes to the color scheme that I prefer and was quite pleased.
Also, I made a few changes in the config to add some extra support:
In simplesyntax_ruby.config I added:
regex SYMBOLS => :[a-z][A-Za-z0-9_]+
descriptions[ SYMBOLS ] = SymbolsThis change supports highlighting symbols.
I also modified the tags\keywords.rb file change my keyword array to be:
KEYWORDS = ["alias", "and", "BEGIN", "begin", "break", "case", "class", "def", "defined", "do", "else", "elsif", "END", "end", "ensure", "false", "for", "if",
"in", "module", "next", "nil", "not", "or", "redo", "rescue", "retry", "return", "self", "super", "then", "true", "undef", "unless", "until", "when",
"while", "yield"]Overall, super job Daniel.
Monday, May 01, 2006
Setting up a printer
As a consultant I'm constantly working from different locations and printing to foreign printers. For some reason, printers seem to be a black art to most of us who are otherwise quite comfortable working with technology. I too used to fear printers until Pat Sarnacke showed me the one true way to enlightenment: Printing to a Standard TCP/IP Port.
steps to printer nirvana:
steps to printer nirvana:
- Print the configuration of the printer to get the printer's IP address
- Plug in to the same network the printer is on. (ping if necessary to find out)
- Open the windows add printer wizard
- Select "Local printer attached to this computer" AND uncheck "Automatically detect and install my Plug and Play printer"
- Select "Create a new port:"
- Set "Type of port:" to "Standard TCP/IP Port"
- The Add Standard TCP/IP Printer Port Wizard should appear, click Next
- Enter the IP address into the "Printer Name or IP Address" textbox
- Use the default "Port Name", click Next (The 2nd wizard should close)
- Select your printer from the list
- Give your new printer a name
- Do not share, print a test page, and click Finish because you are done.
Why do *YOU* like Ruby?
An old friend of mine recently sent me this:
#1. It's easy to add behavior to objects. You can extract patterns and put them in modules or add them to existing classes. Once you have added the behavior you can quickly add responsibilities to your objects with much less code. Since I've been developing in Ruby I've found myself using Attribute Initializer and Initialization Chain quite often. By having the patterns for both of these behaviors extracted out to class methods I can reduce the code necessary which also reduces the potential for errors.
#2. Ruby's duck typing allows me to easily create dependencies. I strongly believe in the Unit Testing Guidelines I previously posted. The guideline I suggest in that entry is: Only one concrete class (with behavior) should be used per test. Because of duck typing I can follow this rule by creating stub dependencies without worrying about interfaces. Consider the following class:
Now, I want to test the Consultant class in isolation so I need to create stubs for Phone, Laptop, and Boss. In a statically typed language I would need a Phone, Boss and Laptop interface and each stub would need to implement the full behavior of the interface. However, in Ruby I can create simple stubs that only define behavior I'm interested in:
#3. The dynamic features of the language allow me to more easily write Domain Specific Languages (DSL). At my current project we've provided the client with a domain specific language that allows definition of variables and reuse of those variables later in the script.
In the script a variable is defined similar to
The same functionality could be achieved by changing the
Similarly, I recently had a requirement build an ad-hoc sentence using dot notation. For example,
In short, why do *YOU* like Ruby?It's a fair question. I'm currently developing exclusively in Ruby and writing almost only about Ruby. The short answer is Ruby requires less to get the job done. I'll elaborate, but remember, this is why *I* like Ruby, not an attempt to compare Ruby to any other language.
#1. It's easy to add behavior to objects. You can extract patterns and put them in modules or add them to existing classes. Once you have added the behavior you can quickly add responsibilities to your objects with much less code. Since I've been developing in Ruby I've found myself using Attribute Initializer and Initialization Chain quite often. By having the patterns for both of these behaviors extracted out to class methods I can reduce the code necessary which also reduces the potential for errors.
#2. Ruby's duck typing allows me to easily create dependencies. I strongly believe in the Unit Testing Guidelines I previously posted. The guideline I suggest in that entry is: Only one concrete class (with behavior) should be used per test. Because of duck typing I can follow this rule by creating stub dependencies without worrying about interfaces. Consider the following class:
class ConsultantConsultant has a dependency on Boss, Phone, and Laptop. Additionally, when a Consultant instance is created it calls the
def initialize(boss, phone, laptop)
@boss, @phone, @laptop = boss, phone, laptop
@phone.on if @phone.off?
@laptop.on if @laptop.off?
end
def text_boss(message)
@phone.text(message,@boss.number)
end
def send_email(email)
@laptop.send_email(email)
end
end
on method of both the phone and laptop. Because the Consultant instance calls methods on phone and laptop in the constructor, nil cannot be passed as either argument. Now, I want to test the Consultant class in isolation so I need to create stubs for Phone, Laptop, and Boss. In a statically typed language I would need a Phone, Boss and Laptop interface and each stub would need to implement the full behavior of the interface. However, in Ruby I can create simple stubs that only define behavior I'm interested in:
class StubLaptop; def on; end; def send_email(e); end; endNow I can use these stubs and define mocks that allow me test only the isolated Consultant class and it's interactions. Also, these stubs are really only in the test for support, thus making them essentially noise. The less noise I am required to include in a test the more readable (and maintainable) the test becomes.
class StubPhone; def on; end; def text(msg, num); end; end
class StubBoss; def number; end; end;
#3. The dynamic features of the language allow me to more easily write Domain Specific Languages (DSL). At my current project we've provided the client with a domain specific language that allows definition of variables and reuse of those variables later in the script.
In the script a variable is defined similar to
define :jay => thoughtworks.developer. Obviously, we are really using Ruby to call the method define with a hash where :jay is the key and thoughtworks.developer is the value. When the define method is executed it defines a method dynamically by the key and sets the return value to the value of the hash. Later in the script the variables are used similar to work jay, mike, charles. Again, no magic here, the work method is being called with the return values from the jay, mike, and charles methods. However, the end result is a very readable DSL which empowers my business users to code part of the system.The same functionality could be achieved by changing the
work line to read work :jay, :mike, :charles and storing the values in a hash. But, this is a step down from the above syntax in most business users eyes.Similarly, I recently had a requirement build an ad-hoc sentence using dot notation. For example,
this.was.the.syntax.but.the.sentence.needs.to.be.free.form. By using method_missing I was able to support this style with very little code required other than protecting keywords.

