Posted by Michael Brennan on Friday, April 1, 2011
My new blog is at http://thoughts.michaelbrennan.net
Using the ‘var’ keyword in C# has always spurred a hot debate among developers. I believe ‘var’ should be used at all times. I believe this not because I choose to be “lazy,” as those who argue against it frequently claim. Of all the reasons I use ‘var’, laziness is not one of them.
I’ve argued for the constant use of ‘var’ countless times; this blog post is a collection of thoughts that I have compiled resulting from my arguments. Below are my reasons for using ‘var’ all of the time.
It decreases code-coupling
Coupling between code and its dependent code can be reduced by using ‘var’. I do not mean coupling from an architectural perspective nor at an IL-level (the type is inferred anyway), but simply at the code level.
Imagine there are 20 explicit type references spanning over twenty code files to an object that returned an another object of type IFoo. By explicit type references, I mean by prefacing each variable name with IFoo. What happens if IFoo changes to IBar, but the interface’s methods are kept the same?
Wouldn’t you have to change it in 20 distinct places? Doesn’t this increase coupling? If ‘var’ was used, would you have to change anything? Now, one could argue that it is trivial to change IFoo to IBar in a tool like ReSharper and have all of the references changed automatically. However, what if IFoo is outside of our control? It could live outside the solution or it could be a third-party library.
It is completely redundant with any expression involving the "new" operator
Especially with generics:
ICalculator<GBPCurrency, GBPTaxType> calculator = new GBPCalculator<GBPCurrency, GBPTaxType>
can be shorted to:
var calculator = new GBPCalculator<GBPCurrency, GBPTaxType>
Even if calculator is returned from a method (such as when implementing the repository pattern), if the method name is expressive enough it is obvious the object is a calculator. The name of the variable should be expressive enough for you to know what the object it represents is. This is important to realize: the variable expresses not what type it represents, but what the instance of that type actually is. An instance of a type is truly an object, and should be treated as such.
There is a distinction between an object and its type: an object exists at runtime, it has properties and behaviors; types simply describe what an object should be. Knowing what type an object should be simply adds more noise to the source code, distracting the coder from what an object really is.
An object may be brought into this world by following the rules governed by a type, but this is only secondary information. What the object actually is and how it behaves is more important than its type. When we use an object at runtime, we are dependent on its methods and properties, not its type. These methods and properties are an object’s behaviors, and it is behaviors we are dependent upon.
The argument for knowing a variable's type has been brought up in the past. The move from Hungarian notation in Microsoft-based C++ to non-Hungarian notation found in C# is a great example of this once hot topic. Most Microsoft-based C++ developers at the time felt putting type identifiers in front of variable names was helpful, yet Microsoft published coding standards for C# that conflicted with these feelings.
It was a major culture change and mind-shift to get developers to accept non-Hungarian notation. I was among those who thought that non-Hungarian variable naming was downright wicked heresy and anyone following such a practice must be lazy and did not care about their profession. If knowing a variable’s type is so important, shouldn’t we then preface variable names in Hungarian style to know more information about an object's type?
You shouldn't have to care what the type of an object is
You should only care what you are trying do with an object, not what type an object may come from. The methods you are attempting to call on an object are its object contract, not the type. If variable names, methods, and properties are named appropriately, then the type is simply redundant.
In the previous example, the word "calculator" was repeated three times. In that example, you only need to know that the instance of a type (the object) is a calculator, and it allows you to call a particular method or property.
The only reason a calculator object was created was so that other code could interact with its object contract. Other code needs the calculator’s methods and properties to get something done. This need has no dependency on any type, only on an object’s behaviors..
For example, as long as the object is a calculator, and the dependent code needs to call a method named “CalculateTax,” then the dependent code is coupled to an object with a method called “CalculateTax” and not a specific type. This allows for much more flexibility, because now the variable can reference any type as long as that type supports the “CalculateTax” method.
‘var’ is less noisy than explicitly referencing the type
As programming languages evolve, we spend less time telling the compiler and the computer what to do and more time expressing problems that exist in the specific domain we are working in.
For example, there are a number of things in C++ that are very technical with respect to the machine, but have nothing to do with the domain. If you are a customer of Quicken or Microsoft Money, all you really want to do is manage your finances better. These software packages allow you to do that.
The better a software package can do this for you, the more valuable it is to you. Therefore, from a development perspective value is defined by how well a software package solves a user's problem. When we set out to develop such software, the only code that is valuable is the code that contributes to solving a particular user’s problem. The rest of the code is unfortunately a necessary waste, but is required due to limitations of technology.
If we had infinite memory, we would not need to worry about deleting pointers in C++ or garbage collection in C#. However, memory is a limitation and therefore the technician in us has to find ways of coping with this limitation.
The inclusion of ‘var’ into the C# language was done for a reason and bookmarks another iteration of C# (particularly C# 3.0). It allows us to spend less time telling the compiler what to do and more time thinking about the problem we are trying to solve.
Often I hear dogma like "use var only when using anonymous types." Why then should you use an anonymous type? Under these conditions you usually do not have a choice, such as when assigning variables to the results of LINQ expressions. Why do you not have a choice when using LINQ expressions? It's because the expression is accomplishing something more functional and typing concerns are the least of your worries.
In the ideal C# world, we would not have to put any words in front of a variable name at all. In fact, prefacing a variable with anything just confuses the developer even further, and allows for poor variable names to become a standard whereby everyone is reliant upon explicit type references.
Arguments against using ‘var’
Some of the arguments I have heard against using ‘var’ and my responses to these are:
- “It reduces clarity” – How? By removing the noise in front of a variable name, your brain has only one thing to focus on: the variable name. It increases clarity.
- “It reduces readability and adds ambiguity” – This is similar to #1: readability can be increased by removing words in front of the variable and by choosing appropriate variable names and method names. Focusing on type distracts you from the real business problem you are trying to solve.
- “It litters the codebase” – This is usually an argument for consistency. If your codebase uses explicit type references everywhere, then by all means do not use ‘var’. Consistency is far more important. Either change all explicit references in the codebase to ‘var’ or do not use ‘var’ at all. This is a more general argument that applies to many more issues, such as naming conventions, physical organization policies, etc.
As a final thought, why do we preface interface names with “I” but not class names with “C” as we did in the days when Microsoft-C++ was the popular kid in school?
Context Specification is a lot more than merely a new way to write tests. Although it was conceived around the same time as Behavior-Driven Development (BDD), and probably inspired by some of the BDD concepts, it is quite different.
The difference between Context Specification and BDD lies in semantics and purpose. Both are concerned about driving development through behaviors rather than implementation artifacts like classes or components.
BDD frameworks are typically purposed to acceptance testing. Recently, programmers encouraged customers to participate in requirements specification by writing the acceptance tests with tools like FIT. Acceptance testing frameworks are built with the assumption that business experts will be writing tests.
Acceptance testing frameworks often presume the user experience will be as rich as it is for the programmer. However, a programmer’s notion of readability and elegance is usually quote different than a non-programmer’s notion of readability and elegance. Often, programmers’ notions of readability and elegance will be different among themselves.
The Given/When/Then grammar is a good example of programmer’s presumption of a good user experience. We have to remember that when we create a tool to be used by non-programmers, we are designing user interaction and user experience. Programmers are historically the least effective people in creating a good user experience.
Given/When/Then grammar is good for capturing all of the details of a given scenario, but chooses to convey a lot of information in prose. Using prose for this purpose often leads to run-on sentences that defeat effective communication.
Specifications written with the Given/When/Then grammar are just as likely to be hard to read. The grammar can be an obstruction to learning, communication, and understanding.
Creating descriptions of specific uses of software is Context Specification’s primary concern. As with BDD and Test-Driven Development, the descriptions are specifications that end up driving development, and leave behind an automated test suite.
Who Writes Specifications? Who Writes Tests?
Programmer’s don’t like to write documentation, and business experts don’t like to write tests. Context Specification is about creating a specifications document with code. However, it doesn’t presume business experts will be writing the tests. Programmer’s write the test; that’s what they’re good at; business experts are good at being business experts.
Context Specification’s semantics focus on transforming traditional tests into specifications. These specifications serve as descriptions and documentation for how a system behaves under the various situations that the system is put into. Context Specification grammar is designed to make specifications easy to read and understand.
The goal is to create specifications that can be understood by anyone. If defining specifications for business experts, the business experts should be able to understand the specifications that you are writing. The implementation of the specifications in code should be easy to understand by programmers.
Context is a word used to describe the situations and conditions a system is put into by a user. Context is often not fresh on the minds of programmers when writing tests because we’re often too caught up in thinking of the technical aspects of the problem.
Focusing on the programming language and the tools around them can distract programmers from context. Scott Bellware has spoken several times about this concept, and his work is my motivation for this article.
We need to do our best to be aware of context as we write tests, or specifications. We need to avoid thinking like programmers when writing and programming specifications. Context Specification can help us with this thinking.
The Context Specification Mindset
Scott Bellware describes the proper Context Specification mindset to have by stating, “Specify the experience, not the implementation.”
Instead of thinking about technical terms and programming ideas like classes, methods, and modules when writing test, consider the subject of the test in a particular context that has a related set of side-effects that you can observe. The observable side-effects are called specifications in Context Specification grammar.
The test suite isn’t really organized around specific abstractions in your system, unless the abstractions are organized around system behavior instead of data. Subjects and context can describe one class or several classes. Specifications reflect the observable effects of one method or several methods.
Subjects, contexts, and specifications show the true value of the code you are creating from the perspective of business objectives rather than implementation details. If you write specifications for subjects within a context, you end up writing code for what is truly important within your domain.
Example Context Specification Thought Process
There are a number of frameworks and methods to practice Context Specification with. In the .NET world, SpecUnit.NET, MSpec, Specter, or simply NUnit with some syntactic sugar can be used to write specifications.
While these are very functional frameworks, I believe the Ruby RSpec framework is the most elegant and easiest to use. Therefore, to make things simpler I will be writing all of my specifications using RSpec and all of my production code in Ruby.
(I intend to write an article on how to use IronRuby with RSpec to test .NET code; stay tuned)
Let’s use an iPod as an example since most people are familiar with how iPods function at a basic level. While we write specifications we will remain aware of the context by not performing a technical dissection of the problem.
Context Specification grammar requires a subject, context, and observable conditions. In this example, the subject is an iPod. One of the contexts could be, “when playing.”
What conditions can you observe when an iPod is playing? Without Context Specification, the answer depends on who is answering the question. If a programmer is answering the question, you will get a very technical answer. If a layman is answering the question, you will get a more general answer.
The laymen might say, “when an iPod is playing, it should display the track name, artist, and album name; it should also play the track.”
Conversely, a programmer might say, “when an iPod is playing, it should find the audio file in the audio file database, load it into the audio player, and send a signal to the audio player to start playing the audio track, update the LCD to re-draw the screen with the appropriate information,” and this can go further into more technical dissection. The programmer’s dialog is hyper-descriptive.
However, with Context Specification, the specifications should be defined in a way so that almost anyone can understand them. Because the layman understands the context in a more general circumstance, their specifications are more acceptable.
Typically, the best person to work with when defining specifications is the person who knows the specifics of how a system should behave, without necessarily knowing how the technical aspects should be woven together. A product owner is a good example of this.
Product owners know how their product helps their business, and how it may help their customers. However, they really don’t care about implementation details, such as the usage of a producer-consumer pattern to manage I/O requests, or adhering to SOLID principles.
Defining a Specification
Let’s start writing specifications for when an iPod is playing. One of the more obvious specifications, as stated from the laymen’s quote above, is “it should display the track name.”
Using RSpec, here is the code that defines this specification:
1: require 'spec'2:3: describe "iPod", "when playing" do4: it "should display the track name"5: end
Now let’s look at the RSpec output from this specification:
*Pending:iPod when playing should display the track name (Not Yet Implemented)./ipod_spec_definition.rb:4Finished in 0.015 seconds1 example, 0 failures, 1 pending
RSpec informs us that there is 1 example and 1 pending, because we haven’t written the implementation of the specification yet. Notice the readable output? RSpec can generate a nifty HTML document with all of the specifications for reference. This can be helpful to business experts for verifying the correctness of specifications.
It is important to note that the context is typically in the gerund form of a verb (“ing” form, e.g “when running,” “when drinking,” etc.) This eliminates accidental communication of timing or synchronization factors to the reader.
For example, the context “when beginning to run” is irrelevant because it is inclusive in the context “when running.” The same is true for “when finished running” because that statement occurs “when running” as well. Programmers might not immediately understand this logic, but from a communication mindset and for the purposes of documentation it conveys the appropriate intent.
Implementing a Specification
Let’s start implementing the first specification: “iPod when playing should display the track name.”
Following traditional TDD practice, the implementation starts by building a skeleton of the code required for the compiler and with a failing test:
1: require 'spec'2: require 'not_a_mock'3:4: class Ipod5: def play6: end7:8: def display_track_name9: end10: end11:12: describe Ipod, "when playing" do13: before :each do14: @ipod = Ipod.new15: @ipod.track_methods16: @ipod.play17: end18:19: it "should display the track name" do20: @ipod.should have_received(:display_track_name)21: end22: end
The implementation of the first specification is performed by providing a body to the specification we are trying to implement, asserting that the iPod should received the display_track_name method.
Since the instance variable “ipod” does not exist, the “before” block is added to create a new instance of an “Ipod” class and assigns it to the instance variable needed. The “before” block is run before each specification is run.
Since all specifications will require an instance of an Ipod, this block makes writing future specifications convenient and further clarifies what subject the specifications are being written for (or more traditionally, the “system under test”).
In order for the Ruby compiler to accept this code, we have to provide a skeleton Ipod class and define the methods that are being called within the specification and the “before” block (“play” and “display_track_name”).
Note that I’ve elected to use a mocking framework called NotAMock. NotAMock is an extension to RSpec that records all calls on an object in question. I’ve further extended the framework so that you can call “track_methods” without specifying arguments to the method. Previously, you would have to specify each method you wanted to track. My change has been merged to the trunk and can be found here.
After running the new changes in the RSpec runner, the output is:
F1)'Ipod when playing should display the track name' FAILED#<Ipod:0x2b90c24> didn't receive display_track_name./ipod_spec_implementation_failing.rb:20:Finished in 0.094 seconds1 example, 1 failure
Notice the test failed with the message “#<Ipod:0x2b90c24> didn't receive display_track_name". RSpec is reporting that the “display_track_name” method on the instance of the Ipod class was never called.
Now it’s time to execute the next traditional step of TDD: make the test pass with the least amount of work. Adding a call to the display_track_name method inside the play method satisfies the RSpec test. Re-running the RSpec runner will show that the test indeed passes without error.
Now, obviously simply adding a call to the display_track_name method within the play method is an incorrect implementation. Therefore, we are lead to the final phase of traditional TDD: refactor:
1: require 'spec'2: require 'not_a_mock'3:4: class TrackRepository5: def self.get_random_track_set6: # query database to return random track set7: [ "track1", "track2", "track3" ]8: end9: end10:11: class Lcd12: def display_track_name(track_name)13: # interface with hardware to display track name14: end15: end16:17: class Playset18: attr_reader :current_track_name19:20: def self.create_random_playset21: tracks = TrackRepository.get_random_track_set22: playset = Playset.new(tracks)23: end24:25: def initialize(tracks)26: @tracks = tracks27: @current_track_name = @tracks28: end29: end30:31: class Ipod32: def initialize33: @lcd = Lcd.new34: @current_play_set = Playset.create_random_playset35: end36:37: def play38: display_track_name(@current_play_set.current_track_name)39: end40:41: def display_track_name(track_name)42: @lcd.display_track_name(track_name)43: end44: end45:46: describe Ipod, "when playing" do47: before :each do48: @ipod = Ipod.new49: @ipod.track_methods50: @ipod.play51: end52:53: it "should display the track name" do54: @ipod.should have_received(:display_track_name)55: end56: end
The “play” method now asks the “current_play_set” instance variable what the current track name is and provides it to the “display_track_name” method. This leads to creating the “current_play_set” instance variable, the “Playset” class, and a few other classes and methods.
After running the RSpec test runner again, the above code satisfies the test without issues. We know that we’ve safely refactored our code to a decent implementation without changing any behavior.
It’s important to realize that one specification could drive out several methods and several classes. It’s not necessary to pair subjects to classes, and specifications to methods. Notice the specification we implemented lead to the creation of 4 classes and several methods.
Once further specifications have been introduced at a more detailed level, it’s quite possible to switch contexts or subjects (or both) and further define specifications. However, this is not a license to start specifying technical terms in our native programming mindset.
Technical specifications should be reworded in less technical terms as much as possible. Nonetheless, it’s still possible to run into scenarios where technical speech is unavoidable such as in describing state machines.
Context Specification’s focus is not about providing a new tool to write tests in an innovative way. Programmers obsess over tools, and often the goal of Context Specification is eclipsed by programmer mentality to find new ways of doing things.
While it does provide a much different way of writing tests, it requires a change in traditional programmer thinking. A subject within a context must be thought of, and observable conditions form the specifications for that subject in its context.
This demands programmers sway their technical minds from more of a usability, high-level, business oriented mind while defining specifications, and back down to the essence of the technology they are using when writing the code that satisfies those specifications.
Providing documentation this way ensures that whomever can understand the subject and its context can understand the documentation and how that subject is used. This swaying prevents writing documentation that is too technical.
There are few instances when technical specifications need to be defined for a particular subject or context. However, one example where I’ve run into that particular situation involves a sub-system that performs connection caching. It is perfectly acceptable to write specifications for the caching operations under these circumstances because they define an inherent requirement.
Context Specification allows you to write documentation for subjects within specific contexts that are most important, and makes you more aware of what you are trying to test and write code for.
Domain awareness is strengthened by thinking more about a subject and context and less about object-oriented technical feats and the latest and greatest programming technology of the hour. The quality of a solution’s design and code is improved by getting closer to the true problems in the domain.
I hope you have fun writing specifications for your subjects and their contexts!
Special thanks to Scott Bellware for providing the inspiration of these article and for reviewing it.
Fábio Rehm and I have recently started talking about DDD and how to best implement some of the ideas in Ruby. He just posted a very interesting article on his attempt at implementing a basic customer/order DDD example in Ruby. Check it out: http://dddonruby.wordpress.com/2009/06/08/my-aproach-for-doing-ddd-on-ruby-introduction-and-part-i
The Technical Aptitude Required for Object-Oriented Programming
One of the primary purposes of an object-oriented programming language is to recreate representations of solutions to specific problems with code. These representations are transformations of solutions that were typically solved previously with human interactions.
Sometimes certain problems are solved only with the feat of modern computer software. In other facets, ingenuous minds forge an inventive technology in software not unlike the ways of the great mechanical inventors of our age.
Traditional object-oriented programming has been one of those great technical inventions. While simple guidelines allow most programmers to attempt object-oriented programming, only few understand the principles and practices to actually model real concepts correctly.
Frequently, programmers create objects that simply do not exist in the domain, or are bastardizations of the true domain concept. Unfortunately, in these scenarios success is short-lived. Maintainability quickly degrades, and often the code is completely thrown away.
Incorrect representations of the domain are similar to the adage of trying to fit a square peg into a round hole. Perhaps it’s possible to do this with creative technical feats. However, as more features are requested, as bugs are fixed, and the code evolves, misrepresentations are quickly revealed.
When this happens, feature requests and bug fixes take longer than expected, and a tremendous amount of pressure is put on the development team to meet deadlines. Managers and executives begin to wonder why it is taking so much time to complete what they think are smaller tasks.
All-nighters, pizza, and an insane amount of coffee support the developers through their death march. If you have been developing for a decent amount of time, I am sure you have told of such horror stories over lunch or over a good beer.
The Expressiveness of a Language Matters
How do we prevent this pain? There are several factors that contribute to these unfortunate events, however expressiveness within source code can be an important piece of the puzzle. The best developers are often the most expressive in the language he or she is operating in.
Instead of engineering solutions using technical wizardry and concepts that only programmers can understand, being expressive within the source code can keep business rules intact as development continues and solutions evolve.
Expressiveness is a quality that developers must constantly be working toward. Interestingly enough, the choice of a programming language can greatly assist in this struggle. The more expressive a language is, the easier it is for the programmer to think about the domain.
If non-programmer types and developers communicate with a ubiquitous language, this language can pervade into the source code. The pervasiveness of a domain’s language allows ideas to be more easily understood.
Contrasting Language Expressiveness
On May 30th, 2009, Scott Bellware gave a talk entitled, “A Handful of Things You Can Do in Ruby That Scares the Pants Off of C# Developers.” In this fascinating talk, Scott showed two functionally equivalent, plain classes: one defined in C#, the other defined in Ruby.
An interesting illustration emerged when Scott showed how the C# class’s definition comprised of a lot more statements to instruct the compiler on what to do and how to perform. These statements take the programmer’s mind away from the domain and bring him or her to thinking in a technical programming wonderland.
On the other hand, the Ruby class contained very little compiler instructions; the class was mostly comprised of domain expressions. This is a powerful illustration, so I’d like to show it as well as a means of illustrating expressiveness.
Let us define a simple class that records recommendations of businesses. Recommendations can be used for any business type, such as restaurants. A ‘recommender’ can recommend a business only once. The class will allow a means to determine how many recommendations all of the businesses have received.
The C++ Definition (brace yourself)
First, I will define the class using one of the most traditional object-oriented programming languages: C++. Let’s start as we always do with C++: with a header file definition:
This will define the basic contract that consuming code will use to work with the RecommendationTracker class. Pay particular attention to the usage of pointers, references, “constness”, and other statements used to tell the compiler what to do.
Now that we’ve declared the contract, lets look at the overly verbose definition of the class:
Once again, we have several statements regarding constant state of pointers, language like “it->second.size()”, which refers to the size of a vector that is the value portion of a map. Look at how the class name is repeated before each function definition; this tells the compiler what scope we are in.
Although not appearing in this example, frequently C++ programmers have to tell the compiler whether they want to allocate objects’ memory on the stack or heap, and have to manage the lifetime of that memory.
All of these intrinsic C++ concepts force the programmer to be very technical. Much time is spent on considering the language itself rather than the domain.
The C# Definition (a little better)
Now lets port this same class to C#. C# is a bit more modern than C++, slightly more functional, and isn’t so concerned about telling the compiler what to do.
One slight problem: since there is no easy way to return a list of pairs as in the C++ definition, I’ve created a small helper class called “RecommendationTotal.” I could have returned a dictionary, but the usage of a dictionary in C# usually suggests something other than name/value pairs:
Some may argue that this adds readability to the code; for C#, it probably does. Now, the definition of the RecommendationTracker class is as follows:
It’s nice that the declaration and definition are now combined into one, and we don’t have to deal with header files anymore. However, there are still many references to compiler instructions.
Every method must tell the compiler whether it should make the method public or private. There are several references to types, such as “IEnumerable<RecommendationTotal>” and “IDictionary<string, ICollection<string>>.”
These compiler instructions and type declarations take our minds away from thinking about the business and have us spend more time on thinking about programming.
The Ruby Definition
Now let’s look at the same code in Ruby:
1: class RecommendationTracker2: def add_recommendation(recommender, business)3: has_recommender_recommended_business?(recommender, business) do4: raise StandardError, 'You may only recommend the same business once.'5: end6:7: @recommendations[business] << recommender8: end9:10: def recommendation_totals11: recommendation_totals = Hash.new12:13: @recommendations.each_key do |business|14: recommendation_totals[business] = @recommendations[business].length15: end16:17: recommendation_totals18: end19:20: private21: def initialize22: @recommendations = Hash.new do |recommendations, business|23: recommendations[business] = Array.new24: end25: end26:27: def has_recommender_recommended_business?(recommender, business)28: return false if !@recommendations.has_key? business29: return false if !@recommendations[business].include?(recommender)30:31: yield32: true33: end34: end
Notice how concise the Ruby definition is? Compiler instructions are kept to a minimum, and the statements seems to emphasize the logic of the domain more. Furthermore, all unnecessary references to types have been removed.
Simple one line boolean statements have been rearranged in a “action if condition” format instead of “if action then condition.” Methods have a question mark to add to readability. Overall, the code is simply a lot more expressive.
Contrast the Ruby definition to the original C++ definition. We’ve come a long way haven’t we? What’s frightening is to think that Ruby was created in 1992! For some reason, it’s only recent that it has been receiving a lot of attention (probably due to Rails and the direction of software toward web platforms).
C++ is still a great language, and has its place within specific industries. Game development is still primarily performed in C++, mainly due to the performance requirements where every bit of speed must be squeezed out of the code.
C++ is definitely a professional programming language, and requires the programmer to understand the power it can provide. A high degree of technical aptitude is required to create concrete, easy to understand, scalable, and powerful applications in C++. Unfortunately, it also takes more time.
However, instead of focusing on technical aptitude, developers should focus on adding value to the domain they are developing for. This means most thought should be spent thinking about how best to express the core domain, where all of the business rules and logic should reside.
Compiler instructions and nifty technical tricks are cool, but they take time away from adding to the core domain. As Eric Evans has illustrated, the easiest way to determine the core domain is ask why is the development effort important? How will the code you are writing help the business compete?
The answer to those questions should point you straight to the core domain. Expressiveness allows developers to define the core domain with a refined eloquence that helps lead to a deeper understanding of the problems at hand.
If source code is less infected with technical concepts, true domain problems are more easily read. This greatly adds to maintainability, and allows object-oriented principles to be practiced more easily.
I am excited to see more developers diving into Ruby. Hopefully, as the community becomes even more established than it already is, we will all start creating more elegant designs and expressions of problems without focusing too much on telling the compiler what to do.